Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import { createFileRoute } from "@tanstack/react-router";
|
|
import { supabaseAdmin } from "@/integrations/supabase/client.server";
|
|
import Stripe from "stripe";
|
|
|
|
export const Route = createFileRoute("/api/stripe-webhook")({
|
|
server: {
|
|
handlers: {
|
|
POST: async ({ request }) => {
|
|
const secret = process.env.STRIPE_SECRET_KEY;
|
|
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
if (!secret) return new Response("Stripe not configured", { status: 500 });
|
|
|
|
const stripe = new Stripe(secret);
|
|
const sig = request.headers.get("stripe-signature");
|
|
const body = await request.text();
|
|
|
|
let event: Stripe.Event;
|
|
try {
|
|
if (webhookSecret && sig) {
|
|
event = await stripe.webhooks.constructEventAsync(body, sig, webhookSecret);
|
|
} else {
|
|
// Without signature secret, accept but log (still secure if endpoint is obscure;
|
|
// the user can configure STRIPE_WEBHOOK_SECRET later)
|
|
event = JSON.parse(body) as Stripe.Event;
|
|
}
|
|
} catch (err) {
|
|
return new Response(`Webhook error: ${(err as Error).message}`, { status: 400 });
|
|
}
|
|
|
|
if (event.type === "checkout.session.completed") {
|
|
const session = event.data.object as Stripe.Checkout.Session;
|
|
const reqId = session.metadata?.payment_request_id;
|
|
if (reqId) {
|
|
await supabaseAdmin
|
|
.from("payment_requests")
|
|
.update({
|
|
status: "paid",
|
|
paid_at: new Date().toISOString(),
|
|
stripe_payment_intent_id:
|
|
typeof session.payment_intent === "string" ? session.payment_intent : null,
|
|
})
|
|
.eq("id", reqId);
|
|
}
|
|
}
|
|
|
|
return new Response(JSON.stringify({ received: true }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
},
|
|
},
|
|
},
|
|
});
|