Add src/app/api/payment/process/route.ts
This commit is contained in:
204
src/app/api/payment/process/route.ts
Normal file
204
src/app/api/payment/process/route.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
interface PaymentRequest {
|
||||
paymentMethod: string;
|
||||
cardName: string;
|
||||
cardNumber: string;
|
||||
expiryDate: string;
|
||||
cvv: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
items: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
price: string;
|
||||
quantity: number;
|
||||
}>;
|
||||
orderDetails: {
|
||||
subtotal: string;
|
||||
tax: string;
|
||||
total: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface PaymentResponse {
|
||||
success: boolean;
|
||||
transactionId?: string;
|
||||
message: string;
|
||||
order?: {
|
||||
id: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
status: string;
|
||||
};
|
||||
}
|
||||
|
||||
function validatePaymentData(data: PaymentRequest): { valid: boolean; error?: string } {
|
||||
if (!data.cardName?.trim()) {
|
||||
return { valid: false, error: 'Cardholder name is required' };
|
||||
}
|
||||
|
||||
const cardNumberDigits = data.cardNumber.replace(/\D/g, '');
|
||||
if (!/^\d{13,19}$/.test(cardNumberDigits)) {
|
||||
return { valid: false, error: 'Invalid card number' };
|
||||
}
|
||||
|
||||
if (!/^\d{2}\/\d{2}$/.test(data.expiryDate)) {
|
||||
return { valid: false, error: 'Invalid expiry date format' };
|
||||
}
|
||||
|
||||
if (!/^\d{3,4}$/.test(data.cvv)) {
|
||||
return { valid: false, error: 'Invalid CVV' };
|
||||
}
|
||||
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
||||
return { valid: false, error: 'Invalid email address' };
|
||||
}
|
||||
|
||||
if (!data.phone?.trim()) {
|
||||
return { valid: false, error: 'Phone number is required' };
|
||||
}
|
||||
|
||||
if (data.amount <= 0) {
|
||||
return { valid: false, error: 'Invalid payment amount' };
|
||||
}
|
||||
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
function luhnCheck(cardNumber: string): boolean {
|
||||
const digits = cardNumber.replace(/\D/g, '');
|
||||
let sum = 0;
|
||||
let isEven = false;
|
||||
|
||||
for (let i = digits.length - 1; i >= 0; i--) {
|
||||
let digit = parseInt(digits[i], 10);
|
||||
|
||||
if (isEven) {
|
||||
digit *= 2;
|
||||
if (digit > 9) {
|
||||
digit -= 9;
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit;
|
||||
isEven = !isEven;
|
||||
}
|
||||
|
||||
return sum % 10 === 0;
|
||||
}
|
||||
|
||||
function generateTransactionId(): string {
|
||||
return `TXN-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest): Promise<NextResponse<PaymentResponse>> {
|
||||
try {
|
||||
const data: PaymentRequest = await request.json();
|
||||
|
||||
// Validate payment data
|
||||
const validation = validatePaymentData(data);
|
||||
if (!validation.valid) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: validation.error || 'Validation failed'
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate card using Luhn algorithm
|
||||
if (!luhnCheck(data.cardNumber)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'Invalid card number'
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate expiry date
|
||||
const [month, year] = data.expiryDate.split('/');
|
||||
const expiryDate = new Date(2000 + parseInt(year), parseInt(month) - 1);
|
||||
if (expiryDate < new Date()) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'Card has expired'
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Validate items and amount
|
||||
if (!data.items || data.items.length === 0) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'No items in order'
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// In production, integrate with actual payment gateway here
|
||||
// This is a mock processing example
|
||||
const transactionId = generateTransactionId();
|
||||
|
||||
// Simulate payment processing delay
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
// Mock fraud check - reject cards ending in 0002
|
||||
const lastFourDigits = data.cardNumber.replace(/\D/g, '').slice(-4);
|
||||
if (lastFourDigits === '0002') {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'Payment declined. Please try another card.'
|
||||
},
|
||||
{ status: 402 }
|
||||
);
|
||||
}
|
||||
|
||||
// Log order for email notification
|
||||
console.log('Payment Processing Log:', {
|
||||
transactionId,
|
||||
amount: data.amount,
|
||||
currency: data.currency,
|
||||
cardLast4: lastFourDigits,
|
||||
email: data.email,
|
||||
phone: data.phone,
|
||||
itemCount: data.items.length,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
transactionId,
|
||||
message: 'Payment processed successfully',
|
||||
order: {
|
||||
id: transactionId,
|
||||
amount: data.amount,
|
||||
currency: data.currency,
|
||||
status: 'completed'
|
||||
}
|
||||
},
|
||||
{ status: 200 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Payment processing error:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: 'Payment processing failed. Please try again.'
|
||||
},
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user