Skip to content

feat(billing): emit plan.changed event on subscription plan change#3274

Merged
PierreBrisorgueil merged 3 commits intomasterfrom
feat/billing-plan-changed-event
Mar 18, 2026
Merged

feat(billing): emit plan.changed event on subscription plan change#3274
PierreBrisorgueil merged 3 commits intomasterfrom
feat/billing-plan-changed-event

Conversation

@PierreBrisorgueil
Copy link
Contributor

@PierreBrisorgueil PierreBrisorgueil commented Mar 18, 2026

Summary

  • Create modules/billing/lib/events.js — singleton EventEmitter for billing events
  • Detect plan changes in handleSubscriptionUpdated by comparing event.data.previous_attributes with current plan
  • Emit plan.changed event with { organizationId, previousPlan, newPlan, subscription, isDowngrade }
  • Downstream projects can subscribe via billingEvents.on('plan.changed', handler)

Test plan

  • Unit test: plan change emits plan.changed with correct payload (downgrade pro → starter)
  • Unit test: no event emitted when no previous_attributes
  • Unit test: upgrade detection (starter → pro, isDowngrade = false)
  • Unit test: downgrade detection (enterprise → free, isDowngrade = true)
  • Unit test: no event when previous plan equals new plan
  • All 221 existing tests pass
  • Lint passes (0 errors)

Closes #3271

Summary by CodeRabbit

  • New Features

    • Enhanced billing webhook handling to automatically detect customer plan changes, including both upgrades and downgrades.
    • Implemented plan change event notifications with detailed information about plan transitions and downgrade status.
  • Tests

    • Added comprehensive test suite validating plan change detection and event emission across multiple upgrade and downgrade scenarios.

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.
@coderabbitai
Copy link

coderabbitai bot commented Mar 18, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 172e1bd8-1074-4b44-ae38-2b201d11133c

📥 Commits

Reviewing files that changed from the base of the PR and between e4edf4f and 501ec10.

📒 Files selected for processing (2)
  • modules/billing/services/billing.webhook.service.js
  • modules/billing/tests/billing.events.unit.tests.js

Walkthrough

A 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 plan.changed event with downgrade status, and includes comprehensive test coverage for various scenarios.

Changes

Cohort / File(s) Summary
Webhook Controller
modules/billing/controllers/billing.webhook.controller.js
Updated handleSubscriptionUpdated invocation to pass the full event object as a second argument in addition to subscription data.
Event Emitter Module
modules/billing/lib/events.js
Introduced new module exporting a singleton EventEmitter instance for billing events, with documentation for plan.changed event payload structure.
Webhook Service Enhancement
modules/billing/services/billing.webhook.service.js
Added dependencies for config and billingEvents; created planRanks mapping from config.billing.plans; enhanced signature to accept event parameter; added plan change detection logic that compares previous_attributes with new plan and emits plan.changed event with downgrade status.
Test Updates
modules/billing/tests/billing.controller.unit.tests.js, modules/billing/tests/billing.events.unit.tests.js
Updated controller test to reflect new service signature; introduced comprehensive test suite for plan.changed event covering upgrade, downgrade, no-change, and edge case scenarios with event payload validation.

Sequence Diagram

sequenceDiagram
    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
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding plan.changed event emission on subscription plan changes.
Description check ✅ Passed Description covers summary, test plan with verification checks, and linked issue reference, but lacks scope, validation checklist, and guardrails sections from the template.
Linked Issues check ✅ Passed All coding requirements from #3271 are met: plan.changed event emission with correct payload, EventEmitter pattern used, downgrade detection via plan ranks, and comprehensive tests validating the functionality.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing plan.changed event emission as specified in #3271; no unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/billing-plan-changed-event
📝 Coding Plan
  • Generate coding plan for human review comments

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟡 Minor

Missing @param for the new event parameter in JSDoc.

The function signature was updated to accept an event parameter, but the JSDoc is missing its documentation.

As per coding guidelines: "Every new or modified function must have a JSDoc header: one-line description, @param for each argument, @returns for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0eede6e and e4edf4f.

📒 Files selected for processing (5)
  • modules/billing/controllers/billing.webhook.controller.js
  • modules/billing/lib/events.js
  • modules/billing/services/billing.webhook.service.js
  • modules/billing/tests/billing.controller.unit.tests.js
  • modules/billing/tests/billing.events.unit.tests.js

Copilot AI review requested due to automatic review settings March 18, 2026 20:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 EventEmitter and emits plan.changed when Stripe previous_attributes indicates 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.

@PierreBrisorgueil PierreBrisorgueil merged commit fc34ae8 into master Mar 18, 2026
3 checks passed
@PierreBrisorgueil PierreBrisorgueil deleted the feat/billing-plan-changed-event branch March 18, 2026 21:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(billing): plan.changed event hook for downgrade handling

2 participants