Skip to content

Commit f535989

Browse files
steebchenclaude
andcommitted
fix(billing): log declined tier-change cards as warn
Plan tier-change handlers logged every Stripe failure at error severity before checking the decline code, so an expected card_declined / insufficient_funds decline (surfaced to the user as a 402) fired a server-error alert. Move the decline check ahead of the error log and log those expected declines at warn instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2c63fea commit f535989

2 files changed

Lines changed: 20 additions & 9 deletions

File tree

apps/api/src/routes/chat-plans.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,20 +528,26 @@ chatPlans.openapi(changeTier, async (c) => {
528528
if (error instanceof HTTPException) {
529529
throw error;
530530
}
531-
logger.error(
532-
"Stripe chat plan tier change error",
533-
error instanceof Error ? error : new Error(String(error)),
534-
);
531+
// A declined card / required invoice payment is an expected user-facing
532+
// outcome, not a server fault: surface it as a 402 and log at warn — never
533+
// error — to avoid noisy alerts for declined cards.
535534
const errCode =
536535
typeof error === "object" && error !== null && "code" in error
537536
? String((error as { code?: unknown }).code)
538537
: undefined;
539538
if (errCode === "card_declined" || errCode === "invoice_payment_required") {
539+
logger.warn("Chat plan tier change payment declined", {
540+
code: errCode,
541+
});
540542
throw new HTTPException(402, {
541543
message:
542544
"Upgrade payment could not be collected. Update your payment method and try again.",
543545
});
544546
}
547+
logger.error(
548+
"Stripe chat plan tier change error",
549+
error instanceof Error ? error : new Error(String(error)),
550+
);
545551
throw new HTTPException(500, {
546552
message: "Failed to change chat plan tier",
547553
});

apps/api/src/routes/dev-plans.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,24 +1310,29 @@ devPlans.openapi(changeTier, async (c) => {
13101310
if (error instanceof HTTPException) {
13111311
throw error;
13121312
}
1313-
logger.error(
1314-
"Stripe dev plan tier change error",
1315-
error instanceof Error ? error : new Error(String(error)),
1316-
);
13171313
// Stripe returns StripeCardError / StripeInvalidRequestError when an
13181314
// upgrade can't be collected (declined card, no payment method on file,
13191315
// etc.). Surface this to the caller as a 402 instead of a generic 500
1320-
// so the UI can prompt the user to update billing.
1316+
// so the UI can prompt the user to update billing. This is an expected
1317+
// user-facing outcome, not a server fault, so log it at warn — never
1318+
// error — to avoid noisy alerts for declined cards.
13211319
const errCode =
13221320
typeof error === "object" && error !== null && "code" in error
13231321
? String((error as { code?: unknown }).code)
13241322
: undefined;
13251323
if (errCode === "card_declined" || errCode === "invoice_payment_required") {
1324+
logger.warn("Dev plan tier change payment declined", {
1325+
code: errCode,
1326+
});
13261327
throw new HTTPException(402, {
13271328
message:
13281329
"Upgrade payment could not be collected. Update your payment method and try again.",
13291330
});
13301331
}
1332+
logger.error(
1333+
"Stripe dev plan tier change error",
1334+
error instanceof Error ? error : new Error(String(error)),
1335+
);
13311336
throw new HTTPException(500, {
13321337
message: "Failed to change dev plan tier",
13331338
});

0 commit comments

Comments
 (0)