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 <noreply@anthropic.com>
This commit is contained in:
Andymick
2026-07-19 11:24:18 +01:00
co-authored by Claude Haiku 4.5
parent 2f123c6b81
commit 5ec843da5b
+31 -46
View File
@@ -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<ShippingQuote[]> {
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);