import { encode } from "https://deno.land/std@0.168.0/encoding/hex.ts"; import Stripe from "https://esm.sh/stripe@22.0.2"; const getEnv = (key: string): string => { const value = Deno.env.get(key); if (!value) throw new Error(`${key} is not configured`); return value; }; export type StripeEnv = "sandbox" | "live"; const GATEWAY_STRIPE_BASE = "https://connector-gateway.lovable.dev/stripe"; export function getConnectionApiKey(env: StripeEnv): string { return env === "sandbox" ? getEnv("STRIPE_SANDBOX_API_KEY") : getEnv("STRIPE_LIVE_API_KEY"); } export function createStripeClient(env: StripeEnv): Stripe { const connectionApiKey = getConnectionApiKey(env); const lovableApiKey = getEnv("LOVABLE_API_KEY"); return new Stripe(connectionApiKey, { apiVersion: "2026-03-25.dahlia" as any, httpClient: Stripe.createFetchHttpClient((input, init) => { const stripeUrl = input instanceof Request ? input.url : input.toString(); const gatewayUrl = stripeUrl.replace("https://api.stripe.com", GATEWAY_STRIPE_BASE); return fetch(gatewayUrl, { ...init, headers: { ...Object.fromEntries( new Headers( init?.headers ?? (input instanceof Request ? input.headers : undefined), ).entries(), ), "X-Connection-Api-Key": connectionApiKey, "Lovable-API-Key": lovableApiKey, }, }); }), }); } export async function verifyWebhook( req: Request, env: StripeEnv, ): Promise<{ type: string; data: { object: any } }> { const signature = req.headers.get("stripe-signature"); const body = await req.text(); const secret = env === "sandbox" ? getEnv("PAYMENTS_SANDBOX_WEBHOOK_SECRET") : getEnv("PAYMENTS_LIVE_WEBHOOK_SECRET"); if (!signature || !body) throw new Error("Missing signature or body"); let timestamp: string | undefined; const v1Signatures: string[] = []; for (const part of signature.split(",")) { const [key, value] = part.split("=", 2); if (key === "t") timestamp = value; if (key === "v1") v1Signatures.push(value); } if (!timestamp || v1Signatures.length === 0) throw new Error("Invalid signature format"); const age = Math.abs(Date.now() / 1000 - Number(timestamp)); if (age > 300) throw new Error("Webhook timestamp too old"); const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const signed = await crypto.subtle.sign( "HMAC", key, new TextEncoder().encode(`${timestamp}.${body}`), ); const expected = new TextDecoder().decode(encode(new Uint8Array(signed))); if (!v1Signatures.includes(expected)) throw new Error("Invalid webhook signature"); return JSON.parse(body); }