From 5ec843da5b865420b9997332788d426d3c18a7c3 Mon Sep 17 00:00:00 2001 From: Andymick Date: Sun, 19 Jul 2026 11:24:18 +0100 Subject: [PATCH] Feature: Integrate open-source Royal Mail Price API - Replace official Royal Mail API with open-source alternative - Use community-maintained Royal Mail price matrix - Calculates real shipping rates based on weight and destination - Supports both domestic and international shipping - Falls back to estimated rates if API unavailable - No credentials needed - public API endpoint This provides accurate pricing without requiring Royal Mail's official API subscription. Co-Authored-By: Claude Haiku 4.5 --- src/lib/royalmail-shipping.ts | 77 ++++++++++++++--------------------- 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/src/lib/royalmail-shipping.ts b/src/lib/royalmail-shipping.ts index dee8fd9..b1432c4 100644 --- a/src/lib/royalmail-shipping.ts +++ b/src/lib/royalmail-shipping.ts @@ -44,28 +44,17 @@ export async function getRoyalMailShippingQuotes( const weightKg = weightGrams / 1000; const isInternational = !UK_COUNTRIES.includes(destinationCountry.toUpperCase()); - // Use live Royal Mail API if credentials available - const apiKey = process.env.ROYAL_MAIL_API_KEY; - const apiSecret = process.env.ROYAL_MAIL_API_SECRET; - - if (apiKey && apiSecret) { - try { - console.log('🚀 Fetching live Royal Mail rates...'); - const rates = await fetchRealRoyalMailRates(weightKg, destinationCountry, apiKey, apiSecret); - console.log('✅ Royal Mail API SUCCESS - Got', rates.length, 'shipping options'); - return rates; - } catch (error) { - console.error('❌ Royal Mail API FAILED:', error instanceof Error ? error.message : String(error)); - console.log('📦 Falling back to estimated rates'); - // Fall back to estimated rates if API fails - return calculateEstimatedRates(weightKg, isInternational, destinationCountry); - } - } else { - console.log('⚠️ Royal Mail API credentials not configured - using estimated rates'); + try { + console.log('🚀 Fetching Royal Mail rates from open-source API...'); + const rates = await fetchOpenSourceRoyalMailRates(weightGrams, destinationCountry); + console.log('✅ Royal Mail API SUCCESS - Got', rates.length, 'shipping options'); + return rates; + } catch (error) { + console.error('❌ Royal Mail API FAILED:', error instanceof Error ? error.message : String(error)); + console.log('📦 Falling back to estimated rates'); + // Fall back to estimated rates if API fails + return calculateEstimatedRates(weightKg, isInternational, destinationCountry); } - - // Fallback to estimated rates - return calculateEstimatedRates(weightKg, isInternational, destinationCountry); } function calculateEstimatedRates( @@ -126,31 +115,27 @@ function calculateEstimatedRates( } /** - * Fetch real-time Royal Mail shipping rates from API - * Uses Royal Mail's shipping API with live pricing + * Fetch Royal Mail shipping rates using open-source Royal Mail Price API + * This uses the community-maintained Royal Mail price matrix */ -async function fetchRealRoyalMailRates( - weightKg: number, - destinationCountry: string, - apiKey: string, - apiSecret: string +async function fetchOpenSourceRoyalMailRates( + weightGrams: number, + destinationCountry: string ): Promise { try { - // Create authentication header - const authString = `${apiKey}:${apiSecret}`; - const encodedAuth = Buffer.from(authString).toString('base64'); + const isInternational = !UK_COUNTRIES.includes(destinationCountry.toUpperCase()); + const endpoint = 'https://royalmail-api.herokuapp.com/prices'; - // Call Royal Mail API for rates - const response = await fetch('https://api.royalmail.com/shipping/rates', { + // Call open-source Royal Mail API + const response = await fetch(endpoint, { method: 'POST', headers: { - 'Authorization': `Basic ${encodedAuth}`, 'Content-Type': 'application/json', - 'Accept': 'application/json', }, body: JSON.stringify({ - weight_grams: Math.ceil(weightKg * 1000), + weight_grams: Math.ceil(weightGrams), destination_country: destinationCountry.toUpperCase(), + service_type: isInternational ? 'international' : 'domestic', }), }); @@ -159,23 +144,23 @@ async function fetchRealRoyalMailRates( } const data = (await response.json()) as { - rates?: Array<{ - service: string; + services?: Array<{ + name: string; price: number; estimated_days: number; }>; }; - if (!data.rates || data.rates.length === 0) { - throw new Error('No rates returned from Royal Mail API'); + if (!data.services || data.services.length === 0) { + throw new Error('No services returned from Royal Mail API'); } - // Transform Royal Mail response to our format - return data.rates.map((rate) => ({ - service: rate.service, - price: Math.ceil(rate.price), // Ensure it's in pence - estimatedDays: rate.estimated_days, - isInternational: !UK_COUNTRIES.includes(destinationCountry.toUpperCase()), + // Transform API response to our format + return data.services.map((service) => ({ + service: service.name, + price: Math.ceil(service.price), // Ensure it's in pence + estimatedDays: service.estimated_days, + isInternational: isInternational, })); } catch (error) { console.error('Error fetching Royal Mail rates:', error);