Fixed token validation issue Fixed issue displaying shipping method and fee on checkout Added Light only theme Fixed positioning issue on mobile view
329 lines
12 KiB
JavaScript
329 lines
12 KiB
JavaScript
const express = require('express');
|
|
const { body, validationResult } = require('express-validator');
|
|
const paypalService = require('../services/paypal');
|
|
const woocommerceService = require('../services/woocommerce');
|
|
const { generateToken, verifyToken } = require('../utils/helpers');
|
|
|
|
const router = express.Router();
|
|
|
|
// Validation middleware
|
|
const validateCreateOrder = [
|
|
body('wc_order_id').isNumeric().withMessage('WooCommerce order ID must be numeric'),
|
|
body('total').isDecimal({ decimal_digits: '0,2' }).withMessage('Total must be a valid amount'),
|
|
body('currency').optional().isAlpha().isLength({ min: 3, max: 3 }).withMessage('Currency must be 3 letter code'),
|
|
];
|
|
|
|
const validateCaptureOrder = [
|
|
body('paypal_order_id').notEmpty().withMessage('PayPal order ID is required'),
|
|
body('wc_order_id').isNumeric().withMessage('WooCommerce order ID must be numeric'),
|
|
];
|
|
|
|
// Create PayPal order
|
|
router.post('/create-order', validateCreateOrder, async (req, res) => {
|
|
try {
|
|
// Check validation errors
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Validation failed',
|
|
details: errors.array()
|
|
});
|
|
}
|
|
|
|
const { wc_order_id, total, currency, description, items } = req.body;
|
|
|
|
// In development, allow bypassing WooCommerce validation
|
|
let wcOrder = null;
|
|
const skipWooCommerce = process.env.SKIP_WOOCOMMERCE === 'true';
|
|
|
|
console.log('Skip WooCommerce:', skipWooCommerce, '| Env value:', process.env.SKIP_WOOCOMMERCE);
|
|
|
|
if (!skipWooCommerce) {
|
|
// Get WooCommerce order details for verification
|
|
wcOrder = await woocommerceService.getOrder(wc_order_id);
|
|
if (!wcOrder.success) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid WooCommerce order',
|
|
details: wcOrder.error,
|
|
suggestion: 'For testing without WooCommerce, add SKIP_WOOCOMMERCE=true to your backend/.env file'
|
|
});
|
|
}
|
|
|
|
// Verify order total matches (security check)
|
|
if (parseFloat(wcOrder.order.total) !== parseFloat(total)) {
|
|
console.warn('Order total mismatch:', {
|
|
wc_total: wcOrder.order.total,
|
|
requested_total: total,
|
|
order_id: wc_order_id
|
|
});
|
|
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Order total mismatch'
|
|
});
|
|
}
|
|
} else {
|
|
console.log('Skipping WooCommerce validation - TEST MODE');
|
|
}
|
|
|
|
// Prepare order data for PayPal
|
|
const orderData = {
|
|
wc_order_id,
|
|
reference_id: `WC-${wc_order_id}-${Date.now()}`,
|
|
total: total,
|
|
currency: currency || 'USD',
|
|
description: description || `Order #${wc_order_id} from ${wcOrder?.order?.billing?.first_name || 'Test Customer'}`,
|
|
items: items || [],
|
|
brand_name: 'Fashion Store', // Customize this
|
|
|
|
// ADD CUSTOMER DETAILS FROM WOOCOMMERCE:
|
|
payer: wcOrder ? {
|
|
email: wcOrder.order.billing.email,
|
|
first_name: wcOrder.order.billing.first_name,
|
|
last_name: wcOrder.order.billing.last_name,
|
|
phone: wcOrder.order.billing.phone
|
|
} : null,
|
|
|
|
shipping: wcOrder ? {
|
|
first_name: wcOrder.order.shipping.first_name,
|
|
last_name: wcOrder.order.shipping.last_name,
|
|
address_1: wcOrder.order.shipping.address_1,
|
|
address_2: wcOrder.order.shipping.address_2,
|
|
city: wcOrder.order.shipping.city,
|
|
state: wcOrder.order.shipping.state,
|
|
postcode: wcOrder.order.shipping.postcode,
|
|
country: wcOrder.order.shipping.country
|
|
} : null,
|
|
|
|
// Optional: Add breakdown
|
|
items_total: wcOrder ? wcOrder.order.total : total,
|
|
shipping_total: wcOrder ? wcOrder.order.shipping_total : '0.00',
|
|
tax_total: wcOrder ? wcOrder.order.total_tax : '0.00'
|
|
};
|
|
|
|
// Create PayPal order
|
|
const paypalResult = await paypalService.createOrder(orderData);
|
|
|
|
if (!paypalResult.success) {
|
|
return res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to create PayPal order',
|
|
details: paypalResult.error
|
|
});
|
|
}
|
|
|
|
// Generate secure token for this transaction
|
|
const token = generateToken({
|
|
wc_order_id,
|
|
paypal_order_id: paypalResult.order_id,
|
|
total,
|
|
created_at: Date.now()
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
paypal_order_id: paypalResult.order_id,
|
|
token: token,
|
|
approval_url: paypalResult.links?.find(link => link.rel === 'approve')?.href
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Create Order Error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Capture PayPal payment
|
|
router.post('/capture-order', validateCaptureOrder, async (req, res) => {
|
|
try {
|
|
// Check validation errors
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Validation failed',
|
|
details: errors.array()
|
|
});
|
|
}
|
|
|
|
const { paypal_order_id, wc_order_id, token } = req.body;
|
|
|
|
console.log('Capture request received:', {
|
|
paypal_order_id,
|
|
wc_order_id,
|
|
hasToken: !!token,
|
|
tokenLength: token?.length
|
|
});
|
|
|
|
// Declare tokenData at function scope
|
|
let tokenData = null;
|
|
|
|
// Verify token (optional in development)
|
|
const skipTokenValidation = process.env.NODE_ENV === 'development' && process.env.SKIP_TOKEN_VALIDATION === 'true';
|
|
|
|
if (token && !skipTokenValidation) {
|
|
tokenData = verifyToken(token);
|
|
console.log('Token verification result:', {
|
|
isValid: !!tokenData,
|
|
tokenData: tokenData
|
|
});
|
|
|
|
if (!tokenData || tokenData.wc_order_id !== String(wc_order_id)) {
|
|
console.error('Token validation failed:', {
|
|
tokenData,
|
|
expectedOrderId: wc_order_id,
|
|
tokenOrderId: tokenData?.wc_order_id
|
|
});
|
|
|
|
return res.status(401).json({
|
|
success: false,
|
|
error: 'Invalid or expired token'
|
|
});
|
|
}
|
|
} else if (skipTokenValidation) {
|
|
console.warn('Skipping token validation - DEVELOPMENT MODE');
|
|
} else {
|
|
console.warn('No token provided in capture request');
|
|
}
|
|
|
|
// Capture the PayPal payment
|
|
const captureResult = await paypalService.captureOrder(paypal_order_id);
|
|
|
|
if (!captureResult.success) {
|
|
// Mark WooCommerce order as failed only if not skipping WooCommerce
|
|
if (process.env.SKIP_WOOCOMMERCE !== 'true') {
|
|
await woocommerceService.failPayment(wc_order_id, captureResult.error);
|
|
}
|
|
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Payment capture failed',
|
|
details: captureResult.error
|
|
});
|
|
}
|
|
|
|
// Update WooCommerce order (skip if in test mode)
|
|
let wcResult = { success: true, message: 'WooCommerce update skipped (test mode)' };
|
|
|
|
if (process.env.SKIP_WOOCOMMERCE !== 'true') {
|
|
wcResult = await woocommerceService.completePayment(wc_order_id, captureResult);
|
|
|
|
if (!wcResult.success) {
|
|
console.error('WooCommerce update failed after successful payment:', wcResult.error);
|
|
// Payment succeeded but WC update failed - this needs manual review
|
|
}
|
|
} else {
|
|
console.log('Skipping WooCommerce update - TEST MODE');
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
transaction_id: captureResult.transaction_id,
|
|
status: captureResult.status,
|
|
payer_email: captureResult.payer?.email_address,
|
|
woocommerce_updated: wcResult.success,
|
|
test_mode: process.env.SKIP_WOOCOMMERCE === 'true'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Capture Order Error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
// Get order status
|
|
router.get('/order-status/:wc_order_id', async (req, res) => {
|
|
try {
|
|
const { wc_order_id } = req.params;
|
|
|
|
if (!wc_order_id || isNaN(wc_order_id)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Invalid order ID'
|
|
});
|
|
}
|
|
|
|
// Skip WooCommerce check if in test mode
|
|
if (process.env.SKIP_WOOCOMMERCE === 'true') {
|
|
return res.json({
|
|
success: true,
|
|
order_id: wc_order_id,
|
|
status: 'completed',
|
|
total: '0.00',
|
|
currency: 'USD',
|
|
payment_method: 'paypal',
|
|
payment_method_title: 'PayPal (Test Mode)',
|
|
test_mode: true
|
|
});
|
|
}
|
|
|
|
const wcOrder = await woocommerceService.getOrder(wc_order_id);
|
|
|
|
if (!wcOrder.success) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: 'Order not found'
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
success: true,
|
|
order_id: wcOrder.order.id,
|
|
status: wcOrder.order.status,
|
|
total: wcOrder.order.total,
|
|
currency: wcOrder.order.currency,
|
|
payment_method: wcOrder.order.payment_method,
|
|
payment_method_title: wcOrder.order.payment_method_title,
|
|
line_items: wcOrder.order.line_items.map(item => ({
|
|
name: item.name,
|
|
quantity: item.quantity,
|
|
price: item.price,
|
|
total: item.total,
|
|
image: item.image?.src || null
|
|
})),
|
|
billing: {
|
|
first_name: wcOrder.order.billing.first_name,
|
|
last_name: wcOrder.order.billing.last_name,
|
|
company: wcOrder.order.billing.company,
|
|
address_1: wcOrder.order.billing.address_1,
|
|
address_2: wcOrder.order.billing.address_2,
|
|
city: wcOrder.order.billing.city,
|
|
state: wcOrder.order.billing.state,
|
|
postcode: wcOrder.order.billing.postcode,
|
|
country: wcOrder.order.billing.country,
|
|
email: wcOrder.order.billing.email,
|
|
phone: wcOrder.order.billing.phone
|
|
},
|
|
shipping: {
|
|
total: wcOrder.order.shipping_total,
|
|
method: wcOrder.order.shipping_lines[0]?.method_title,
|
|
first_name: wcOrder.order.shipping.first_name,
|
|
last_name: wcOrder.order.shipping.last_name,
|
|
company: wcOrder.order.shipping.company,
|
|
address_1: wcOrder.order.shipping.address_1,
|
|
address_2: wcOrder.order.shipping.address_2,
|
|
city: wcOrder.order.shipping.city,
|
|
state: wcOrder.order.shipping.state,
|
|
postcode: wcOrder.order.shipping.postcode,
|
|
country: wcOrder.order.shipping.country
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Get Order Status Error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Internal server error'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router; |