Repository navigation
Conversation
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
|
It looks like this PR contains a migration 👀 General requirements
Schema changes
Data changes
|
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run ghost:test:ci:unit |
✅ Succeeded | 1m 17s | View ↗ |
nx run-many -t test:unit -p @tryghost/admin,gho... |
✅ Succeeded | 3m 59s | View ↗ |
nx run @tryghost/admin:test:acceptance --shard=1/3 |
✅ Succeeded | 6m 27s | View ↗ |
nx run ghost:test:ci:integration |
✅ Succeeded | 1m 47s | View ↗ |
nx run @tryghost/admin:test:acceptance --shard=2/3 |
✅ Succeeded | 5m 41s | View ↗ |
nx run ghost:test:integration |
✅ Succeeded | 1m 29s | View ↗ |
nx run @tryghost/admin:test:acceptance --shard=3/3 |
✅ Succeeded | 4m 45s | View ↗ |
nx run ghost:test:ci:e2e |
✅ Succeeded | 3m 33s | View ↗ |
Additional runs (10) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-10-22 13:05:17 UTC
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #32115 +/- ##
==========================================
- Coverage 85.55% 85.39% -0.16%
==========================================
Files 1398 1399 +1
Lines 52426 52559 +133
Branches 9006 9030 +24
==========================================
+ Hits 44853 44884 +31
- Misses 6470 6566 +96
- Partials 1103 1109 +6
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
f6558dc to
a74c924
Compare
257ac37 to
860f217
Compare
ref https://linear.app/ghost/issue/BER-4047 Which paid tiers ask for a shipping address or a phone number was a Stripe Checkout setting, so only Stripe Checkout could act on it, while a paying member changing tier never reaches a checkout and Portal will need to ask the tier itself what it needs. Tiers now carry requirements: what their members must give, named by the port a checkout collects it under, such as `checkout.shipping`, on every paid tier including tiers added later or on the paid tiers named for it. While checkouts collect them, which Labs gates, the Admin and Content APIs list each tier's requirements to callers that ask with `include=requirements`, as checkouts would ask for them, with the parts each is asked in, named by port, leaving where the answers are kept to the ports' bindings, and adding or editing a tier answers with them when asked, as a requirement on every paid tier covers a new one. Other readers of tiers, such as themes listing them, read nothing more. Tiers are still read into memory at boot, but their requirements are read from the database whenever they're asked for, so every instance serving a site lists them as soon as they're saved. Saving a change to shipping or the phone number, or archiving, restoring or deleting a custom field a port writes into, clears the site's cache, as those tier responses are public. The checkout settings stay where they're set and their Admin API is unchanged: saving shipping or the phone number writes its tier requirement, while the Stripe Checkout settings keep only what is Stripe's own, the design, the countries it ships to and the tax ID with its tiers. The ports are renamed under `checkout.` so they say what collects them, and collection is behind a private flag, so its saved settings are discarded rather than moved.
860f217 to
5895151
Compare

ref https://linear.app/ghost/issue/BER-4047
Problem
Which paid tiers ask for a shipping address or a phone number lived in the Stripe Checkout settings, beside Stripe's own options, so only Stripe Checkout could act on it. A paying member who changes tier never reaches a checkout (BER-3893), so Portal will need to ask the tier itself what it needs. Publishing the Stripe settings through the tiers API was taken back out of #31623, because it made tiers depend on the payment integration.
Solution
The settings split into three sets, each owned where it belongs:
The checkout settings stay the entry point, and their Admin API doesn't change. Saving shipping or the phone number binds its ports and writes its tier requirement:
Tiers in the Admin and Content APIs list their requirements when asked with
include=requirements, with the parts each is asked in, each named by the port its answer is collected under:The ports are renamed under
checkout.so they say what collects them.@tryghost/checkoutnow says which ports a tier can require, the parts each is given in, and which parts it can't be asked for without:How to review
It reads best in this order:
packages/checkout/src/field-ports.ts: the renamed ports, their parts andisAskable;schema.js;tiers/requirements.ts, thentier-repository.js,tiers-api.jsand the tier serializer;writeTargetsand the read instripe-checkout-config/service.ts;bindings-service.tsfieldsFor.Decisions
@tryghost/checkout, and the ports a binding can point at are derived from them. The compiler ties each part the checkout settings bind to the port its tiers are required under, and a part added to a port doesn't compile until the checkout settings give it somewhere to be saved. The alternative, matching parts by name socheckout.shippingcovers whatever startscheckout.shipping., leaves nothing to check that a part belongs to its port.isAskable: every part has a field, and every essential part's field can take a value. With the recipient's name archived, shipping is still listed, with the address only. With the address archived, it's left out until it's restored. Once either field is deleted, it's left out, and the checkout settings read shipping as off.stripeCheckoutDesignandstripeCheckoutCollectionare on, as collection releases after the design. Checkouts and the tiers' input serializer use the same check, so with either flag off no tier response changes and nothing more is read, even when asked.include=requirements. Themes listing tiers with{{#get "tiers"}}, post lists, members and gifts all read tiers, and none of them needs requirements, so they read nothing more.includeis how Ghost's APIs let a caller ask for more of a resource, and nothing reads requirements yet, so asking costs Portal one parameter. The alternative, listing them on every tier read while the flags are on, added two queries to each theme's tier list and needed the site's cache header to ask for the free tier alone. Nothing is read for a list with no paid tier in it. Adding or editing a tier answers with it as saved, with its requirements when asked, as an "every paid tier" requirement covers a new paid tier straight away.gift_subscription_redemption_notificationfromuserstable #27369). While a rolling deploy replaces instances, one still on the old code fails to read the checkout settings: its checkouts use Stripe's own design, and Admin's checkout settings don't load until it's replaced. The alternative, dropping it a release later, leaves a column nothing reads for a feature that's still private.stripeCheckoutCollectionis private, so shipping and the phone number read as off until switched on again. Their bindings go with them, as the ports were renamed. The tax ID and the design are kept.Suggested follow-ups
include=requirements, sends each answer by port, and Ghost writes it through the port's binding, as completed checkouts do, so Portal never needs a field's key. Pre-filling a member's own answers would read them by port the same way. Portal labels each part from its port, as Stripe Checkout does.Validation
With Stripe connected and the
stripeCheckoutCollectionandmembersCustomFieldsLabs flags on:GET /ghost/api/content/tiers/?include=requirementslistscheckout.shippingwith both fields on that tier only. Withoutinclude=requirements, no tier lists requirements. Switch it to every paid tier: every paid tier lists it, and a paid tier created afterwards lists it too.stripeCheckoutCollectionoff.GET /ghost/api/content/tiers/?include=requirementslists no requirements. Switch it back on and switchstripeCheckoutDesignoff: still none.