fix(billing): short-circuit retryWithBackoff on non-transient Stripe errors#3697
Conversation
…errors retryWithBackoff retried every error type, wasting ~600ms of backoff on deterministic StripeInvalidRequestError before dead-lettering. Add an optional shouldRetry(err) predicate (default: always retry) and pass a non-transient classifier at the PI-backfill call site. The err.stack finding from the issue is already satisfied by #3690 (both logger.error calls include stack) — no change needed. Closes #3691
…etry short-circuit The shouldRetry predicate guards both Stripe error type spellings (StripeInvalidRequestError class name + invalid_request_error raw type, which vary by stripe-node version). Add a parallel short-circuit test for the class-name value and align the existing test's predicate with the production one. Closes a coverage gap flagged by the pre-push gate.
Walkthrough
ChangesRetry predicate enhancement for deterministic Stripe error short-circuiting
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #3697 +/- ##
==========================================
+ Coverage 89.71% 89.72% +0.01%
==========================================
Files 142 142
Lines 4784 4789 +5
Branches 1500 1503 +3
==========================================
+ Hits 4292 4297 +5
Misses 385 385
Partials 107 107
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR refines the billing retry utility to allow callers to short-circuit retries on non-transient errors, and applies that behavior to the Stripe PaymentIntent metadata backfill path to avoid unnecessary backoff delays on deterministic Stripe client failures.
Changes:
- Extend
retryWithBackoffto accept an optionalshouldRetry(err)predicate (default: always retry) with argument validation. - Wire a Stripe invalid-request classifier into the webhook backfill call site to skip retries for
invalid_request_error/StripeInvalidRequestError. - Add a dedicated
billing.retry.unit.tests.jssuite covering success, transient retry, exhaustion, short-circuit behavior, and validation (using fake timers).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| modules/billing/lib/billing.retry.js | Adds shouldRetry predicate support + validation to the backoff retry utility. |
| modules/billing/services/billing.webhook.service.js | Uses shouldRetry to short-circuit retries for deterministic Stripe invalid-request errors during PI metadata backfill. |
| modules/billing/tests/billing.retry.unit.tests.js | New unit tests validating retry/short-circuit/validation behavior without real timer delays. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/billing/services/billing.webhook.service.js`:
- Around line 326-334: The failure log wording is inaccurate when the new
shouldRetry predicate short-circuits retries; update the logger call that
currently reads like "failed after retries" to reflect whether retries were
attempted or skipped by shouldRetry. Locate the shouldRetry predicate and the
corresponding error logging in the webhook processing (the block that logs
failures after the retry policy) and change the message to distinguish two
cases: (a) retries were skipped due to a deterministic Stripe error (include
err.type or a phrase like "retries skipped - deterministic error"), and (b)
retries were attempted and exhausted (keep "failed after retries"); include the
error type/details in both logs for clarity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5a7896ea-9879-4efb-ab20-e885823f6e36
📒 Files selected for processing (3)
modules/billing/lib/billing.retry.jsmodules/billing/services/billing.webhook.service.jsmodules/billing/tests/billing.retry.unit.tests.js
…rding - Narrow shouldRetry JSDoc comment to only mention invalid_request_error (not auth errors) — matches what the predicate actually checks (Copilot thread PRRT_kwDOBss37M6EM5HL) - Change failure log from "failed after retries" to "failed (retries exhausted or skipped)" to reflect short-circuit path (CodeRabbit thread PRRT_kwDOBss37M6EM5c1) - Update billing.refund-correlation tests to match new log wording
Summary
shouldRetry(err)predicate toretryWithBackoff(defaults to always-retry — backward-compatible for all existing callers).StripeInvalidRequestError/invalid_request_errorshort-circuits straight to the dead-letter path instead of burning ~600ms of backoff.billing.retry.unit.tests.js: success / transient-retry / exhaustion / short-circuit (both type spellings) / arg validation — fake timers, no real delay.err.stackfinding from fix(billing): refine retryWithBackoff — short-circuit on non-transient Stripe errors + restore err.stack #3691 is already satisfied by fix(billing): retry refund correlation backfill 3x + dead-letter #3690 (bothlogger.errorcalls includestack) — no change needed here.Closes #3691
Scope
modules/billing/lib/billing.retry.js,modules/billing/services/billing.webhook.service.js,modules/billing/tests/billing.retry.unit.tests.jsretryWithBackoffis only called inbilling.webhook.service.js; new param is optional and defaults to prior behaviourValidation
npm run lintnpm run test:unit— 1509/1509 green (incl. 7 new retry tests, sub-second with fake timers)invalid_request_errorandStripeInvalidRequestErrorGuardrails check
shouldRetryparam is optional with backward-compatible default)Notes for reviewers
retryWithBackoffvia the default (always-retry) path so zero propagation needed