import { db } from "@/lib/server";import { env } from "cloudflare:workers";
const enc=new TextEncoder();function hex(buf:ArrayBuffer){return[...new Uint8Array(buf)].map(x=>x.toString(16).padStart(2,"0")).join("")}
async function valid(payload:string,header:string,secret:string){const parts=Object.fromEntries(header.split(",").map(x=>x.split("=") as [string,string]));if(!parts.t||!parts.v1)return false;if(Math.abs(Date.now()/1000-Number(parts.t))>300)return false;const key=await crypto.subtle.importKey("raw",enc.encode(secret),{name:"HMAC",hash:"SHA-256"},false,["sign"]);const sig=hex(await crypto.subtle.sign("HMAC",key,enc.encode(`${parts.t}.${payload}`)));if(sig.length!==parts.v1.length)return false;let n=0;for(let i=0;i<sig.length;i++)n|=sig.charCodeAt(i)^parts.v1.charCodeAt(i);return n===0}
export async function POST(request:Request){const secret=(env as unknown as Record<string,string>).STRIPE_WEBHOOK_SECRET;if(!secret)return new Response("Webhook not configured",{status:503});const raw=await request.text();if(!await valid(raw,request.headers.get("stripe-signature")||"",secret))return new Response("Invalid signature",{status:400});const event=JSON.parse(raw) as {id:string,type:string,data:{object:Record<string,unknown>}};try{await db().prepare("INSERT INTO webhook_events(id,provider,event_type) VALUES(?,?,?)").bind(event.id,"stripe",event.type).run()}catch{return Response.json({received:true,duplicate:true})}const obj=event.data.object;const metadata=obj.metadata as Record<string,string>|undefined;const paymentId=metadata?.payment_id;if(paymentId){let status:string|undefined;if(event.type==="checkout.session.completed"||event.type==="payment_intent.succeeded")status="successful";if(event.type==="checkout.session.expired"||event.type==="payment_intent.canceled")status="cancelled";if(event.type==="payment_intent.payment_failed")status="failed";if(event.type==="charge.refunded")status="refunded";if(status){await db().prepare("UPDATE payments SET status=?,provider_reference=COALESCE(?,provider_reference),failure_reason=?,refunded_at=CASE WHEN ?='refunded' THEN CURRENT_TIMESTAMP ELSE refunded_at END,updated_at=CURRENT_TIMESTAMP WHERE id=?").bind(status,String(obj.payment_intent||obj.id||""),String((obj.last_payment_error as {message?:string}|undefined)?.message||""),status,paymentId).run();const p=await db().prepare("SELECT registration_id,purpose FROM payments WHERE id=?").bind(paymentId).first<{registration_id:string|null,purpose:string}>();if(p?.registration_id&&status==="successful"){await db().prepare("UPDATE registrations SET payment_status='successful',status='confirmed',updated_at=CURRENT_TIMESTAMP WHERE id=?").bind(p.registration_id).run();await db().prepare("INSERT INTO activity_logs(id,registration_id,action,detail) VALUES(?,?,?,?)").bind(crypto.randomUUID(),p.registration_id,"payment_successful","Stripe verified payment").run()}if(p?.registration_id&&["failed","cancelled","refunded"].includes(status))await db().prepare("UPDATE registrations SET payment_status=?,updated_at=CURRENT_TIMESTAMP WHERE id=?").bind(status,p.registration_id).run()}}return Response.json({received:true})}
