feat(billing): admin toolkit + operational foundations (Étape 3)#3612
feat(billing): admin toolkit + operational foundations (Étape 3)#3612PierreBrisorgueil merged 3 commits intomasterfrom
Conversation
A — Compound indexes on Subscription for weeklyReset + dunningSweep scans.
B — 6 admin endpoints: customer status, Stripe→DB sync, webhook replay,
dead-letter list + purge, subscription cancel (Decision #5 verify).
CASL: BillingAdminOps subject on 5 routes.
C — Reconciliation cron (billing.reconcile.js, weekly Sunday 03h).
LOG-ONLY policy — emits billing.reconciliation.divergence events.
D — syncIndexes in integration test beforeAll (E11000 flake fix).
Refund edge-case tests: refund>original→422, multi-refund cumul.
Stripe invalid_request_error/StripeInvalidRequestError→422 mapping.
|
Warning Rate limit exceeded
To continue reviewing without waiting, purchase usage credits in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (17)
WalkthroughThis PR introduces a comprehensive billing admin toolkit for managing Stripe operations (customer status, synchronization, webhook replay, dead-letter event management, subscription cancellation) alongside a separate weekly reconciliation cron that detects divergences between Stripe subscriptions and the database without mutating state. Both features are backed by new services, repository functions, endpoints, schemas, and extensive test coverage. ChangesBilling Admin Toolkit
Billing Reconciliation
Sequence Diagram(s)sequenceDiagram
participant Admin as Admin User
participant Controller as Admin Controller
participant AdminService as Admin Service
participant Stripe as Stripe API
participant DB as Database
Admin->>Controller: POST /api/admin/billing/sync/:orgId
Controller->>AdminService: syncOrgFromStripe(orgId)
AdminService->>DB: findByOrganization(orgId)
DB-->>AdminService: subscription with stripeSubscriptionId
AdminService->>Stripe: retrieveSubscription(stripeSubscriptionId)
Stripe-->>AdminService: live Stripe subscription
AdminService->>AdminService: resolvePlan(stripeSubscription)
AdminService->>DB: updateSubscription with new plan/status
AdminService->>DB: updateOrganization plan
AdminService-->>Controller: { status, plan, ... }
Controller-->>Admin: 200 { status, plan, ... }
sequenceDiagram
participant ReconcileCron as Reconcile Cron
participant ReconcileService as Reconcile Service
participant DB as Database
participant Stripe as Stripe API
participant EventBus as Event Bus
ReconcileCron->>ReconcileService: runReconciliation()
ReconcileService->>DB: find subscriptions by status
DB-->>ReconcileService: paginated active/past-due subs
loop For each subscription
ReconcileService->>Stripe: retrieveSubscription(stripeId)
Stripe-->>ReconcileService: live Stripe subscription
ReconcileService->>ReconcileService: compareStatus and plan
alt Divergence detected
ReconcileService->>EventBus: emit billing.reconciliation.divergence
end
end
ReconcileService-->>ReconcileCron: { checked, divergences, errors }
ReconcileCron->>ReconcileCron: log summary and set exitCode
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 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 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❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3612 +/- ##
==========================================
+ Coverage 87.68% 88.11% +0.43%
==========================================
Files 132 134 +2
Lines 4149 4417 +268
Branches 1290 1364 +74
==========================================
+ Hits 3638 3892 +254
- Misses 398 412 +14
Partials 113 113
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:
|
…patch, controller error paths Add tests for: multi-page pagination in reconcile service (page += 1 branch), billingEvents listener error (non-fatal swallow), all 10 dispatchWebhookEvent switch cases via test.each, and controller catch paths for sync/replay/dead-letters (422/502/500). Fixes codecov/patch failure on PR #3612.
- HIGH: remove dead .catch() on deleteByEventId — repo returns
{deleted:false} on not-found, infra errors now propagate correctly
- MEDIUM 1: wire AdminDeadLettersQuery.safeParse in controller —
model.isValid is body-only (POST/PUT), query validation in controller
- MEDIUM 2: admin has can('manage','all') — BillingAdminOps covered
- MEDIUM 3: add comment clarifying full-sync vs adminUpdatePlanOnly
- test: replace swallow-error test with infra-error-propagates test
Summary
Subscriptionmodel:{ status, lastResetAt }(weeklyReset scan) and{ status, pastDueSince }(dunningSweep scan)policy.isAllowedadmin guard):GET /api/admin/billing/customer/:orgId— Stripe + DB side-by-side diagnosticPOST /api/admin/billing/sync/:orgId— Force Stripe→DB syncPOST /api/admin/billing/webhook/replay— Re-fetch Stripe event + re-dispatchGET /api/admin/billing/dead-letters— Paginated dead-letter listDELETE /api/admin/billing/dead-letters/:eventId— Purge post-investigationPOST /api/admin/billing/cancel/:orgId— Cancel + Decision adapting to front #5 (cancel→retrieve-verify→DB write)BillingAdminOps(admins already havecan('manage', 'all'))billing.reconcile.js, Sunday 03h): paginates active|past_due subs, fetches live Stripe status, emitsbilling.reconciliation.divergence. LOG-ONLY — no auto-fix.syncIndexes()in integration testbeforeAll(prevents E11000 on new compound indexes); refund edge-case tests (refund > original → 422, multi-refund dedup,charge_already_refunded→ 422);StripeInvalidRequestError/invalid_request_errorerror types now map to 422 inadminRefundCharge.Test plan
billing.admin.service.unit.tests.js(28 tests),billing.admin.toolkit.integration.tests.js(18 tests),billing.reconcile.service.unit.tests.js(8 tests),billing.processedStripeEvent.repository.unit.tests.js(+12 tests for listDeadLetters + purgeDeadLetterByEventId)Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores