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
This commit is contained in:
OpenClaw 2026-02-15 09:52:25 +00:00
parent f5a85c6fc3
commit aa23d4ae2a

View file

@ -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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@ -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<string> {
return cachedPriceId;
}
export { router as billingRouter };
export { router as billingRouter };