Files
AluxPay/backend/routes/payment.js
matej ee1ce09508 Fixed order total mismatch issue on cancelled page
Fixed token validation issue
Fixed issue displaying shipping method and fee on checkout
Added Light only theme
Fixed positioning issue on mobile view
2025-10-12 00:45:36 +02:00

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;