Skip to content

Fixed gift-to-paid member activity entry reverting after first payment#27681

Merged
sagzy merged 1 commit intomainfrom
gift-subscriptions/member-activity-on-gift-to-paid
May 6, 2026
Merged

Fixed gift-to-paid member activity entry reverting after first payment#27681
sagzy merged 1 commit intomainfrom
gift-subscriptions/member-activity-on-gift-to-paid

Conversation

@sagzy
Copy link
Copy Markdown
Contributor

@sagzy sagzy commented May 5, 2026

closes https://linear.app/ghost/issue/BER-3542

Fixes the member activity feed entry for gift members who continue into a paid subscription.

When a gift member upgraded to a paid subscription, the subscription event initially had enough context to show continued paid subscription after gift. However, getSubscriptionEvents() deleted the eager-loaded paidStatusEvent relation from the shared SubscriptionCreatedEvent Bookshelf model while serializing the first event row.

Because multiple MemberPaidSubscriptionEvent rows can share the same SubscriptionCreatedEvent instance, later rows could lose access to paidStatusEvent. That caused previous_status to become null, and the activity feed could revert to the generic started paid subscription wording after the next payment/update event.

This change:

  • keeps paidStatusEvent available on the shared relation while deriving previous_status
  • removes subscriptionCreatedEvent.paidStatusEvent only from the serialized event payload
  • adds unit coverage for shared subscription-created-event relations so the regression is caught

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Walkthrough

Reworked getSubscriptionEvents to stop removing the helper relation from the in-memory subscriptionCreatedEvent prior to building the serialized payload. The code now deletes model.relations.stripeSubscription before serialization and then removes subscriptionCreatedEvent.paidStatusEvent from the final data object (delete d.subscriptionCreatedEvent?.paidStatusEvent). Added unit tests that mock Bookshelf-like models where multiple paid-subscription events share one SubscriptionCreatedEvent (and a shared paidStatusEvent) to assert previous_status is preserved and the internal paidStatusEvent is not leaked in the serialized output.

Possibly related PRs

  • TryGhost/Ghost#27582: Modifies the same getSubscriptionEvents handling of SubscriptionCreatedEvent.paidStatusEvent, ensuring it’s used for deriving previous_status but not leaked in the serialized subscription_event payload.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main bug fix: preventing a member activity entry from reverting after the first payment for gift-to-paid conversions.
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.
Description check ✅ Passed The pull request description clearly explains the bug being fixed, root cause analysis, the solution approach, and testing coverage.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gift-subscriptions/member-activity-on-gift-to-paid

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.

@sagzy sagzy changed the title 🐛 Fixed gift-to-paid activity entry reverting after first payment Fixed gift-to-paid activity entry reverting after first payment May 5, 2026
@sagzy sagzy changed the title Fixed gift-to-paid activity entry reverting after first payment Fixed gift-to-paid member activity entry reverting after first payment May 6, 2026
@sagzy sagzy force-pushed the gift-subscriptions/member-activity-on-gift-to-paid branch from 75e6850 to 17d168f Compare May 6, 2026 07:52
@sagzy sagzy force-pushed the gift-subscriptions/member-activity-on-gift-to-paid branch from 17d168f to e7f993c Compare May 6, 2026 08:36
ref https://linear.app/ghost/issue/BER-3542

- when a gift member continued with a paid trial, the activity feed correctly logged "Continued paid subscription after gift"
- after the trial converted on first payment, the same entry was reverting to "Started paid subscription"
- root cause: getSubscriptionEvents iterated paid-subscription rows newest-first and deleted the paidStatusEvent helper relation from the shared, eager-loaded SubscriptionCreatedEvent instance after computing previous_status
- once a newer 'updated' row was added on trial→active, it processed first and wiped the relation, so the older 'created' row read previous_status=null on the next iteration
- removed the in-loop delete; the activity-feed serializer already strips subscriptionCreatedEvent from the response, so no leak is reintroduced
- added a regression test that mirrors Bookshelf's shared-instance behaviour and asserts previous_status='gift' survives across iterations
@sagzy sagzy force-pushed the gift-subscriptions/member-activity-on-gift-to-paid branch from e7f993c to 9b6482d Compare May 6, 2026 09:18
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
ghost/core/test/unit/server/services/members/members-api/repositories/event-repository.test.js (1)

