feat(billing): emit plan.changed event on subscription plan change#3274
feat(billing): emit plan.changed event on subscription plan change#3274PierreBrisorgueil merged 3 commits intomasterfrom
Conversation
Adds a billing event emitter that downstream projects can subscribe to for handling plan upgrades/downgrades. The webhook service now detects plan changes from Stripe's previous_attributes and emits plan.changed with organizationId, previousPlan, newPlan, subscription, and isDowngrade.
|
Warning Rate limit exceeded
⌛ 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 (2)
WalkthroughA billing plan change detection system is implemented via Stripe webhooks. The webhook controller now passes the full event object to the service, which detects plan changes from previous attributes, emits a Changes
Sequence DiagramsequenceDiagram
participant Stripe as Stripe Webhook
participant Controller as Webhook Controller
participant Service as Billing Webhook Service
participant Emitter as Event Emitter
participant Listener as Event Listeners<br/>(Projects)
Stripe->>Controller: POST customer.subscription.updated
Controller->>Service: handleSubscriptionUpdated(subscriptionData, event)
Service->>Service: Extract previous plan from event.data.previous_attributes
alt Plan changed detected
Service->>Service: Determine if downgrade using planRanks
Service->>Emitter: emit('plan.changed', {organizationId, previousPlan, newPlan, subscription, isDowngrade})
Emitter->>Listener: Registered handler called
Listener->>Listener: React to downgrade (disable crons, etc.)
else No plan change
Service->>Service: Skip event emission
end
Service->>Service: Update organization subscription
Service-->>Controller: Return response
Controller-->>Stripe: Return 200 OK
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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)
📝 Coding Plan
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the 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 |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
modules/billing/services/billing.webhook.service.js (1)
81-86:⚠️ Potential issue | 🟡 MinorMissing
@paramfor the neweventparameter in JSDoc.The function signature was updated to accept an
eventparameter, but the JSDoc is missing its documentation.As per coding guidelines: "Every new or modified function must have a JSDoc header: one-line description,
@paramfor each argument,@returnsfor any non-void return value."📝 Proposed fix
/** * `@desc` Handle customer.subscription.updated event — sync subscription state * `@param` {Object} subscription - Stripe subscription object + * `@param` {Object} event - Full Stripe event object containing previous_attributes * `@returns` {Promise<void>} */ const handleSubscriptionUpdated = async (subscription, event) => {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@modules/billing/services/billing.webhook.service.js` around lines 81 - 86, The JSDoc for handleSubscriptionUpdated was not updated to document the new event parameter; update the function comment block for handleSubscriptionUpdated to include a `@param` for event (e.g., `@param` {Object} event - Stripe event object or webhook event) alongside the existing subscription param documentation, keep the one-line description and the `@returns` {Promise<void>} unchanged, and ensure the param descriptions clearly state their purpose to satisfy the JSDoc guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@modules/billing/services/billing.webhook.service.js`:
- Around line 12-16: planRanks initialization assumes config.billing.plans
exists and will crash at module load if undefined; update the code that defines
planRanks to first validate that config.billing and
Array.isArray(config.billing.plans) (or default to an empty array) before
mapping, e.g. guard the expression used to build planRanks (refer to the
planRanks constant) so it uses a safe fallback or throws a clear, early error
with context rather than letting a TypeError occur.
In `@modules/billing/tests/billing.events.unit.tests.js`:
- Around line 28-45: Tests are missing a mock for config.billing.plans which is
used at module load to build planRanks and can change isDowngrade behavior; add
a jest.unstable_mockModule that exports a config object with a stable
billing.plans array (e.g., ["free","pro","enterprise"]) before importing
BillingWebhookService or billingEvents in the test so planRanks is computed from
the mocked config, ensuring deterministic isDowngrade assertions for
BillingWebhookService and billingEvents.
---
Outside diff comments:
In `@modules/billing/services/billing.webhook.service.js`:
- Around line 81-86: The JSDoc for handleSubscriptionUpdated was not updated to
document the new event parameter; update the function comment block for
handleSubscriptionUpdated to include a `@param` for event (e.g., `@param` {Object}
event - Stripe event object or webhook event) alongside the existing
subscription param documentation, keep the one-line description and the `@returns`
{Promise<void>} unchanged, and ensure the param descriptions clearly state their
purpose to satisfy the JSDoc guideline.
🪄 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: d60bc016-5cb3-40e5-a088-cec47a9be78a
📒 Files selected for processing (5)
modules/billing/controllers/billing.webhook.controller.jsmodules/billing/lib/events.jsmodules/billing/services/billing.webhook.service.jsmodules/billing/tests/billing.controller.unit.tests.jsmodules/billing/tests/billing.events.unit.tests.js
There was a problem hiding this comment.
Pull request overview
Adds an internal billing event hook so downstream projects can react to Stripe subscription plan changes detected in the billing webhook flow.
Changes:
- Introduces a singleton billing
EventEmitterand emitsplan.changedwhen Stripeprevious_attributesindicates a plan transition. - Extends the subscription-updated webhook service/controller plumbing to pass the full Stripe event for diffing.
- Adds unit tests covering upgrade/downgrade detection and “no-change” scenarios.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| modules/billing/lib/events.js | Adds singleton billingEvents emitter used for billing-domain events. |
| modules/billing/services/billing.webhook.service.js | Detects plan changes via previous_attributes, computes downgrade/upgrade, emits plan.changed. |
| modules/billing/controllers/billing.webhook.controller.js | Passes the full Stripe event into handleSubscriptionUpdated. |
| modules/billing/tests/billing.events.unit.tests.js | New unit tests for plan.changed emission and payload correctness. |
| modules/billing/tests/billing.controller.unit.tests.js | Updates controller unit test to expect the new service call signature. |
Summary
modules/billing/lib/events.js— singleton EventEmitter for billing eventshandleSubscriptionUpdatedby comparingevent.data.previous_attributeswith current planplan.changedevent with{ organizationId, previousPlan, newPlan, subscription, isDowngrade }billingEvents.on('plan.changed', handler)Test plan
plan.changedwith correct payload (downgrade pro → starter)previous_attributesCloses #3271
Summary by CodeRabbit
New Features
Tests