From aa23d4ae2a4ee4ea7ff03a1e8456496197075a73 Mon Sep 17 00:00:00 2001 From: OpenClaw Date: Sun, 15 Feb 2026 09:52:25 +0000 Subject: [PATCH] Add checkout.session.completed webhook handler for Pro key creation - Extract customer email from session.customer_details?.email - Check if Pro key already exists for that email (idempotent) - Create Pro key only if one does not exist - Add comprehensive logging for debugging - Ensures webhook and success page work together without duplicates --- src/routes/billing.ts | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/src/routes/billing.ts b/src/routes/billing.ts index d1dfef1..a6cdae7 100644 --- a/src/routes/billing.ts +++ b/src/routes/billing.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import Stripe from "stripe"; -import { createProKey, revokeByCustomer } from "../services/keys.js"; +import { createProKey, revokeByCustomer, getAllKeys } from "../services/keys.js"; function escapeHtml(s: string): string { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); @@ -105,6 +105,39 @@ router.post("/webhook", async (req: Request, res: Response) => { } switch (event.type) { + case "checkout.session.completed": { + const session = event.data.object as Stripe.Checkout.Session; + const customerId = session.customer as string; + const email = session.customer_details?.email; + + console.log(`[Webhook] checkout.session.completed - sessionId: ${session.id}, customerId: ${customerId}, email: ${email}`); + + if (!email) { + console.error(`[Webhook] No customer email found for session ${session.id}`); + break; + } + + if (!customerId) { + console.error(`[Webhook] No customer ID found for session ${session.id}`); + break; + } + + // Check if a Pro key already exists for this email (idempotent handling) + const existingKeys = getAllKeys(); + const existingProKey = existingKeys.find(k => k.email === email && k.tier === "pro"); + + if (existingProKey) { + console.log(`[Webhook] Pro key already exists for email ${email}, skipping creation`); + } else { + try { + const keyInfo = createProKey(email, customerId); + console.log(`[Webhook] Created Pro key for ${email}: ${keyInfo.key}`); + } catch (err: any) { + console.error(`[Webhook] Failed to create Pro key for ${email}:`, err.message); + } + } + break; + } case "customer.subscription.deleted": { const sub = event.data.object as Stripe.Subscription; const customerId = sub.customer as string; @@ -113,6 +146,7 @@ router.post("/webhook", async (req: Request, res: Response) => { break; } default: + console.log(`[Webhook] Unhandled event type: ${event.type}`); break; } @@ -154,4 +188,4 @@ async function getOrCreateProPrice(): Promise { return cachedPriceId; } -export { router as billingRouter }; +export { router as billingRouter }; \ No newline at end of file