47 lines
1.8 KiB
TypeScript
47 lines
1.8 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { getSessionUser } from "@/lib/session";
|
|
import { clearSavedPaymentMethod, getOrgBillingConfig } from "@/lib/db";
|
|
import { detachPaymentMethod } from "@/lib/stripe";
|
|
import { safeError } from "@/lib/errors";
|
|
|
|
/**
|
|
* DELETE /api/billing/saved-card
|
|
*
|
|
* Phase 9. Remove the saved card for the caller's org. Detaches
|
|
* the PaymentMethod in Stripe (so it can't be charged again) and
|
|
* clears the four display columns + the pm_id reference locally.
|
|
*
|
|
* Idempotent: calling on an org with no saved card returns 200
|
|
* (the desired end-state is already reached).
|
|
*
|
|
* Auth: any signed-in member of the org. Same reasoning as the
|
|
* setup endpoint — card removal is a customer-visible action; it
|
|
* doesn't leak anything, and a non-owner needing to remove a
|
|
* stolen-card-on-file shouldn't be blocked by role gating.
|
|
*/
|
|
export async function DELETE() {
|
|
const user = await getSessionUser();
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
try {
|
|
const cfg = await getOrgBillingConfig(user.orgId);
|
|
if (!cfg || !cfg.stripeDefaultPaymentMethodId) {
|
|
// Already empty — no-op, return success.
|
|
return NextResponse.json({ removed: false });
|
|
}
|
|
// Stripe detach first. If it fails for a real reason (network,
|
|
// 500 from Stripe), we don't clear the DB — admin can retry.
|
|
// 404 is treated as success by detachPaymentMethod (PM already
|
|
// gone), so we proceed to clear the DB regardless.
|
|
await detachPaymentMethod(cfg.stripeDefaultPaymentMethodId);
|
|
await clearSavedPaymentMethod(user.orgId);
|
|
return NextResponse.json({ removed: true });
|
|
} catch (e) {
|
|
return NextResponse.json(
|
|
{ error: safeError(e, "Failed to remove card") },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|