import { NextResponse } from "next/server"; import { getSessionUser } from "@/lib/session"; import { getOrgBilling } from "@/lib/db"; import { createSetupCheckoutSession, ensureStripeCustomerForOrg, } from "@/lib/stripe"; import { safeError } from "@/lib/errors"; /** * POST /api/billing/setup-card * * Phase 9. Customer-initiated "Set up auto-pay" / "Update card" * flow. Creates a Checkout session in setup mode and returns its * URL — the caller redirects the browser. On completion, the * webhook handler saves the resulting PaymentMethod's display * fields against this org's billing config. * * Auth: any signed-in member of the org. We don't owner-gate this * because non-owners might legitimately need to update payment * (e.g., for a team they administer). The actual card data is * collected by Stripe, not us — there's nothing to leak from * misuse here. * * Requires an existing billing snapshot (org_billing row). If * absent, returns 400 — the customer hasn't set their billing * address yet, and Stripe needs the address for the customer * object. */ export async function POST(request: Request) { const user = await getSessionUser(); if (!user) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const orgBilling = await getOrgBilling(user.orgId); if (!orgBilling) { return NextResponse.json( { error: "Billing address required before saving a card." }, { status: 400 } ); } try { // Ensure the Stripe customer exists. Idempotent — if we // already created one for this org (e.g. from a prior // "Pay by Card" Checkout), it's reused. const customerId = await ensureStripeCustomerForOrg({ zitadelOrgId: user.orgId, companyName: orgBilling.companyName, billingEmail: orgBilling.billingEmail, address: { line1: orgBilling.streetAddress, postalCode: orgBilling.postalCode, city: orgBilling.city, country: orgBilling.country, }, }); // Pick the base URL from the request's origin so redirects // work in dev (localhost), staging, and prod without env vars. const origin = new URL(request.url).origin; const session = await createSetupCheckoutSession({ customerId, baseUrl: origin, }); return NextResponse.json({ url: session.url }); } catch (e) { return NextResponse.json( { error: safeError(e, "Failed to start card setup") }, { status: 500 } ); } }