250 lines
No EOL
7.7 KiB
JavaScript
250 lines
No EOL
7.7 KiB
JavaScript
const axios = require('axios');
|
|
const config = require('../config');
|
|
|
|
/**
|
|
* Service for handling shipping operations with EasyPost API
|
|
*/
|
|
const shippingService = {
|
|
/**
|
|
* Create a shipment in EasyPost and get available rates
|
|
* @param {Object} addressFrom - Shipping origin address
|
|
* @param {Object} addressTo - Customer shipping address
|
|
* @param {Object} parcelDetails - Package dimensions and weight
|
|
* @returns {Promise<Array>} Array of available shipping rates
|
|
*/
|
|
async getShippingRates(addressFrom, addressTo, parcelDetails) {
|
|
// If EasyPost is not enabled, return flat rate shipping
|
|
if (!config.shipping.easypostEnabled || !config.shipping.easypostApiKey) {
|
|
console.log("EASY POST NOT CONFIGURED ", !config.shipping.easypostEnabled, !config.shipping.easypostApiKey)
|
|
return this.getFlatRateShipping(parcelDetails.order_total);
|
|
}
|
|
|
|
try {
|
|
// Format addresses for EasyPost
|
|
const fromAddress = this.formatAddress(addressFrom || config.shipping.originAddress);
|
|
const toAddress = this.formatAddress(addressTo);
|
|
|
|
// Format parcel for EasyPost
|
|
const parcel = this.formatParcel(parcelDetails);
|
|
console.log("EasyPost shipment request", JSON.stringify({
|
|
shipment: {
|
|
from_address: fromAddress,
|
|
to_address: toAddress,
|
|
parcel: parcel
|
|
}
|
|
}, null , 4))
|
|
// Create shipment via EasyPost API
|
|
const response = await axios.post(
|
|
'https://api.easypost.com/beta/rates',
|
|
{
|
|
shipment: {
|
|
from_address: fromAddress,
|
|
to_address: toAddress,
|
|
parcel: parcel
|
|
}
|
|
},
|
|
{
|
|
auth: {
|
|
username: config.shipping.easypostApiKey,
|
|
password: ''
|
|
},
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
}
|
|
);
|
|
// console.log("EasyPost shipment response", response)
|
|
|
|
// Process and filter rates
|
|
return this.processShippingRates(response.data.rates, parcelDetails.order_total);
|
|
} catch (error) {
|
|
console.error('EasyPost API error:', error.response?.data || error.message);
|
|
// Fallback to flat rate if API fails
|
|
return this.getFlatRateShipping(parcelDetails.order_total);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Format address for EasyPost API
|
|
* @param {Object} address - Address details
|
|
* @returns {Object} Formatted address object
|
|
*/
|
|
formatAddress(address) {
|
|
return {
|
|
street1: address.street || address.street1,
|
|
city: address.city,
|
|
state: address.state || address.province,
|
|
zip: address.zip || address.postalCode,
|
|
country: address.country,
|
|
name: address.name || undefined,
|
|
company: address.company || undefined,
|
|
phone: address.phone || undefined,
|
|
email: address.email || undefined
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Format parcel for EasyPost API
|
|
* @param {Object} parcelDetails - Package dimensions and weight
|
|
* @returns {Object} Formatted parcel object
|
|
*/
|
|
formatParcel(parcelDetails) {
|
|
const pkg = config.shipping.defaultPackage;
|
|
|
|
// Convert weight to ounces if coming from grams
|
|
const weight = parcelDetails.weight || 500; // Default to 500g if not provided
|
|
const weightOz = pkg.weightUnit === 'g' ? weight * 0.035274 : weight;
|
|
|
|
// Convert dimensions to inches if coming from cm
|
|
const lengthConversionFactor = pkg.unit === 'cm' ? 0.393701 : 1;
|
|
|
|
return {
|
|
length: (parcelDetails.length || pkg.length) * lengthConversionFactor,
|
|
width: (parcelDetails.width || pkg.width) * lengthConversionFactor,
|
|
height: (parcelDetails.height || pkg.height) * lengthConversionFactor,
|
|
weight: weightOz,
|
|
predefined_package: parcelDetails.predefined_package || null
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Process and filter shipping rates from EasyPost
|
|
* @param {Array} rates - EasyPost shipping rates
|
|
* @param {number} orderTotal - Order total amount
|
|
* @returns {Array} Processed shipping rates
|
|
*/
|
|
processShippingRates(rates, orderTotal) {
|
|
console.log("processShippingRates", rates, orderTotal)
|
|
if (!rates || !Array.isArray(rates)) {
|
|
return this.getFlatRateShipping(orderTotal);
|
|
}
|
|
|
|
// Filter by allowed carriers
|
|
let filteredRates = rates.filter(rate =>
|
|
config.shipping.carriersAllowed.some(carrier =>
|
|
rate.carrier.toUpperCase().includes(carrier.toUpperCase())
|
|
)
|
|
);
|
|
|
|
if (filteredRates.length === 0) {
|
|
return this.getFlatRateShipping(orderTotal);
|
|
}
|
|
|
|
// Format rates to standardized format
|
|
const formattedRates = filteredRates.map(rate => ({
|
|
id: rate.id,
|
|
carrier: rate.carrier,
|
|
service: rate.service,
|
|
rate: parseFloat(rate.rate),
|
|
currency: rate.currency,
|
|
delivery_days: rate.delivery_days || 'Unknown',
|
|
delivery_date: rate.delivery_date || null,
|
|
delivery_time: rate.est_delivery_time || null
|
|
}));
|
|
|
|
// Check if free shipping applies
|
|
if (orderTotal >= config.shipping.freeThreshold) {
|
|
formattedRates.push({
|
|
id: 'free-shipping',
|
|
carrier: 'FREE',
|
|
service: 'Standard Shipping',
|
|
rate: 0,
|
|
currency: 'USD',
|
|
delivery_days: '5-7',
|
|
delivery_date: null,
|
|
delivery_time: null
|
|
});
|
|
}
|
|
|
|
return formattedRates;
|
|
},
|
|
|
|
/**
|
|
* Get flat rate shipping as fallback
|
|
* @param {number} orderTotal - Order total amount
|
|
* @returns {Array} Flat rate shipping options
|
|
*/
|
|
getFlatRateShipping(orderTotal) {
|
|
const shippingOptions = [{
|
|
id: 'flat-rate',
|
|
carrier: 'Standard',
|
|
service: 'Flat Rate Shipping',
|
|
rate: config.shipping.flatRate,
|
|
currency: 'USD',
|
|
delivery_days: '5-7',
|
|
delivery_date: null,
|
|
delivery_time: null
|
|
}];
|
|
|
|
// Add free shipping if order qualifies
|
|
if (orderTotal >= config.shipping.freeThreshold) {
|
|
shippingOptions.push({
|
|
id: 'free-shipping',
|
|
carrier: 'FREE',
|
|
service: 'Standard Shipping',
|
|
rate: 0,
|
|
currency: 'USD',
|
|
delivery_days: '5-7',
|
|
delivery_date: null,
|
|
delivery_time: null
|
|
});
|
|
}
|
|
|
|
return shippingOptions;
|
|
},
|
|
|
|
/**
|
|
* Parse shipping address from string format
|
|
* @param {string} addressString - Shipping address as string
|
|
* @returns {Object} Parsed address object
|
|
*/
|
|
parseAddressString(addressString) {
|
|
const lines = addressString.trim().split('\n').map(line => line.trim());
|
|
|
|
// Try to intelligently parse the address components
|
|
// This is a simplified version - might need enhancement for edge cases
|
|
const parsedAddress = {
|
|
name: lines[0] || '',
|
|
street: lines[1] || '',
|
|
city: '',
|
|
state: '',
|
|
zip: '',
|
|
country: lines[lines.length - 1] || ''
|
|
};
|
|
|
|
// Try to parse city, state, zip from line 2
|
|
if (lines[2]) {
|
|
const cityStateZip = lines[2].split(',');
|
|
if (cityStateZip.length >= 2) {
|
|
parsedAddress.city = cityStateZip[0].trim();
|
|
|
|
// Split state and zip
|
|
const stateZip = cityStateZip[1].trim().split(' ');
|
|
if (stateZip.length >= 2) {
|
|
parsedAddress.state = stateZip[0].trim();
|
|
parsedAddress.zip = stateZip.slice(1).join(' ').trim();
|
|
} else {
|
|
parsedAddress.state = stateZip[0].trim();
|
|
}
|
|
} else {
|
|
parsedAddress.city = lines[2];
|
|
}
|
|
}
|
|
|
|
return parsedAddress;
|
|
},
|
|
|
|
/**
|
|
* Calculate total shipping weight from cart items
|
|
* @param {Array} items - Cart items
|
|
* @returns {number} Total weight in grams
|
|
*/
|
|
calculateTotalWeight(items) {
|
|
return items.reduce((total, item) => {
|
|
const itemWeight = item.weight_grams || 100;
|
|
return total + (itemWeight * item.quantity);
|
|
}, 0);
|
|
}
|
|
};
|
|
|
|
module.exports = shippingService; |