Skip to content

Update the existing subscription when upgrading during a trial - #847

Open
heisbrot wants to merge 1 commit into
mainfrom
fix/trial-upgrade-double-subscription
Open

heisbrot wants to merge 1 commit into
mainfrom
fix/trial-upgrade-double-subscription

Conversation

@heisbrot

@heisbrot heisbrot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Issue ticket number and link

N/A — found while testing billing locally.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Fixes which billing endpoint the plans page calls; nothing user-facing to document.

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

E2E tests

Optional: override the image tags used by the Playwright e2e workflow.
Defaults to main when omitted.

management-cloud-tag: main
reverse-proxy-tag: main

Summary

Since netbirdio/netbird-cloud#508, a Team account can start a Business trial. Its Team Stripe subscription stays active underneath (plan_tier: "trial", active: true, price_id set).

Upgrading during that trial went through subscribe (checkout), because the plans page only used changeSubscription when active && !isTrial. Checkout creates a second Stripe subscription. Management then stores only the new subscription ID, so the Team subscription is never cancelled and keeps billing for the current period.

Changes

  • PlansAndBillingTab: update in place whenever the subscription is active, trial or not. A trial started from Free is inactive and still goes through checkout.
  • canUpgrade in BillingProvider and PlanCard: drop the blanket "trials can always upgrade" exception. It dates from when trials only existed on top of Free, where upgrading is a checkout and not rate limited; the !active check above it still covers that case. A trial on top of Team is now subject to the same 48-hour lock management enforces on in-place updates (starting the trial bumps updated_at), so the button is disabled with the usual tooltip instead of failing with a 412.

Not affected

  • AWSChoosePlan: AWS subscriptions never report remaining_trial, so isTrial is always false there.
  • MSP / distributor tenant flows: they POST a managed subscription, and management already refuses that when the tenant has an active one (ErrAccountAlreadySubscribed).

Follow-ups in management (separate PR)

  • checkoutSession has no equivalent of that IsActive() guard, so other clients can still double-subscribe.
  • Starting a trial bumps updated_at, so a Team user who starts a Business trial must wait 48 hours before upgrading.

Testing
Locally against cloud management with Stripe test mode: Team → start Business trial → upgrade to Business should update the existing subscription (one subscription on the Stripe customer), after the 48-hour lock.

Summary by CodeRabbit

  • Billing
    • Active trial subscriptions now follow the same plan-change process as other active subscriptions.
    • Trial subscriptions are subject to the existing 48-hour eligibility check before changing plans.

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d80d82b4-12f2-4acb-b2b9-5c37fe397791
📥 Commits

Reviewing files that changed from the base of the PR and between 3a267df and 86c32c4.

📒 Files selected for processing (3)
  • src/contexts/BillingProvider.tsx
  • src/modules/billing/PlanCard.tsx
  • src/modules/billing/PlansAndBillingTab.tsx
💤 Files with no reviewable changes (2)
  • src/contexts/BillingProvider.tsx
  • src/modules/billing/PlanCard.tsx

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

Trial subscriptions now follow the existing 48-hour update-time check for upgrade eligibility. Active trial subscriptions now use changeSubscription instead of subscribe.

Changes

Trial Subscription Plan Changes

Layer / File(s) Summary
Trial eligibility and active subscription routing
src/contexts/BillingProvider.tsx, src/modules/billing/PlanCard.tsx, src/modules/billing/PlansAndBillingTab.tsx
Trial subscriptions now follow the 48-hour update-time check. Active trial subscriptions now route through changeSubscription.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~8 minutes

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 86c32

Active trials now follow the subscription update path and the same 48-hour upgrade threshold. The endpoint’s handling of Stripe trials is not visible here, but no actionable merge blocker is established.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description includes the issue reference, documentation selection with an explanation, the required E2E image tags, a clear summary of the bug and changes, affected flows, follow-ups, and testing …
Title check ✅ Passed The title clearly and concisely describes the main change: updating the existing subscription when upgrading during a trial.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 1…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

I’m a rabbit, and I hop with cheer,
Trial plans now check the clock’s clear year.
Forty-eight hours guide the way,
Active changes take a different pathway.
I nibble clover, then I’m gone,
While billing follows the rule from now on.

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

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.

1 participant