full shipping flow
This commit is contained in:
parent
57c5b6f864
commit
d9953baa19
4 changed files with 152 additions and 132 deletions
|
|
@ -475,7 +475,8 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
|
|
||||||
// Get cart items with product weights
|
// Get cart items with product weights
|
||||||
const cartItemsResult = await query(
|
const cartItemsResult = await query(
|
||||||
`SELECT ci.quantity, p.id, p.weight_grams, p.price
|
`SELECT ci.quantity, p.id, p.weight_grams, p.price,
|
||||||
|
p.length_cm, p.width_cm, p.height_cm
|
||||||
FROM cart_items ci
|
FROM cart_items ci
|
||||||
JOIN products p ON ci.product_id = p.id
|
JOIN products p ON ci.product_id = p.id
|
||||||
WHERE ci.cart_id = $1`,
|
WHERE ci.cart_id = $1`,
|
||||||
|
|
@ -497,34 +498,55 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
|
|
||||||
// If no address provided, return only flat rate shipping
|
// If no address provided, return only flat rate shipping
|
||||||
if (!shippingAddress) {
|
if (!shippingAddress) {
|
||||||
console.log("No Address provide flat rate");
|
console.log("No Address provided - using flat rate");
|
||||||
const rates = await shippingService.getFlatRateShipping(subtotal);
|
const rates = shippingService.getFlatRateShipping(subtotal);
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
|
shipment_id: null,
|
||||||
rates
|
rates
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get real shipping rates
|
// Get real shipping rates with a shipment
|
||||||
const parsedAddress = typeof shippingAddress === 'string'
|
const parsedAddress = typeof shippingAddress === 'string'
|
||||||
? shippingService.parseAddressString(shippingAddress)
|
? shippingService.parseAddressString(shippingAddress)
|
||||||
: shippingAddress;
|
: shippingAddress;
|
||||||
console.log("parsedAddress provided ", parsedAddress);
|
|
||||||
|
|
||||||
const rates = await shippingService.getShippingRates(
|
console.log("Fetching rates for parsed address:", parsedAddress);
|
||||||
|
|
||||||
|
const shippingResponse = await shippingService.getShippingRates(
|
||||||
null, // Use default from config
|
null, // Use default from config
|
||||||
parsedAddress,
|
parsedAddress,
|
||||||
{
|
{
|
||||||
weight: totalWeight,
|
weight: totalWeight,
|
||||||
|
length: Math.max(...cartItemsResult.rows.map(item => item.length_cm || 0)),
|
||||||
|
width: Math.max(...cartItemsResult.rows.map(item => item.width_cm || 0)),
|
||||||
|
height: Math.max(...cartItemsResult.rows.map(item => item.height_cm || 0)),
|
||||||
order_total: subtotal
|
order_total: subtotal
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
console.log("rates provided ", JSON.stringify(rates, null ,4));
|
|
||||||
|
console.log("Shipping rates response:", JSON.stringify(shippingResponse, null, 4));
|
||||||
|
|
||||||
|
// Save the shipment ID to the session or temporary storage
|
||||||
|
// This will be used when the user selects a rate
|
||||||
|
if (shippingResponse.shipment_id) {
|
||||||
|
// Store the shipment ID in the user's cart for later use
|
||||||
|
await query(
|
||||||
|
`UPDATE carts SET metadata = jsonb_set(
|
||||||
|
COALESCE(metadata, '{}'::jsonb),
|
||||||
|
'{temp_shipment_id}',
|
||||||
|
$1::jsonb
|
||||||
|
) WHERE id = $2`,
|
||||||
|
[JSON.stringify(shippingResponse.shipment_id), cartId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
rates
|
shipment_id: shippingResponse.shipment_id,
|
||||||
|
rates: shippingResponse.rates
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting shipping rates:', error);
|
console.error('Error getting shipping rates:', error);
|
||||||
|
|
@ -595,49 +617,54 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
if (config.shipping.enabled) {
|
if (config.shipping.enabled) {
|
||||||
// If a specific shipping method was selected
|
// If a specific shipping method was selected
|
||||||
if (shippingMethod && shippingMethod.id) {
|
if (shippingMethod && shippingMethod.id) {
|
||||||
// Parse shipping address to object if it's a string
|
shippingCost = parseFloat(shippingMethod.rate) || 0;
|
||||||
const parsedAddress = typeof shippingAddress === 'string'
|
|
||||||
? shippingService.parseAddressString(shippingAddress)
|
|
||||||
: shippingAddress;
|
|
||||||
|
|
||||||
// Calculate total weight
|
|
||||||
const totalWeight = shippingService.calculateTotalWeight(cartItemsResult.rows);
|
|
||||||
|
|
||||||
// Create an actual shipment on EasyPost if a real rate was selected (not flat/free)
|
// Check if this is a non-flat rate and we need to purchase a real shipment
|
||||||
if (config.shipping.easypostEnabled &&
|
if (config.shipping.easypostEnabled &&
|
||||||
!shippingMethod.id.includes('flat-rate') &&
|
!shippingMethod.id.includes('flat-rate') &&
|
||||||
!shippingMethod.id.includes('free-shipping')) {
|
!shippingMethod.id.includes('free-shipping')) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create parcel details
|
// Parse shipping address to object if it's a string
|
||||||
const parcelDetails = {
|
const parsedAddress = typeof shippingAddress === 'string'
|
||||||
weight: totalWeight,
|
? shippingService.parseAddressString(shippingAddress)
|
||||||
order_total: subtotal
|
: shippingAddress;
|
||||||
};
|
|
||||||
|
// Retrieve temporary shipment ID from cart metadata
|
||||||
// Create shipment with the selected rate
|
const cartMetadataResult = await query(
|
||||||
shipmentData = await shippingService.createShipment(
|
'SELECT metadata FROM carts WHERE id = $1',
|
||||||
null, // Use default origin
|
[cartId]
|
||||||
parsedAddress,
|
|
||||||
parcelDetails,
|
|
||||||
shippingMethod.id
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use the actual rate from the created shipment
|
let shipmentId = null;
|
||||||
shippingCost = shipmentData.selected_rate.rate;
|
if (cartMetadataResult.rows.length > 0 &&
|
||||||
|
cartMetadataResult.rows[0].metadata &&
|
||||||
|
cartMetadataResult.rows[0].metadata.temp_shipment_id) {
|
||||||
|
shipmentId = cartMetadataResult.rows[0].metadata.temp_shipment_id;
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Shipment created successfully:', shipmentData.shipment_id);
|
if (shipmentId) {
|
||||||
|
// Purchase the shipment with the selected rate
|
||||||
|
shipmentData = await shippingService.purchaseShipment(
|
||||||
|
shipmentId,
|
||||||
|
shippingMethod.id
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Shipment purchased successfully:', shipmentData.shipment_id);
|
||||||
|
|
||||||
|
// Use the actual rate from the purchased shipment
|
||||||
|
shippingCost = shipmentData.selected_rate.rate;
|
||||||
|
} else {
|
||||||
|
console.log('No shipment ID found in cart metadata, using standard rate');
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating shipment:', error);
|
console.error('Error purchasing shipment:', error);
|
||||||
// Fallback to the rate provided
|
// Continue with the rate provided
|
||||||
shippingCost = parseFloat(shippingMethod.rate) || 0;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Use the rate provided for flat rate or free shipping
|
|
||||||
shippingCost = parseFloat(shippingMethod.rate) || 0;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Default to flat rate if no method selected
|
// Default to flat rate if no method selected
|
||||||
const shippingRates = await shippingService.getFlatRateShipping(subtotal);
|
const shippingRates = shippingService.getFlatRateShipping(subtotal);
|
||||||
shippingCost = shippingRates[0].rate;
|
shippingCost = shippingRates[0].rate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -666,7 +693,7 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a shipping method was selected, save it with the order
|
// If we have shipping details, save them with the order
|
||||||
if (shippingMethod && shippingMethod.id) {
|
if (shippingMethod && shippingMethod.id) {
|
||||||
const shippingInfo = shipmentData || {
|
const shippingInfo = shipmentData || {
|
||||||
method_id: shippingMethod.id,
|
method_id: shippingMethod.id,
|
||||||
|
|
@ -683,6 +710,12 @@ module.exports = (pool, query, authMiddleware) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the temporary shipment ID from cart metadata
|
||||||
|
await client.query(
|
||||||
|
`UPDATE carts SET metadata = metadata - 'temp_shipment_id' WHERE id = $1`,
|
||||||
|
[cartId]
|
||||||
|
);
|
||||||
|
|
||||||
await client.query('COMMIT');
|
await client.query('COMMIT');
|
||||||
|
|
||||||
// Send back cart items for Stripe checkout
|
// Send back cart items for Stripe checkout
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,20 @@ const config = require('../config');
|
||||||
*/
|
*/
|
||||||
const shippingService = {
|
const shippingService = {
|
||||||
/**
|
/**
|
||||||
* Create a shipment in EasyPost and get available rates
|
* Create a shipment in EasyPost and get available rates with proper IDs
|
||||||
* @param {Object} addressFrom - Shipping origin address
|
* @param {Object} addressFrom - Shipping origin address
|
||||||
* @param {Object} addressTo - Customer shipping address
|
* @param {Object} addressTo - Customer shipping address
|
||||||
* @param {Object} parcelDetails - Package dimensions and weight
|
* @param {Object} parcelDetails - Package dimensions and weight
|
||||||
* @returns {Promise<Array>} Array of available shipping rates
|
* @returns {Promise<Object>} Object containing shipment details and available rates
|
||||||
*/
|
*/
|
||||||
async getShippingRates(addressFrom, addressTo, parcelDetails) {
|
async getShippingRates(addressFrom, addressTo, parcelDetails) {
|
||||||
// If EasyPost is not enabled, return flat rate shipping
|
// If EasyPost is not enabled, return flat rate shipping
|
||||||
if (!config.shipping.easypostEnabled || !config.shipping.easypostApiKey) {
|
if (!config.shipping.easypostEnabled || !config.shipping.easypostApiKey) {
|
||||||
console.log("EASY POST NOT CONFIGURED ", !config.shipping.easypostEnabled, !config.shipping.easypostApiKey)
|
console.log("EASY POST NOT CONFIGURED ", !config.shipping.easypostEnabled, !config.shipping.easypostApiKey)
|
||||||
return this.getFlatRateShipping(parcelDetails.order_total);
|
return {
|
||||||
|
shipment_id: null,
|
||||||
|
rates: this.getFlatRateShipping(parcelDetails.order_total)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -26,16 +29,10 @@ const shippingService = {
|
||||||
|
|
||||||
// Format parcel for EasyPost
|
// Format parcel for EasyPost
|
||||||
const parcel = this.formatParcel(parcelDetails);
|
const parcel = this.formatParcel(parcelDetails);
|
||||||
console.log("EasyPost shipment request", JSON.stringify({
|
|
||||||
shipment: {
|
// Create shipment first to get proper rate IDs (using v2 API)
|
||||||
from_address: fromAddress,
|
|
||||||
to_address: toAddress,
|
|
||||||
parcel: parcel
|
|
||||||
}
|
|
||||||
}, null , 4))
|
|
||||||
// Create shipment via EasyPost API
|
|
||||||
const response = await axios.post(
|
const response = await axios.post(
|
||||||
'https://api.easypost.com/beta/rates',
|
'https://api.easypost.com/v2/shipments',
|
||||||
{
|
{
|
||||||
shipment: {
|
shipment: {
|
||||||
from_address: fromAddress,
|
from_address: fromAddress,
|
||||||
|
|
@ -53,74 +50,48 @@ const shippingService = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// console.log("EasyPost shipment response", response)
|
|
||||||
|
|
||||||
// Process and filter rates
|
const shipment = response.data;
|
||||||
return this.processShippingRates(response.data.rates, parcelDetails.order_total);
|
console.log("Shipment created successfully:", shipment.id);
|
||||||
|
|
||||||
|
// Process and filter rates from the shipment object
|
||||||
|
const formattedRates = this.processShippingRates(shipment.rates, parcelDetails.order_total);
|
||||||
|
|
||||||
|
// Return both the shipment ID and the rates
|
||||||
|
return {
|
||||||
|
shipment_id: shipment.id,
|
||||||
|
rates: formattedRates
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('EasyPost API error:', error.response?.data || error.message);
|
console.error('EasyPost API error:', error.response?.data || error.message);
|
||||||
// Fallback to flat rate if API fails
|
// Fallback to flat rate if API fails
|
||||||
return this.getFlatRateShipping(parcelDetails.order_total);
|
return {
|
||||||
|
shipment_id: null,
|
||||||
|
rates: this.getFlatRateShipping(parcelDetails.order_total)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an actual shipment in EasyPost based on selected rate
|
* Purchase a selected rate for an existing shipment
|
||||||
* @param {Object} addressFrom - Shipping origin address
|
* @param {string} shipmentId - The EasyPost shipment ID
|
||||||
* @param {Object} addressTo - Customer shipping address
|
* @param {string} rateId - The ID of the selected rate
|
||||||
* @param {Object} parcelDetails - Package dimensions and weight
|
* @returns {Promise<Object>} Purchased shipment details
|
||||||
* @param {string} selectedRateId - The ID of the selected rate
|
|
||||||
* @returns {Promise<Object>} Created shipment details
|
|
||||||
*/
|
*/
|
||||||
async createShipment(addressFrom, addressTo, parcelDetails, selectedRateId) {
|
async purchaseShipment(shipmentId, rateId) {
|
||||||
// If EasyPost is not enabled, return a simulated shipment
|
// If EasyPost is not enabled or we don't have a shipment ID, return a simulated shipment
|
||||||
if (!config.shipping.easypostEnabled || !config.shipping.easypostApiKey) {
|
if (!config.shipping.easypostEnabled || !config.shipping.easypostApiKey || !shipmentId) {
|
||||||
console.log("EasyPost not configured for shipment creation");
|
console.log("Cannot purchase shipment - EasyPost not configured or no shipment ID");
|
||||||
return this.createSimulatedShipment(selectedRateId);
|
return this.createSimulatedShipment(rateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Format addresses for EasyPost
|
// Buy the selected rate
|
||||||
const fromAddress = this.formatAddress(addressFrom || config.shipping.originAddress);
|
|
||||||
const toAddress = this.formatAddress(addressTo);
|
|
||||||
|
|
||||||
// Format parcel for EasyPost
|
|
||||||
const parcel = this.formatParcel(parcelDetails);
|
|
||||||
|
|
||||||
// Step 1: Create a shipment
|
|
||||||
const shipmentResponse = await axios.post(
|
|
||||||
'https://api.easypost.com/v2/shipments',
|
|
||||||
{
|
|
||||||
shipment: {
|
|
||||||
from_address: fromAddress,
|
|
||||||
to_address: toAddress,
|
|
||||||
parcel: parcel,
|
|
||||||
options: {
|
|
||||||
label_format: 'PDF',
|
|
||||||
label_size: '4x6'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
auth: {
|
|
||||||
username: config.shipping.easypostApiKey,
|
|
||||||
password: ''
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const shipment = shipmentResponse.data;
|
|
||||||
console.log("Shipment created successfully:", shipment.id);
|
|
||||||
|
|
||||||
// Step 2: Buy the selected rate
|
|
||||||
const buyResponse = await axios.post(
|
const buyResponse = await axios.post(
|
||||||
`https://api.easypost.com/v2/shipments/${shipment.id}/buy`,
|
`https://api.easypost.com/v2/shipments/${shipmentId}/buy`,
|
||||||
{
|
{
|
||||||
rate: {
|
rate: {
|
||||||
id: selectedRateId
|
id: rateId
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -156,19 +127,19 @@ const shippingService = {
|
||||||
status: purchasedShipment.status
|
status: purchasedShipment.status
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('EasyPost API error during shipment creation:', error.response?.data || error.message);
|
console.error('EasyPost API error during purchasing shipment:', error.response?.data || error.message);
|
||||||
|
|
||||||
// Return a fallback simulated shipment
|
// Return a fallback simulated shipment
|
||||||
return this.createSimulatedShipment(selectedRateId);
|
return this.createSimulatedShipment(rateId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a simulated shipment when EasyPost is not available
|
* Create a simulated shipment when EasyPost is not available
|
||||||
* @param {string} selectedRateId - The ID of the selected rate
|
* @param {string} rateId - The ID of the selected rate
|
||||||
* @returns {Object} Simulated shipment details
|
* @returns {Object} Simulated shipment details
|
||||||
*/
|
*/
|
||||||
createSimulatedShipment(selectedRateId) {
|
createSimulatedShipment(rateId) {
|
||||||
// Generate a random tracking number
|
// Generate a random tracking number
|
||||||
const trackingNumber = `SIMSHIP${Math.floor(Math.random() * 1000000000)}`;
|
const trackingNumber = `SIMSHIP${Math.floor(Math.random() * 1000000000)}`;
|
||||||
|
|
||||||
|
|
@ -178,15 +149,15 @@ const shippingService = {
|
||||||
tracking_code: trackingNumber,
|
tracking_code: trackingNumber,
|
||||||
label_url: null,
|
label_url: null,
|
||||||
selected_rate: {
|
selected_rate: {
|
||||||
id: selectedRateId,
|
id: rateId,
|
||||||
carrier: selectedRateId.includes('flat') ? 'Standard' : 'Simulated',
|
carrier: rateId.includes('flat') ? 'Standard' : 'Simulated',
|
||||||
service: selectedRateId.includes('flat') ? 'Flat Rate Shipping' : 'Standard Service',
|
service: rateId.includes('flat') ? 'Flat Rate Shipping' : 'Standard Service',
|
||||||
rate: selectedRateId.includes('free') ? 0 : config.shipping.flatRate,
|
rate: rateId.includes('free') ? 0 : config.shipping.flatRate,
|
||||||
delivery_days: '5-7',
|
delivery_days: '5-7',
|
||||||
delivery_date: null
|
delivery_date: null
|
||||||
},
|
},
|
||||||
carrier: selectedRateId.includes('flat') ? 'Standard' : 'Simulated',
|
carrier: rateId.includes('flat') ? 'Standard' : 'Simulated',
|
||||||
service: selectedRateId.includes('flat') ? 'Flat Rate Shipping' : 'Standard Service',
|
service: rateId.includes('flat') ? 'Flat Rate Shipping' : 'Standard Service',
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
status: 'simulated'
|
status: 'simulated'
|
||||||
};
|
};
|
||||||
|
|
@ -242,7 +213,6 @@ const shippingService = {
|
||||||
* @returns {Array} Processed shipping rates
|
* @returns {Array} Processed shipping rates
|
||||||
*/
|
*/
|
||||||
processShippingRates(rates, orderTotal) {
|
processShippingRates(rates, orderTotal) {
|
||||||
console.log("processShippingRates", rates, orderTotal, config.shipping.carriersAllowed)
|
|
||||||
if (!rates || !Array.isArray(rates)) {
|
if (!rates || !Array.isArray(rates)) {
|
||||||
return this.getFlatRateShipping(orderTotal);
|
return this.getFlatRateShipping(orderTotal);
|
||||||
}
|
}
|
||||||
|
|
@ -258,31 +228,31 @@ const shippingService = {
|
||||||
return this.getFlatRateShipping(orderTotal);
|
return this.getFlatRateShipping(orderTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format rates to standardized format
|
// Format rates to standardized format - now including the rate ID
|
||||||
const formattedRates = filteredRates.map(rate => ({
|
const formattedRates = filteredRates.map(rate => ({
|
||||||
id: rate.id,
|
id: rate.id,
|
||||||
carrier: rate.carrier,
|
carrier: rate.carrier,
|
||||||
service: rate.service,
|
service: rate.service,
|
||||||
rate: parseFloat(rate.rate),
|
rate: parseFloat(rate.rate),
|
||||||
currency: rate.currency,
|
currency: rate.currency,
|
||||||
delivery_days: rate.delivery_days || 'Unknown',
|
delivery_days: rate.est_delivery_days || rate.delivery_days || 'Unknown',
|
||||||
delivery_date: rate.delivery_date || null,
|
delivery_date: rate.delivery_date || null,
|
||||||
delivery_time: rate.est_delivery_time || null
|
delivery_time: rate.est_delivery_time || null
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// // Check if free shipping applies
|
// Check if free shipping applies
|
||||||
// if (orderTotal >= config.shipping.freeThreshold) {
|
if (orderTotal >= config.shipping.freeThreshold) {
|
||||||
// formattedRates.push({
|
formattedRates.push({
|
||||||
// id: 'free-shipping',
|
id: 'free-shipping',
|
||||||
// carrier: 'FREE',
|
carrier: 'FREE',
|
||||||
// service: 'Standard Shipping',
|
service: 'Standard Shipping',
|
||||||
// rate: 0,
|
rate: 0,
|
||||||
// currency: 'USD',
|
currency: 'USD',
|
||||||
// delivery_days: '5-7',
|
delivery_days: '5-7',
|
||||||
// delivery_date: null,
|
delivery_date: null,
|
||||||
// delivery_time: null
|
delivery_time: null
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
|
|
||||||
return formattedRates;
|
return formattedRates;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
1
db/init/13-cart-metadata.sql
Normal file
1
db/init/13-cart-metadata.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE carts ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}'::jsonb;
|
||||||
|
|
@ -26,7 +26,6 @@ import { useNavigate, Link as RouterLink } from 'react-router-dom';
|
||||||
import { useAuth, useCart } from '../hooks/reduxHooks';
|
import { useAuth, useCart } from '../hooks/reduxHooks';
|
||||||
import { useCheckout } from '../hooks/apiHooks';
|
import { useCheckout } from '../hooks/apiHooks';
|
||||||
import { useStripe, StripeElementsProvider } from '../context/StripeContext';
|
import { useStripe, StripeElementsProvider } from '../context/StripeContext';
|
||||||
import StripePaymentForm from '../components/StripePaymentForm';
|
|
||||||
import apiClient from '../services/api';
|
import apiClient from '../services/api';
|
||||||
|
|
||||||
// Checkout steps
|
// Checkout steps
|
||||||
|
|
@ -51,6 +50,7 @@ const CheckoutPage = () => {
|
||||||
const [shippingRates, setShippingRates] = useState([]);
|
const [shippingRates, setShippingRates] = useState([]);
|
||||||
const [selectedShippingMethod, setSelectedShippingMethod] = useState(null);
|
const [selectedShippingMethod, setSelectedShippingMethod] = useState(null);
|
||||||
const [shippingCost, setShippingCost] = useState(0);
|
const [shippingCost, setShippingCost] = useState(0);
|
||||||
|
const [shipmentId, setShipmentId] = useState(null);
|
||||||
|
|
||||||
// State for form data
|
// State for form data
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
|
|
@ -72,6 +72,11 @@ const CheckoutPage = () => {
|
||||||
...formData,
|
...formData,
|
||||||
[name]: name === 'saveAddress' ? checked : value,
|
[name]: name === 'saveAddress' ? checked : value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Clear validation error when field is edited
|
||||||
|
if (error) {
|
||||||
|
setError(null);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle next step
|
// Handle next step
|
||||||
|
|
@ -87,8 +92,8 @@ const CheckoutPage = () => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If valid, fetch shipping rates
|
|
||||||
fetchShippingRates();
|
fetchShippingRates();
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If on shipping method step, validate selection
|
// If on shipping method step, validate selection
|
||||||
|
|
@ -162,6 +167,12 @@ const CheckoutPage = () => {
|
||||||
|
|
||||||
if (response.data.rates && response.data.rates.length > 0) {
|
if (response.data.rates && response.data.rates.length > 0) {
|
||||||
setShippingRates(response.data.rates);
|
setShippingRates(response.data.rates);
|
||||||
|
|
||||||
|
// Store shipment_id if provided (needed for actual shipment creation)
|
||||||
|
if (response.data.shipment_id) {
|
||||||
|
setShipmentId(response.data.shipment_id);
|
||||||
|
}
|
||||||
|
|
||||||
// Default to lowest cost option
|
// Default to lowest cost option
|
||||||
const lowestCostOption = response.data.rates.reduce(
|
const lowestCostOption = response.data.rates.reduce(
|
||||||
(lowest, current) => current.rate < lowest.rate ? current : lowest,
|
(lowest, current) => current.rate < lowest.rate ? current : lowest,
|
||||||
|
|
@ -172,9 +183,13 @@ const CheckoutPage = () => {
|
||||||
} else {
|
} else {
|
||||||
setShippingRates([]);
|
setShippingRates([]);
|
||||||
setSelectedShippingMethod(null);
|
setSelectedShippingMethod(null);
|
||||||
|
setShipmentId(null);
|
||||||
setShippingCost(0);
|
setShippingCost(0);
|
||||||
setError('No shipping options available for this address');
|
setError('No shipping options available for this address');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Move to next step
|
||||||
|
setActiveStep((prevStep) => prevStep + 1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching shipping rates:', error);
|
console.error('Error fetching shipping rates:', error);
|
||||||
setError('Failed to retrieve shipping options. Please try again.');
|
setError('Failed to retrieve shipping options. Please try again.');
|
||||||
|
|
@ -257,6 +272,7 @@ const CheckoutPage = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Redirect to Stripe checkout when the URL is available
|
// Redirect to Stripe checkout when the URL is available
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (checkoutUrl) {
|
if (checkoutUrl) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue