// api/paypal.js — Secured with rate limiting + webhook signature verification const { rateLimit, detectThreats, logThreat } = (() => { const rateLimitMap = new Map(); function rateLimit(ip, max=10, windowMs=60000) { const now=Date.now(), e=rateLimitMap.get(ip)||{count:0,reset:now+windowMs}; if(now>e.reset){e.count=0;e.reset=now+windowMs;} e.count++; rateLimitMap.set(ip,e); if(rateLimitMap.size>500) for(const[k,v]of rateLimitMap)if(Date.now()>v.reset)rateLimitMap.delete(k); return e.count>max; } function detectThreats(req) { const body=JSON.stringify(req.body||'').toLowerCase(), url=req.url||''; if(/(\bselect\b|\binsert\b|\bdrop\b|\bunion\b|--|;--)/.test(body)) return 'SQL_INJECTION'; if(/(console.warn('Supabase update failed:',e.message)); } module.exports = async (req, res) => { res.setHeader('Access-Control-Allow-Origin','https://thekoshermoor.com'); res.setHeader('Access-Control-Allow-Methods','POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers','Content-Type'); if(req.method==='OPTIONS') return res.status(200).end(); if(req.method!=='POST') return res.status(405).json({error:'Method not allowed'}); const action=req.query.action; const ip=req.headers['x-forwarded-for']?.split(',')[0]||'unknown'; // Threat detection const threat=detectThreats(req); if(threat){ await logThreat(ip,threat,req); return res.status(400).json({error:'Request blocked'}); } // Rate limiting (skip webhooks) if(action!=='webhook' && rateLimit(ip,10,60000)) return res.status(429).json({error:'Too many requests. Please wait.'}); try { if(action==='create-order') { const {items,total,orderId,customerEmail}=req.body||{}; if(!items||!Array.isArray(items)||!total||!orderId) return res.status(400).json({error:'Missing fields'}); if(isNaN(parseFloat(total))||parseFloat(total)<=0) return res.status(400).json({error:'Invalid total'}); if(typeof orderId!=='string'||orderId.length>50) return res.status(400).json({error:'Invalid order ID'}); if(process.env.SUPABASE_URL&&process.env.SUPABASE_SERVICE_KEY) { await fetch(process.env.SUPABASE_URL+'/rest/v1/orders',{method:'POST',headers:{'apikey':process.env.SUPABASE_SERVICE_KEY,'Authorization':'Bearer '+process.env.SUPABASE_SERVICE_KEY,'Content-Type':'application/json','Prefer':'return=minimal'},body:JSON.stringify({order_number:orderId,customer_email:customerEmail||'paypal-guest',total:parseFloat(total).toFixed(2),status:'pending',payment_method:'paypal',items})}).catch(e=>console.warn('Supabase save failed:',e.message)); } const token=await getToken(); const orderRes=await fetch(PAYPAL_BASE+'/v2/checkout/orders',{method:'POST',headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json','Prefer':'return=representation'},body:JSON.stringify({intent:'CAPTURE',purchase_units:[{reference_id:orderId,description:'The Kosher Moor — '+orderId,amount:{currency_code:'USD',value:parseFloat(total).toFixed(2),breakdown:{item_total:{currency_code:'USD',value:parseFloat(total).toFixed(2)}}},items:items.slice(0,20).map(i=>({name:String(i.title||'Product').substring(0,127),quantity:String(Math.max(1,parseInt(i.qty)||1)),unit_amount:{currency_code:'USD',value:parseFloat(i.price).toFixed(2)},category:i.type==='digital'?'DIGITAL_GOODS':'PHYSICAL_GOODS'}))}],application_context:{brand_name:'The Kosher Moor',landing_page:'BILLING',user_action:'PAY_NOW',return_url:'https://thekoshermoor.com?checkout=success&method=paypal',cancel_url:'https://thekoshermoor.com?checkout=cancel'}})}); const order=await orderRes.json(); if(!order.links){console.error('PayPal order error:',order);return res.status(400).json({error:order.message||'Order creation failed'});} return res.status(200).json({ppOrderId:order.id,approvalUrl:order.links.find(l=>l.rel==='approve')?.href}); } if(action==='capture-order') { const {ppOrderId,orderRef}=req.body||{}; if(!ppOrderId||typeof ppOrderId!=='string') return res.status(400).json({error:'Invalid PayPal order ID'}); const token=await getToken(); const r=await fetch(PAYPAL_BASE+'/v2/checkout/orders/'+ppOrderId+'/capture',{method:'POST',headers:{'Authorization':'Bearer '+token,'Content-Type':'application/json'},body:'{}'}); const capture=await r.json(); if(capture.status==='COMPLETED'&&orderRef) await supabaseUpdate('orders','order_number=eq.'+orderRef,{status:'paid',payment_method:'paypal'}); return res.status(200).json({success:capture.status==='COMPLETED',status:capture.status}); } if(action==='webhook') { const isValid=await verifyWebhookSignature(req.body,req.headers); if(!isValid&&process.env.PAYPAL_WEBHOOK_ID){ await logThreat(ip,'INVALID_WEBHOOK_SIGNATURE',req); return res.status(401).json({error:'Invalid webhook signature'}); } if(req.body?.event_type==='PAYMENT.CAPTURE.COMPLETED') { const orderRef=req.body?.resource?.purchase_units?.[0]?.reference_id; if(orderRef) await supabaseUpdate('orders','order_number=eq.'+orderRef,{status:'paid',payment_method:'paypal'}); } return res.status(200).json({received:true}); } return res.status(400).json({error:'Unknown action'}); } catch(err) { console.error('PayPal error:',err.message); return res.status(500).json({error:'Payment processing error. Please try again.'}); } };