Skip to content

fix(billing): max seats stays out of sync after plan upgrades - #8278

Open
bardock-2393 wants to merge 1 commit into
Flagsmith:mainfrom
bardock-2393:fix/max-seats-out-of-sync
Open

fix(billing): max seats stays out of sync after plan upgrades#8278
bardock-2393 wants to merge 1 commit into
Flagsmith:mainfrom
bardock-2393:fix/max-seats-out-of-sync

Conversation

@bardock-2393

Copy link
Copy Markdown
Contributor

Seat allowances bought as Chargebee addons never reached the organisation's subscription record, so the seat count shown on Users and Permissions stayed at the old value after a seat upgrade while Organisation Billing showed the correct one. Anyone who buys extra seats sees a stale — and lower — limit on the page they use to manage their team.

The subscription record was only refreshed when the Chargebee plan ID itself changed, and even then it took its allowances from the plan's own metadata, which knows nothing about addons. Since seat upgrades are sold as addons, the record was never updated. The subscription information cache behind the billing page was already deriving its figures from the plan and its addons, which is why the two pages disagreed. The webhook now takes the seat and API call allowances from that same addon-aware source.

Changes

  • Seats added to an existing plan are now reflected on the Users and Permissions page.
  • The same applies to plan upgrades that come with addons, and to API call allowances.
  • Tests covering both cases, which fail without the fix.

Closes #8276

Review effort: 2/5

How did you test this code?

Automated. Two tests were added to test_unit_organisations_views.py, both of which fail on main and pass with this change:

  • test_chargebee_webhook__seats_added_to_same_plan__updates_seats — an addon raises the seat count while the plan is unchanged. Fails without the fix with assert 5 == 6.
  • test_chargebee_webhook__plan_changed_with_addons__updates_seats — a plan change where addons add seats on top of the plan's own allowance. Fails without the fix with assert 5 == 8, the plan's figure winning over the real one.

tests/unit/organisations and tests/unit/sales_dashboard pass in full (370 passed, 1 skipped), as do make typecheck on the changed files and the pre-commit suite.

One thing to flag

This keeps the record in sync from here on: an organisation's seat count corrects itself the next time Chargebee sends a subscription webhook for it. Organisations whose record is already stale today are not backfilled, so they will keep showing the old number until their subscription next changes.

_update_caches_with_chargebee_data, the periodic reconciliation task, already holds both the subscription and the addon-aware metadata and could heal every stale record in a few lines. I have left it out because backfilling live billing data seemed like your call rather than mine — happy to add it if you would like it in this PR.

Seats and API calls bought as Chargebee addons only ever reached the
subscription information cache. The subscription record itself was updated
from the plan's own metadata, and only when the plan id changed, so buying
extra seats left `max_seats` stale indefinitely.

Take the seat and API call allowances from the metadata extracted from the
webhook payload, which accounts for the plan and its addons.
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@bardock-2393 is attempting to deploy a commit to the Flagsmith Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

process_subscription now synchronises max_seats and max_api_calls from extracted subscription metadata and saves the subscription only when either value changes. Tests cover seat increases from addons on an unchanged plan and during a plan change. The tests verify subscription and cached seat limits.

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

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

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.

@github-actions github-actions Bot added the api Issue related to the REST API label Aug 12, 2026
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.59%. Comparing base (6c88198) to head (05d2f53).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8278      +/-   ##
==========================================
- Coverage   98.73%   98.59%   -0.15%     
==========================================
  Files        1567     1567              
  Lines       62379    62414      +35     
==========================================
- Hits        61591    61536      -55     
- Misses        788      878      +90     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@bardock-2393
bardock-2393 marked this pull request as ready for review August 12, 2026 20:33
@bardock-2393
bardock-2393 requested a review from a team as a code owner August 12, 2026 20:33
@bardock-2393
bardock-2393 requested review from emyller and removed request for a team August 12, 2026 20:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a2472028-d4a5-471e-9cd5-c39baf5c37a0

📥 Commits

Reviewing files that changed from the base of the PR and between 6c88198 and 05d2f53.

📒 Files selected for processing (2)
  • api/organisations/chargebee/webhook_handlers.py
  • api/tests/unit/organisations/test_unit_organisations_views.py

Comment on lines +1425 to +1458
mock_extract_subscription_metadata.return_value = ChargebeeObjMetadata(
seats=6,
api_calls=1_000_000,
projects=10,
chargebee_email=chargebee_email,
)

data = {
"content": {
"subscription": {
"status": "active",
"id": subscription.subscription_id,
"plan_id": subscription.plan,
},
"customer": {"email": chargebee_email},
}
}

# When
response = admin_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == status.HTTP_200_OK
mock_get_plan_meta_data.assert_not_called()

subscription.refresh_from_db()
assert subscription.max_seats == 6

subscription_information_cache = (
OrganisationSubscriptionInformationCache.objects.get(organisation=organisation)
)
assert subscription_information_cache.allowed_seats == 6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test addon-aware API-call allowances.

Both tests use api_calls=1_000_000, which equals the plan allowance. Neither test checks the persisted API-call allowance. A defect in the max_api_calls synchronisation would pass these tests.

  • api/tests/unit/organisations/test_unit_organisations_views.py#L1425-L1458: use an addon-adjusted api_calls value and assert subscription.max_api_calls and subscription_information_cache.allowed_30d_api_calls.
  • api/tests/unit/organisations/test_unit_organisations_views.py#L1479-L1513: set extracted api_calls above the mocked plan value and assert both persisted values.
🧰 Tools
🪛 ast-grep (0.45.1)

[info] 1444-1444: use jsonify instead of json.dumps for JSON output
Context: json.dumps(data)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

📍 Affects 1 file
  • api/tests/unit/organisations/test_unit_organisations_views.py#L1425-L1458 (this comment)
  • api/tests/unit/organisations/test_unit_organisations_views.py#L1479-L1513

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Max seats stays out of sync after plan upgrades

1 participant