760-871: 💤 Low value

Solid regression coverage; consider also asserting the updated row.

The mock faithfully reproduces Bookshelf's shared-instance behaviour for subscriptionCreatedEvent (same reference returned to both rows), and the final assertion on Line 870 nicely guarantees the shared origin is left unmutated — exactly the invariant the production fix establishes. Ordering the rows as updated then created correctly mirrors the buggy iteration order.

A small enhancement to consider: the test only asserts previous_status for the created row. Adding assert.equal(updated.data.previous_status, 'gift'); would tighten the regression so a future "skip previous_status for non-created rows" change couldn't accidentally bypass the check. Optional.

One minor cleanup: makeRelated/sharedRelated (Lines 766–773) are unreachable in this test because relations (Lines 776–779) already contains both subscriptionCreatedEvent and stripeSubscription, so the ?? sharedRelated(name) fallback never fires. You can drop makeRelated/sharedRelated without changing behaviour.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ghost/core/test/unit/server/services/members/members-api/repositories/event-repository.test.js`
around lines 760 - 871, The test getSubscriptionEvents's regression should both
assert the updated row's previous_status and remove dead helper code: add an
assertion similar to assert.equal(updated.data.previous_status, 'gift') in the
"preserves previous_status..." it block so the updated row is explicitly
checked, and delete the unused helper functions makeRelated and sharedRelated
(and their references) from buildModels since relations already contains
subscriptionCreatedEvent and stripeSubscription and the fallback never runs;
update buildModels to rely only on the relations object and toJSON as it
currently does.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@ghost/core/test/unit/server/services/members/members-api/repositories/event-repository.test.js`:
- Around line 760-871: The test getSubscriptionEvents's regression should both
assert the updated row's previous_status and remove dead helper code: add an
assertion similar to assert.equal(updated.data.previous_status, 'gift') in the
"preserves previous_status..." it block so the updated row is explicitly
checked, and delete the unused helper functions makeRelated and sharedRelated
(and their references) from buildModels since relations already contains
subscriptionCreatedEvent and stripeSubscription and the fallback never runs;
update buildModels to rely only on the relations object and toJSON as it
currently does.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 267f7008-f99e-4150-8da4-4f1904321b47

📥 Commits

Reviewing files that changed from the base of the PR and between e7f993c and 9b6482d.

📒 Files selected for processing (2)
  • ghost/core/core/server/services/members/members-api/repositories/event-repository.js
  • ghost/core/test/unit/server/services/members/members-api/repositories/event-repository.test.js

@sagzy sagzy enabled auto-merge (squash) May 6, 2026 09:42
@sagzy sagzy merged commit 5841022 into main May 6, 2026
43 checks passed
@sagzy sagzy deleted the gift-subscriptions/member-activity-on-gift-to-paid branch May 6, 2026 09:44
lujuldotcom pushed a commit to lujuldotcom/Ghost that referenced this pull request May 6, 2026
TryGhost#27681)

closes https://linear.app/ghost/issue/BER-3542

Fixes the member activity feed entry for gift members who continue into
a paid subscription.

When a gift member upgraded to a paid subscription, the subscription
event initially had enough context to show `continued paid subscription
after gift`. However, `getSubscriptionEvents()` deleted the eager-loaded
`paidStatusEvent` relation from the shared `SubscriptionCreatedEvent`
Bookshelf model while serializing the first event row.

Because multiple `MemberPaidSubscriptionEvent` rows can share the same
`SubscriptionCreatedEvent` instance, later rows could lose access to
`paidStatusEvent`. That caused `previous_status` to become `null`, and
the activity feed could revert to the generic `started paid
subscription` wording after the next payment/update event.

This change:

- keeps `paidStatusEvent` available on the shared relation while
deriving `previous_status`
- removes `subscriptionCreatedEvent.paidStatusEvent` only from the
serialized event payload
- adds unit coverage for shared subscription-created-event relations so
the regression is caught
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