Skip to content

🐛 Fixed orphaned Stripe prices when importing members by tier#28955

Open
Aubaid12 wants to merge 1 commit into
TryGhost:mainfrom
Aubaid12:fix/22115-archive-orphaned-stripe-price
Open

🐛 Fixed orphaned Stripe prices when importing members by tier#28955
Aubaid12 wants to merge 1 commit into
TryGhost:mainfrom
Aubaid12:fix/22115-archive-orphaned-stripe-price

Conversation

@Aubaid12

Copy link
Copy Markdown

fixes #22115

Why

@9larsons reproduced this on main and tagged it bug + help wanted. Importing members by tier could leave orphaned, unused prices piling up on a Stripe product.

What it does

When you import a member with an import_tier and a Stripe customer, forceStripeSubscriptionToProduct() creates a new price on the tier's product, then updates the customer's subscription to use it. That new price is only recorded for archival (it gets cleaned up after the import) once the update succeeds. So if the update throws, for example because Stripe rejects it or the subscription is schedule-managed, the price is never archived and stays orphaned on the product, building up on every import.

The subscription update is now wrapped so that on failure the just-created price is archived before the error propagates. The original error always surfaces, even if archiving itself fails (that's logged as a warning). This is what @9larsons suggested on the issue: "archive the just-created price if the update throws."

Why Ghost users/developers need it

Site owners and integrations that import paid members by tier end up with a growing list of unused prices on their Stripe products, which is confusing to manage. This keeps the price list clean.

Tests

5 unit cases covering the full create → update → archive path: a successful update does not archive; a failed update archives the new price; the original error still surfaces if archiving also fails; an existing-price update failure does not archive (nothing was created); and a createPrice failure does not update or archive. All 17 tests in the file pass.

  • I've read and followed the Contributor Guide
  • I've written an automated test to prove my change works

fixes TryGhost#22115

When importing members with an import_tier, the Stripe utils create a new price on
the tier's product and then update the customer's subscription to use it. If that
update threw (Stripe rejecting it, or a schedule-managed subscription), the
just-created price was never recorded for archival and was left orphaned on the
product, accumulating across imports. As suggested on the issue, the price is now
archived if the subscription update fails, and the original error is surfaced
regardless of whether archiving succeeds.
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 79a4414a-5899-4642-93eb-11f88d9ae146

📥 Commits

Reviewing files that changed from the base of the PR and between d9e9681 and 2e60214.

📒 Files selected for processing (2)
  • ghost/core/core/server/services/members/importer/members-csv-importer-stripe-utils.js
  • ghost/core/test/unit/server/services/members/importer/members-csv-importer-stripe-utils.test.js

Walkthrough

In forceStripeSubscriptionToProduct, the direct updateSubscriptionItemPrice call is replaced with a try/catch block. If the update fails after a new Stripe price was created, the code now calls updatePrice with active: false to archive the orphaned price, logs a warning via @tryghost/logging if archiving itself fails, and re-throws the original update error. New unit tests cover the failure path (with and without archiving failure), the no-new-price path, the createPrice failure path, and the success path.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: orphaned Stripe prices during tier-based member imports.
Description check ✅ Passed The description matches the code changes and explains the orphaned-price fix, failure handling, and tests.
Linked Issues check ✅ Passed The PR addresses #22115 by archiving newly created Stripe prices when subscription updates fail.
Out of Scope Changes check ✅ Passed The code and tests stay focused on the Stripe price cleanup behavior described in the linked issue.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

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.

Import by tier can create lots of prices in Stripe if prices don't match

1 participant