Conversation
c7ff59b to
be97bab
Compare
WalkthroughThis pull request refactors the handling of complimentary subscriptions in the Members service. A method Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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.jsThanks 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 |
E2E Tests FailedTo 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
E2E Tests FailedTo 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" |
ghost/core/core/server/services/members/members-api/repositories/member-repository.js
Outdated
Show resolved
Hide resolved
ghost/core/core/server/services/stripe/services/webhook/checkout-session-event-service.js
Show resolved
Hide resolved
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>
be97bab to
f8a2d71
Compare
E2E Tests FailedTo 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" |
There was a problem hiding this comment.
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.
ghost/core/core/server/services/members/members-api/repositories/member-repository.js
Show resolved
Hide resolved
E2E Tests FailedTo 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" |
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
compedstatus instead offree— 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
removeComplimentarySubscriptionmethod in MemberRepository. Ghost has two types of comp access: Stripe-backed subscriptions (withplan_nickname: 'Complimentary'andunit_amount: 0) and Ghost-only comps (products added directly via the Admin UI with no backing Stripe subscription). The oldcancelComplimentarySubscriptiononly 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 vialinkSubscription), 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 —SubscriptionEventServiceandCheckoutSessionEventService— afterlinkSubscriptioncompletes. If the member's status is nowpaid, any remaining comp access is cleaned up. This keepslinkSubscriptionfocused on its single responsibility of syncing one Stripe subscription to the Ghost database.Supersedes #26074.