Skip to content

🐛 Fixed comp subscription not being cancelled on upgrade to paid#26109

Merged
rob-ghost merged 1 commit intomainfrom
comp-subscription-not-removed-when-upgrading-to-paid-v2
Feb 3, 2026
Merged

🐛 Fixed comp subscription not being cancelled on upgrade to paid#26109
rob-ghost merged 1 commit intomainfrom
comp-subscription-not-removed-when-upgrading-to-paid-v2

Conversation

@rob-ghost
Copy link
Contributor

When a comped member upgrades to a paid subscription, the complimentary access was not being removed. This meant that when the paid subscription was later cancelled, the member would revert to comped status instead of free — effectively giving them perpetual access after a single payment cycle. The bug manifests when the comp tier is different from the paid subscription tier, which is why it was tricky to reproduce locally.

This PR consolidates the two flavours of complimentary access removal into a single removeComplimentarySubscription method in MemberRepository. Ghost has two types of comp access: Stripe-backed subscriptions (with plan_nickname: 'Complimentary' and unit_amount: 0) and Ghost-only comps (products added directly via the Admin UI with no backing Stripe subscription). The old cancelComplimentarySubscription only handled Stripe-backed comps, and didn't filter specifically for complimentary subscriptions — it would cancel all non-canceled subscriptions. The new method handles both types correctly: it cancels Stripe-backed comp subscriptions via the Stripe API (then syncs via linkSubscription), and removes Ghost-only comp products by checking which products are backed by active Stripe subscriptions.

Rather than embedding this logic inside linkSubscription (which was the v1 approach and raised recursion concerns), the comp removal is called from the webhook handlers — SubscriptionEventService and CheckoutSessionEventService — after linkSubscription completes. If the member's status is now paid, any remaining comp access is cleaned up. This keeps linkSubscription focused on its single responsibility of syncing one Stripe subscription to the Ghost database.

Supersedes #26074.

@rob-ghost rob-ghost force-pushed the comp-subscription-not-removed-when-upgrading-to-paid-v2 branch from c7ff59b to be97bab Compare February 2, 2026 17:22
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

Walkthrough

This pull request refactors the handling of complimentary subscriptions in the Members service. A method cancelComplimentarySubscription is renamed to removeComplimentarySubscription and rewritten to handle both Stripe-backed and Ghost-only complimentary subscriptions through distinct removal pathways. A new helper method isComplimentaryPlanNickname encapsulates nickname comparison logic. Service layer files are updated to use the renamed method and invoke it during subscription state transitions (checkout and subscription events). Test files are extended with new unit and E2E test scenarios covering comped-to-paid transitions, removal of Ghost-only complimentary products, and multiple subscription interactions with webhook events.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main bug fix: comp subscriptions not being cancelled when members upgrade to paid, which is the core problem solved by this PR.
Description check ✅ Passed The description comprehensively explains the bug, the two types of complimentary access, the solution approach, and why it supersedes previous attempts, all directly related to the changeset.
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 docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch comp-subscription-not-removed-when-upgrading-to-paid-v2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.40.5)
ghost/core/test/e2e-api/members/webhooks.test.js

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.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 21600240994 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

1 similar comment
@github-actions
Copy link
Contributor

github-actions bot commented Feb 2, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 21600240994 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

ref https://linear.app/ghost/issue/BER-3243

When a comped member upgrades to a paid plan, the complimentary
subscription was not being cancelled, leaving the member with two
active subscriptions. This caused the member to revert to comped
status if the paid subscription was later cancelled.

There are two types of comp: Stripe-backed (legacy, via the comped
flag) and Ghost-only (current Admin UI, via the tiers array). Both
are now handled via a consolidated removeComplimentaryAccess()
method on the member repository, called from linkSubscription when
a paid subscription is linked. Stripe-backed comps are cancelled
via the Stripe API, and Ghost-only comp products are filtered out
by removing products not backed by an active Stripe subscription.

Co-authored-by: Steve Larson <9larsons@gmail.com>
@rob-ghost rob-ghost force-pushed the comp-subscription-not-removed-when-upgrading-to-paid-v2 branch from be97bab to f8a2d71 Compare February 2, 2026 21:10
@github-actions
Copy link
Contributor

github-actions bot commented Feb 3, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 21606912021 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In
`@ghost/core/core/server/services/members/members-api/repositories/member-repository.js`:
- Around line 2007-2056: In removeComplimentarySubscription: when Ghost-only
complimentary products are removed filteredProducts can be empty but
member.status remains stale and _Member.edit currently discards transaction
context; update the member's status based on whether any active Stripe
subscriptions remain (use activeSubscriptionProductIds /
isActiveSubscriptionStatus to determine) and persist the change in the same
transaction by passing options through (or call the repository's update method
that honors options/transacting) instead of calling _Member.edit without
options; ensure you reference member.id (id) and include options/transacting so
the status and products update atomically.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 3, 2026

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 21606912021 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@rob-ghost rob-ghost merged commit 16cf22a into main Feb 3, 2026
85 of 97 checks passed
@rob-ghost rob-ghost deleted the comp-subscription-not-removed-when-upgrading-to-paid-v2 branch February 3, 2026 11:41
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.

2 participants