Initial commit

This commit is contained in:
2025-09-26 17:34:37 +02:00
commit b96ccfd112
37 changed files with 23138 additions and 0 deletions

1595
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

25
backend/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "payment-backend",
"version": "1.0.0",
"description": "Express backend for payment processing",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"@paypal/checkout-server-sdk": "^1.0.3",
"axios": "^1.6.2",
"cors": "^2.8.5",
"dotenv": "^16.6.1",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"express-validator": "^7.0.1",
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}

269
backend/routes/payment.js Normal file
View File

@@ -0,0 +1,269 @@
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: 'Your Store Name' // Customize this
};
// 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
});
// Verify token (optional in development)
const skipTokenValidation = process.env.NODE_ENV === 'development' && process.env.SKIP_TOKEN_VALIDATION === 'true';
if (token && !skipTokenValidation) {
const tokenData = verifyToken(token);
console.log('Token verification result:', {
isValid: !!tokenData,
tokenData: tokenData
});
if (!tokenData || tokenData.wc_order_id !== parseInt(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
});
} catch (error) {
console.error('Get Order Status Error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
module.exports = router;

173
backend/routes/webhook.js Normal file
View File

@@ -0,0 +1,173 @@
const express = require('express');
const paypalService = require('../services/paypal');
const woocommerceService = require('../services/woocommerce');
const router = express.Router();
// PayPal webhook handler
router.post('/paypal', async (req, res) => {
try {
const webhookBody = req.body;
const headers = req.headers;
console.log('PayPal Webhook Received:', {
event_type: webhookBody.event_type,
resource_id: webhookBody.resource?.id,
timestamp: new Date().toISOString()
});
// Verify webhook signature (simplified for development)
const isValid = paypalService.verifyWebhookSignature(headers, webhookBody, process.env.PAYPAL_WEBHOOK_ID);
if (!isValid) {
console.warn('Invalid PayPal webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// Handle different webhook events
switch (webhookBody.event_type) {
case 'PAYMENT.CAPTURE.COMPLETED':
await handlePaymentCaptured(webhookBody.resource);
break;
case 'PAYMENT.CAPTURE.DENIED':
await handlePaymentDenied(webhookBody.resource);
break;
case 'PAYMENT.CAPTURE.REFUNDED':
await handlePaymentRefunded(webhookBody.resource);
break;
default:
console.log('Unhandled PayPal webhook event:', webhookBody.event_type);
}
res.status(200).json({ status: 'success' });
} catch (error) {
console.error('PayPal Webhook Error:', error);
res.status(500).json({ error: 'Webhook processing failed' });
}
});
// Handle payment captured
async function handlePaymentCaptured(resource) {
try {
console.log('Processing payment capture webhook:', {
capture_id: resource.id,
amount: resource.amount?.value,
custom_id: resource.custom_id
});
const customId = resource.custom_id;
if (!customId) {
console.warn('No custom_id found in payment capture');
return;
}
// Extract WooCommerce order ID from custom_id
const wcOrderId = parseInt(customId);
if (isNaN(wcOrderId)) {
console.warn('Invalid WooCommerce order ID in custom_id:', customId);
return;
}
// Prepare payment data
const paymentData = {
transaction_id: resource.id,
payer: {
email_address: resource.payer?.email_address || 'N/A'
}
};
// Update WooCommerce order
const result = await woocommerceService.completePayment(wcOrderId, paymentData);
if (result.success) {
console.log('Payment capture webhook processed successfully');
} else {
console.error('Failed to update WooCommerce order from webhook:', result.error);
}
} catch (error) {
console.error('Handle Payment Captured Error:', error);
}
}
// Handle payment denied
async function handlePaymentDenied(resource) {
try {
console.log('Processing payment denied webhook:', {
capture_id: resource.id,
custom_id: resource.custom_id
});
const customId = resource.custom_id;
if (!customId) {
console.warn('No custom_id found in payment denial');
return;
}
const wcOrderId = parseInt(customId);
if (isNaN(wcOrderId)) {
console.warn('Invalid WooCommerce order ID in custom_id:', customId);
return;
}
// Mark order as failed
const result = await woocommerceService.failPayment(wcOrderId, 'Payment was denied by PayPal');
if (result.success) {
console.log('Payment denial webhook processed successfully');
} else {
console.error('Failed to update WooCommerce order from denial webhook:', result.error);
}
} catch (error) {
console.error('Handle Payment Denied Error:', error);
}
}
// Handle payment refunded
async function handlePaymentRefunded(resource) {
try {
console.log('Processing payment refund webhook:', {
refund_id: resource.id,
amount: resource.amount?.value,
custom_id: resource.custom_id
});
const customId = resource.custom_id;
if (!customId) {
console.warn('No custom_id found in payment refund');
return;
}
const wcOrderId = parseInt(customId);
if (isNaN(wcOrderId)) {
console.warn('Invalid WooCommerce order ID in custom_id:', customId);
return;
}
// Add refund note to WooCommerce order
const noteText = `Payment refunded via PayPal. Refund ID: ${resource.id}. Amount: ${resource.amount?.value} ${resource.amount?.currency_code}`;
await woocommerceService.addOrderNote(wcOrderId, noteText, true);
console.log('Payment refund webhook processed successfully');
} catch (error) {
console.error('Handle Payment Refunded Error:', error);
}
}
// Health check for webhooks
router.get('/health', (req, res) => {
res.json({
status: 'OK',
service: 'webhooks',
timestamp: new Date().toISOString()
});
});
module.exports = router;

112
backend/server.js Normal file
View File

@@ -0,0 +1,112 @@
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
require('dotenv').config();
const paymentRoutes = require('./routes/payment');
const webhookRoutes = require('./routes/webhook');
const app = express();
const PORT = process.env.PORT || 5000;
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
// Middleware
app.use(helmet());
app.use(limiter);
app.use(morgan('combined'));
// CORS configuration
const corsOptions = {
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
// Body parsing middleware
app.use('/webhook', express.raw({ type: 'application/json' })); // Raw body for webhooks
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/payment', paymentRoutes);
app.use('/webhook', webhookRoutes);
// Health check endpoint
app.get('/api/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV || 'development'
});
});
// Debug endpoint (only in development)
app.get('/api/debug/env', (req, res) => {
if (process.env.NODE_ENV === 'production') {
return res.status(403).json({ error: 'Not available in production' });
}
res.json({
NODE_ENV: process.env.NODE_ENV,
PORT: process.env.PORT,
FRONTEND_URL: process.env.FRONTEND_URL,
SKIP_WOOCOMMERCE: process.env.SKIP_WOOCOMMERCE,
SKIP_TOKEN_VALIDATION: process.env.SKIP_TOKEN_VALIDATION,
PAYPAL_MODE: process.env.PAYPAL_MODE,
HAS_PAYPAL_CLIENT_ID: !!process.env.PAYPAL_CLIENT_ID,
HAS_PAYPAL_CLIENT_SECRET: !!process.env.PAYPAL_CLIENT_SECRET,
HAS_JWT_SECRET: !!process.env.JWT_SECRET,
JWT_SECRET_LENGTH: process.env.JWT_SECRET?.length || 0,
HAS_WEBHOOK_SECRET: !!process.env.WEBHOOK_SECRET,
HAS_WC_URL: !!process.env.WOOCOMMERCE_URL,
HAS_WC_CONSUMER_KEY: !!process.env.WOOCOMMERCE_CONSUMER_KEY,
HAS_WC_CONSUMER_SECRET: !!process.env.WOOCOMMERCE_CONSUMER_SECRET,
ALLOWED_ORIGINS: process.env.ALLOWED_ORIGINS
});
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error('Error:', err);
// Don't expose error details in production
if (process.env.NODE_ENV === 'production') {
res.status(500).json({
error: 'Internal server error',
timestamp: new Date().toISOString()
});
} else {
res.status(500).json({
error: err.message,
stack: err.stack,
timestamp: new Date().toISOString()
});
}
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({
error: 'Route not found',
path: req.originalUrl,
timestamp: new Date().toISOString()
});
});
// Start server
app.listen(PORT, () => {
console.log(`🚀 Server running on port ${PORT}`);
console.log(`📊 Environment: ${process.env.NODE_ENV || 'development'}`);
console.log(`💳 PayPal Mode: ${process.env.PAYPAL_MODE || 'sandbox'}`);
console.log(`🌐 CORS Origins: ${process.env.ALLOWED_ORIGINS || 'http://localhost:3000'}`);
});
module.exports = app;

145
backend/services/paypal.js Normal file
View File

@@ -0,0 +1,145 @@
const paypal = require('@paypal/checkout-server-sdk');
// PayPal environment setup
function environment() {
const clientId = process.env.PAYPAL_CLIENT_ID;
const clientSecret = process.env.PAYPAL_CLIENT_SECRET;
const mode = process.env.PAYPAL_MODE || 'sandbox';
if (!clientId || !clientSecret) {
throw new Error('PayPal credentials not found in environment variables');
}
return mode === 'live'
? new paypal.core.LiveEnvironment(clientId, clientSecret)
: new paypal.core.SandboxEnvironment(clientId, clientSecret);
}
// PayPal client
function client() {
return new paypal.core.PayPalHttpClient(environment());
}
// Create PayPal order
async function createOrder(orderData) {
try {
const request = new paypal.orders.OrdersCreateRequest();
request.prefer("return=representation");
request.requestBody({
intent: 'CAPTURE',
purchase_units: [{
reference_id: orderData.reference_id,
amount: {
currency_code: orderData.currency || 'USD',
value: orderData.total
},
description: orderData.description || 'Payment from WooCommerce',
custom_id: orderData.wc_order_id,
items: orderData.items || []
}],
application_context: {
brand_name: orderData.brand_name || 'Your Store',
landing_page: 'BILLING',
user_action: 'PAY_NOW',
return_url: `${process.env.FRONTEND_URL}/success`,
cancel_url: `${process.env.FRONTEND_URL}/cancel`
}
});
const order = await client().execute(request);
console.log('PayPal Order Created:', {
id: order.result.id,
status: order.result.status,
amount: orderData.total
});
return {
success: true,
order_id: order.result.id,
status: order.result.status,
links: order.result.links
};
} catch (error) {
console.error('PayPal Create Order Error:', error);
return {
success: false,
error: error.message,
details: error.details || []
};
}
}
// Capture PayPal payment
async function captureOrder(orderId) {
try {
const request = new paypal.orders.OrdersCaptureRequest(orderId);
request.requestBody({});
const capture = await client().execute(request);
console.log('PayPal Order Captured:', {
id: capture.result.id,
status: capture.result.status,
payer_email: capture.result.payer?.email_address
});
return {
success: true,
capture_id: capture.result.id,
status: capture.result.status,
payer: capture.result.payer,
purchase_units: capture.result.purchase_units,
transaction_id: capture.result.purchase_units[0]?.payments?.captures[0]?.id
};
} catch (error) {
console.error('PayPal Capture Order Error:', error);
return {
success: false,
error: error.message,
details: error.details || []
};
}
}
// Get order details
async function getOrderDetails(orderId) {
try {
const request = new paypal.orders.OrdersGetRequest(orderId);
const order = await client().execute(request);
return {
success: true,
order: order.result
};
} catch (error) {
console.error('PayPal Get Order Error:', error);
return {
success: false,
error: error.message
};
}
}
// Verify webhook signature
function verifyWebhookSignature(headers, body, webhookId) {
// This is a simplified version - in production, use PayPal's webhook verification
// For now, we'll do basic validation
const webhookSecret = process.env.WEBHOOK_SECRET;
if (!webhookSecret) {
console.warn('WEBHOOK_SECRET not set - skipping signature verification');
return true;
}
// Add proper webhook signature verification here
// For development, we'll return true
return true;
}
module.exports = {
createOrder,
captureOrder,
getOrderDetails,
verifyWebhookSignature
};

View File

@@ -0,0 +1,204 @@
const axios = require('axios');
const https = require('https');
class WooCommerceService {
constructor() {
this.baseURL = process.env.WOOCOMMERCE_URL;
this.consumerKey = process.env.WOOCOMMERCE_CONSUMER_KEY;
this.consumerSecret = process.env.WOOCOMMERCE_CONSUMER_SECRET;
if (!this.baseURL || !this.consumerKey || !this.consumerSecret) {
console.warn('WooCommerce credentials not fully configured');
}
// Create HTTPS agent that ignores SSL errors in development
const httpsAgent = new https.Agent({
rejectUnauthorized: process.env.NODE_ENV === 'production'
});
this.client = axios.create({
baseURL: `${this.baseURL}/wp-json/wc/v3`,
auth: {
username: this.consumerKey,
password: this.consumerSecret
},
timeout: 10000,
httpsAgent: httpsAgent,
// Additional options for SSL issues
headers: {
'User-Agent': 'PaymentWebsite/1.0'
}
});
}
// Get order details from WooCommerce
async getOrder(orderId) {
try {
const response = await this.client.get(`/orders/${orderId}`);
console.log('WooCommerce Order Retrieved:', {
id: response.data.id,
status: response.data.status,
total: response.data.total
});
return {
success: true,
order: response.data
};
} catch (error) {
console.error('WooCommerce Get Order Error:', {
orderId,
error: error.response?.data || error.message
});
return {
success: false,
error: error.response?.data?.message || error.message
};
}
}
// Update order status in WooCommerce
async updateOrderStatus(orderId, status, transactionId = null) {
try {
const updateData = {
status: status
};
// Add transaction ID if payment was successful
if (transactionId) {
updateData.transaction_id = transactionId;
updateData.meta_data = [
{
key: '_paypal_transaction_id',
value: transactionId
}
];
}
const response = await this.client.put(`/orders/${orderId}`, updateData);
console.log('WooCommerce Order Updated:', {
id: response.data.id,
status: response.data.status,
transaction_id: transactionId
});
return {
success: true,
order: response.data
};
} catch (error) {
console.error('WooCommerce Update Order Error:', {
orderId,
status,
error: error.response?.data || error.message
});
return {
success: false,
error: error.response?.data?.message || error.message
};
}
}
// Add order note
async addOrderNote(orderId, note, customerNote = false) {
try {
const response = await this.client.post(`/orders/${orderId}/notes`, {
note: note,
customer_note: customerNote
});
console.log('Order note added:', {
orderId,
noteId: response.data.id
});
return {
success: true,
note: response.data
};
} catch (error) {
console.error('Add Order Note Error:', {
orderId,
error: error.response?.data || error.message
});
return {
success: false,
error: error.response?.data?.message || error.message
};
}
}
// Process payment completion
async completePayment(orderId, paypalData) {
try {
// Update order to processing/completed
const updateResult = await this.updateOrderStatus(
orderId,
'processing', // or 'completed' based on your workflow
paypalData.transaction_id
);
if (!updateResult.success) {
throw new Error(updateResult.error);
}
// Add payment note
const noteText = `Payment completed via PayPal. Transaction ID: ${paypalData.transaction_id}. Payer Email: ${paypalData.payer?.email_address || 'N/A'}`;
await this.addOrderNote(orderId, noteText, false);
return {
success: true,
message: 'Payment completed and order updated'
};
} catch (error) {
console.error('Complete Payment Error:', {
orderId,
error: error.message
});
return {
success: false,
error: error.message
};
}
}
// Handle payment failure
async failPayment(orderId, reason) {
try {
// Update order to failed
const updateResult = await this.updateOrderStatus(orderId, 'failed');
if (!updateResult.success) {
throw new Error(updateResult.error);
}
// Add failure note
const noteText = `Payment failed: ${reason}`;
await this.addOrderNote(orderId, noteText, false);
return {
success: true,
message: 'Order marked as failed'
};
} catch (error) {
console.error('Fail Payment Error:', {
orderId,
error: error.message
});
return {
success: false,
error: error.message
};
}
}
}
module.exports = new WooCommerceService();

197
backend/utils/helpers.js Normal file
View File

@@ -0,0 +1,197 @@
const jwt = require('jsonwebtoken');
// Generate secure token for transactions
function generateToken(data, expiresIn = '1h') {
try {
const secret = process.env.JWT_SECRET || 'your-fallback-secret-key';
return jwt.sign(data, secret, { expiresIn });
} catch (error) {
console.error('Token generation error:', error);
return null;
}
}
// Verify transaction token
function verifyToken(token) {
try {
const secret = process.env.JWT_SECRET || 'your-fallback-secret-key';
return jwt.verify(token, secret);
} catch (error) {
console.error('Token verification error:', error);
return null;
}
}
// Format currency amount
function formatCurrency(amount, currency = 'USD') {
try {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(parseFloat(amount));
} catch (error) {
return `${currency} ${parseFloat(amount).toFixed(2)}`;
}
}
// Validate email format
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
// Sanitize order data for logging
function sanitizeOrderData(orderData) {
const sanitized = { ...orderData };
// Remove sensitive data
delete sanitized.billing?.email;
delete sanitized.billing?.phone;
delete sanitized.customer_id;
return {
id: sanitized.id,
status: sanitized.status,
total: sanitized.total,
currency: sanitized.currency,
payment_method: sanitized.payment_method,
date_created: sanitized.date_created
};
}
// Generate reference ID
function generateReferenceId(prefix = 'REF') {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 8).toUpperCase();
return `${prefix}-${timestamp}-${random}`;
}
// Validate PayPal amount format
function validateAmount(amount) {
if (!amount) return false;
const numAmount = parseFloat(amount);
if (isNaN(numAmount)) return false;
if (numAmount <= 0) return false;
if (numAmount > 10000) return false; // Max amount check
// Check decimal places (max 2)
const decimalPlaces = (amount.toString().split('.')[1] || '').length;
if (decimalPlaces > 2) return false;
return true;
}
// Parse query parameters safely
function parseQueryParams(req) {
const {
wc_order_id,
total,
currency,
customer_email,
return_url,
cancel_url
} = req.query;
return {
wc_order_id: wc_order_id ? parseInt(wc_order_id) : null,
total: total ? parseFloat(total) : null,
currency: currency || 'USD',
customer_email: customer_email || null,
return_url: return_url || `${process.env.FRONTEND_URL}/success`,
cancel_url: cancel_url || `${process.env.FRONTEND_URL}/cancel`
};
}
// Log API request/response
function logApiCall(type, endpoint, data, response) {
console.log(`${type.toUpperCase()} API Call:`, {
endpoint,
timestamp: new Date().toISOString(),
data: sanitizeLogData(data),
response: sanitizeLogData(response),
success: response?.success || false
});
}
// Sanitize data for logging (remove sensitive info)
function sanitizeLogData(data) {
if (!data || typeof data !== 'object') return data;
const sanitized = { ...data };
const sensitiveFields = [
'password', 'secret', 'token', 'key', 'auth',
'email', 'phone', 'address', 'credit_card'
];
sensitiveFields.forEach(field => {
if (sanitized[field]) {
sanitized[field] = '[REDACTED]';
}
});
return sanitized;
}
// Error response helper
function createErrorResponse(message, details = null, statusCode = 500) {
const response = {
success: false,
error: message,
timestamp: new Date().toISOString()
};
if (details && process.env.NODE_ENV !== 'production') {
response.details = details;
}
return { response, statusCode };
}
// Success response helper
function createSuccessResponse(data, message = null) {
return {
success: true,
data: data,
message: message,
timestamp: new Date().toISOString()
};
}
// Retry mechanism for API calls
async function retryOperation(operation, maxRetries = 3, delayMs = 1000) {
let lastError;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
console.warn(`Operation failed (attempt ${attempt}/${maxRetries}):`, error.message);
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, delayMs * attempt));
}
}
}
throw lastError;
}
module.exports = {
generateToken,
verifyToken,
formatCurrency,
isValidEmail,
sanitizeOrderData,
generateReferenceId,
validateAmount,
parseQueryParams,
logApiCall,
sanitizeLogData,
createErrorResponse,
createSuccessResponse,
retryOperation
};