← back to Wine Finder Next

lib/affiliates/urlBuilder.ts

170 lines

import { AffiliatePartner } from '../types/affiliate';
import { Wine } from '../types/wine';
import { getPartnerById } from './partners';

/**
 * Build affiliate URL with tracking parameters
 */
export function buildAffiliateUrl(
  wine: Wine,
  partnerId: string,
  additionalParams?: Record<string, string>
): string {
  const partner = getPartnerById(partnerId);

  if (!partner) {
    console.error(`Unknown affiliate partner: ${partnerId}`);
    return wine.url || '#';
  }

  // If partner not configured, return original URL
  if (!partner.affiliateId || partner.affiliateId === 'PENDING') {
    console.warn(`Affiliate not configured for ${partnerId}, using direct link`);
    return wine.url || partner.baseUrl;
  }

  // Start with original URL or partner base URL
  let baseUrl = wine.url || partner.baseUrl;

  // Create URL object for parameter manipulation
  let url: URL;
  try {
    url = new URL(baseUrl);
  } catch (error) {
    console.error(`Invalid URL: ${baseUrl}`);
    return baseUrl;
  }

  // Add affiliate parameter
  url.searchParams.set(partner.affiliateParam, partner.affiliateId);

  // Add additional tracking parameters
  if (additionalParams) {
    Object.entries(additionalParams).forEach(([key, value]) => {
      url.searchParams.set(key, value);
    });
  }

  // Add source tracking
  url.searchParams.set('utm_source', 'wine-finder');
  url.searchParams.set('utm_medium', 'affiliate');
  url.searchParams.set('utm_campaign', 'wine-search');

  return url.toString();
}

/**
 * Build deeplink URL for specific wine
 */
export function buildDeeplinkUrl(
  wine: Wine,
  partnerId: string
): string {
  const partner = getPartnerById(partnerId);

  if (!partner || !partner.deeplinkPattern) {
    return buildAffiliateUrl(wine, partnerId);
  }

  // Replace pattern variables
  let deeplink = partner.deeplinkPattern;
  deeplink = deeplink.replace('{wineName}', encodeURIComponent(wine.name));
  deeplink = deeplink.replace('{vintage}', wine.vintage || '');
  deeplink = deeplink.replace('{winery}', encodeURIComponent(wine.winery || ''));

  return buildAffiliateUrl({ ...wine, url: deeplink }, partnerId);
}

/**
 * Build search URL for partner
 */
export function buildSearchUrl(
  searchQuery: string,
  partnerId: string
): string {
  const partner = getPartnerById(partnerId);

  if (!partner) {
    return '#';
  }

  const searchUrls: Record<string, string> = {
    'vivino': `${partner.baseUrl}/search/wines?q=${encodeURIComponent(searchQuery)}`,
    'wine-com': `${partner.baseUrl}/list/wine/${encodeURIComponent(searchQuery)}/7155`,
    'total-wine': `${partner.baseUrl}/search/all?text=${encodeURIComponent(searchQuery)}`,
    'kl-wines': `${partner.baseUrl}/Products?searchText=${encodeURIComponent(searchQuery)}`,
    'wine-searcher': `${partner.baseUrl}/find/${encodeURIComponent(searchQuery)}`,
    'wine-access': `${partner.baseUrl}/wine?query=${encodeURIComponent(searchQuery)}`,
  };

  const searchUrl = searchUrls[partnerId] || `${partner.baseUrl}?q=${encodeURIComponent(searchQuery)}`;

  return buildAffiliateUrl({ name: searchQuery, url: searchUrl, source: partner.name, price: '0' }, partnerId);
}

/**
 * Get multiple affiliate options for a wine
 */
export function getAffiliateOptions(wine: Wine): Array<{
  partnerId: string;
  partnerName: string;
  url: string;
  commissionRate?: string;
  type: string;
}> {
  const partners = getPartnerById('vivino') ? [
    'vivino',
    'wine-com',
    'total-wine',
    'kl-wines',
    'wine-access'
  ] : [];

  return partners
    .map(partnerId => {
      const partner = getPartnerById(partnerId);
      if (!partner) return null;

      return {
        partnerId: partner.id,
        partnerName: partner.name,
        url: buildAffiliateUrl(wine, partnerId),
        commissionRate: partner.commissionRate,
        type: partner.type
      };
    })
    .filter(Boolean) as Array<{
      partnerId: string;
      partnerName: string;
      url: string;
      commissionRate?: string;
      type: string;
    }>;
}

/**
 * Extract tracking info from URL
 */
export function extractTrackingInfo(url: string): {
  partnerId?: string;
  affiliateId?: string;
  utmParams?: Record<string, string>;
} {
  try {
    const urlObj = new URL(url);
    const utmParams: Record<string, string> = {};

    urlObj.searchParams.forEach((value, key) => {
      if (key.startsWith('utm_')) {
        utmParams[key] = value;
      }
    });

    return {
      utmParams: Object.keys(utmParams).length > 0 ? utmParams : undefined
    };
  } catch (error) {
    return {};
  }
}