Added agent payments for premium markdown URLs - #29840
Conversation
|
It looks like this PR contains a migration 👀 General requirements
Schema changes
Data changes
|
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin:test:acceptance |
✅ Succeeded | 7m 38s | View ↗ |
nx run ghost:test:ci:integration |
✅ Succeeded | 2m 19s | View ↗ |
nx run ghost:test:integration |
✅ Succeeded | 2m 26s | View ↗ |
nx run-many -t test:unit -p @tryghost/admin-x-f... |
✅ Succeeded | 4m 45s | View ↗ |
nx run @tryghost/koenig-lexical:test:acceptance |
✅ Succeeded | 2m 32s | View ↗ |
nx run ghost-admin:test |
✅ Succeeded | 3m 8s | View ↗ |
nx run ghost:test:legacy |
✅ Succeeded | 2m 47s | View ↗ |
nx run ghost:test:e2e |
✅ Succeeded | 2m 7s | View ↗ |
Additional runs (8) |
✅ Succeeded | ... | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-08-10 18:31:47 UTC
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds machine-payment settings, feature flags, validation, Stripe and blockchain payment adapters, payment event storage, and admin controls. Paid Markdown requests can use payment challenges and fulfillment. LLMS discovery and Markdown alternate links now account for purchasable entries and machine-payment enablement. Tests and package metadata are updated. Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
🧹 Nitpick comments (6)
ghost/core/test/unit/server/services/machine-payments/adapters.test.js (1)
14-16: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAssert the approved contract address.
Line 15 accepts any hexadecimal value. A different valid address would pass the test and send payments to the wrong contract. Assert
TEMPO_USDCequals the approved Tempo USDC address. You can retain a 20-byte format assertion as an additional check.🤖 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/machine-payments/adapters.test.js` around lines 14 - 16, Update the test case for TEMPO_USDC to assert equality with the approved Tempo USDC contract address, rather than accepting any hexadecimal address; retain the existing 20-byte format validation only as an optional additional check.ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js (2)
72-83: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winLog the failure before you fall back.
The catch block discards the Stripe error. A credential problem, a permission problem, and a transient outage all look identical, and the code then issues a second Stripe call. Log the error at warning level.
🛠️ Proposed fix
+const logging = require('`@tryghost/logging`');} catch (err) { // Fall through to PaymentIntent deposit mode. + logging.warn(err); }🤖 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/core/server/services/machine-payments/stripe/deposit-address-store.js` around lines 72 - 83, Update the catch block in the deposit-address creation flow to log the caught Stripe error at warning level before falling through to PaymentIntent deposit mode. Preserve the existing fallback behavior and include enough context plus the original error details for diagnosis.
50-66: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy liftAddress creation stays on the request path and is only deduplicated per process.
getOrCreateAddressruns during a paid Markdown request, and#inflightdeduplicates within a single Node process only. Multiple Ghost instances that start with an empty cache each create an address. Stripe recommends keeping deposit-address creation off the core request path: "You can create deposit addresses as often as you want, but we recommend that you keep these calls off your core request path."Consider creating the address during boot or when an admin enables machine payments, and treat the request path as read-only.
🤖 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/core/server/services/machine-payments/stripe/deposit-address-store.js` around lines 50 - 66, Move address creation out of getOrCreateAddress and into a boot-time or machine-payments enablement initialization flow, ensuring it runs before paid Markdown requests need the address. Make getOrCreateAddress request-path read-only: return the cached machine_payments_deposit_address when present and otherwise fail with the established unavailable-state behavior instead of calling `#createAndPersist`. Preserve request-level inflight handling only if it remains necessary for reads.ghost/core/core/frontend/services/llms/service.js (1)
39-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the require to module scope.
isDiscoverableresolves../../../shared/machine-paymentson every entry. Hoist it next to the other imports unless a circular dependency forces the lazy require.🤖 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/core/frontend/services/llms/service.js` around lines 39 - 41, Move the machine-payments require from inside isDiscoverable to module scope alongside the other imports, then reuse that module-level binding within isDiscoverable. Only retain the lazy require if the change introduces a circular dependency.ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js (1)
19-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueAdd
stripePaymentIntentIdto the JSDoc.Line 38 reads
payment.stripePaymentIntentId, but the type on line 20 does not declare it.🤖 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/core/server/services/machine-payments/stripe/payment-recorder.js` around lines 19 - 21, Add the optional stripePaymentIntentId property to the payment parameter type in the JSDoc for the relevant payment-recording function, matching the property accessed via payment.stripePaymentIntentId while preserving the existing fields.ghost/core/test/unit/server/services/machine-payments/service.test.js (1)
126-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for the rejected-credential path.
#paymentCredentialErrorResponsemaps astatusCode === 403error to a 403 problem response and any other error to 503. No test exercises either branch. Add a case wheremppAdapter.fulfillrejects.🤖 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/machine-payments/service.test.js` around lines 126 - 138, Extend the machine-payments service tests around challengeOrFulfill with a rejected-credential case by making mppAdapter.fulfill reject with a 403-status error and asserting a 403 response, plus a non-403 rejection asserting 503. Reuse the existing service setup and request context from the “returns 503 when no adapter can challenge” test to cover both branches of `#paymentCredentialErrorResponse`.
🤖 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.
Inline comments:
In `@ghost/core/core/frontend/helpers/ghost_head.js`:
- Around line 28-33: Update isMachinePaymentsEnabled to reuse the shared
enablement logic from MachinePaymentsService.isEnabled or
settingsHelpers.isStripeConnected(), rather than checking for either Stripe
secret key directly. Preserve the existing labs and llms_enabled conditions
while ensuring the helper follows the same active-key-mode validation as the
service.
In
`@ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js`:
- Around line 5-14: The machine payment events table lacks an atomic idempotency
constraint. In the migration definition at
ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js
lines 5-14, add a composite unique constraint covering protocol and reference;
mirror the same constraint in the machine_payment_events schema declaration at
ghost/core/core/server/data/schema/schema.js lines 841-850.
In `@ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js`:
- Around line 47-64: Update MppAdapter.fulfill to require the expected
successful payment status before reading receipt fields or returning fulfillment
data; continue mapping status 402 to NoPermissionError, and reject every other
non-success status with an appropriate error. Only construct and return the
receipt-derived fulfillment after this validation.
In `@ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js`:
- Around line 52-60: The x402 adapter’s payment reference must be stable across
retries; update the return logic around paymentResponse to derive reference from
immutable settlement data such as the on-chain transaction hash, and remove the
Date.now fallback. If no stable settlement reference is available, fail
fulfillment rather than returning a fabricated reference, while preserving the
existing payment-response header behavior.
- Around line 95-100: Update the route registration in the x402 adapter so the
handler uses the HTTP method derived from terms.method, matching the route built
earlier, instead of always registering GET. Preserve the existing response body,
status, headers, and app.fetch(request) behavior for all supported methods.
- Around line 4-10: Update formatPrice to restrict x402 pricing to
USD-denominated terms, rather than emitting unsupported non-USD strings.
Validate the currency before formatting and reject or otherwise prevent non-USD
values from entering the Base/ExactEvmScheme rail; preserve the existing USD
output format.
- Around line 63-93: Refactor the x402 adapter to construct and cache the Hono
resource server, facilitator, and ExactEvmScheme setup once rather than
rebuilding them inside `#dispatch`. Reuse that server across challenge() and
fulfill() dispatches, while preserving per-request route, terms, and
deposit-address handling as required; update the nearby comment to describe the
reusable-server behavior and remove any outdated signature concern.
In
`@ghost/core/core/server/services/machine-payments/events/machine-payment-event-repository.js`:
- Around line 20-42: Update MachinePaymentEventRepository.save to handle
concurrent duplicate inserts: preserve the existing findOne fast path, wrap the
Model.add call in unique-constraint error handling, and on that specific failure
re-read and return the existing row using the same protocol/reference criteria.
Do not suppress unrelated persistence errors; if no unique constraint exists,
retain the current behavior and rely on the lookup result.
In
`@ghost/core/core/server/services/machine-payments/events/machine-payment-event.js`:
- Around line 17-23: Update MachinePaymentEvent.create to validate that data
includes non-missing postId, amount, currency, protocol, method, and reference
before constructing the event. Reject invalid input immediately, while
preserving the existing timestamp fallback and successful construction path.
In `@ghost/core/core/server/services/machine-payments/index.js`:
- Around line 46-48: Update the comment immediately above X402Adapter
registration in the adapter setup to accurately state that x402 is always
registered as a second payment rail, while agents that do not support it ignore
it; remove the incorrect facilitator-config condition.
In `@ghost/core/core/server/services/machine-payments/pricing.js`:
- Around line 19-27: Update getTerms() to default machine_payments_amount only
when the setting is null or undefined, preserving an explicit zero. After
resolving the amount, call assertValidAmount(amount) before returning the terms
so invalid or nonnumeric values cannot reach the payment adapter; keep the
existing currency resolution unchanged.
In `@ghost/core/core/server/services/machine-payments/service.js`:
- Around line 133-178: Update `#handleFulfill` to record the payment immediately
after adapter.fulfill succeeds and before loadFullEntry can return the 403
response. Capture the value returned by paymentRecorder.record and use it as the
Stripe PaymentIntent ID when building the eventRepository.save payload, falling
back only when no recorder result is available; preserve the existing warning
behavior for recording failures.
In
`@ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js`:
- Around line 88-101: The `#createViaPaymentIntent` method should cancel the
temporary PaymentIntent after extracting the deposit address, preventing an
unused requires_action record from persisting. Reuse the existing configured
amount and currency symbols instead of hardcoding them in the PaymentIntent
creation, while preserving the address extraction flow.
- Around line 1-7: Update the workspace Stripe dependency/catalog from 8.222.0
to a Stripe Node.js version that supports stripe.crypto.depositAddresses and the
2026-05-27.preview API, then retain the STRIPE_MACHINE_PAYMENTS_API_VERSION
usage in the deposit-address implementation.
In `@ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js`:
- Around line 31-48: Ensure non-USD terms are rejected consistently across both
payment paths: in
ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js:31-48,
update the payment-intent creation in the payment recorder to use
payment.currency or reject unsupported currencies before the call; in
ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js:4-10,
update formatPrice to produce only an x402-supported price format and refuse to
build challenges for unsupported currencies.
In `@ghost/core/test/unit/server/services/machine-payments/service.test.js`:
- Around line 17-19: Fix the labs stub setup in the relevant test fixture so
isSet itself is configured with withArgs('machinePayments').returns(true),
rather than assigning the filtered child stub; this must allow unexpected flag
names to return the default value. Add a test case covering the disabled
machinePayments flag and assert that isEnabled() returns false.
---
Nitpick comments:
In `@ghost/core/core/frontend/services/llms/service.js`:
- Around line 39-41: Move the machine-payments require from inside
isDiscoverable to module scope alongside the other imports, then reuse that
module-level binding within isDiscoverable. Only retain the lazy require if the
change introduces a circular dependency.
In
`@ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js`:
- Around line 72-83: Update the catch block in the deposit-address creation flow
to log the caught Stripe error at warning level before falling through to
PaymentIntent deposit mode. Preserve the existing fallback behavior and include
enough context plus the original error details for diagnosis.
- Around line 50-66: Move address creation out of getOrCreateAddress and into a
boot-time or machine-payments enablement initialization flow, ensuring it runs
before paid Markdown requests need the address. Make getOrCreateAddress
request-path read-only: return the cached machine_payments_deposit_address when
present and otherwise fail with the established unavailable-state behavior
instead of calling `#createAndPersist`. Preserve request-level inflight handling
only if it remains necessary for reads.
In `@ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js`:
- Around line 19-21: Add the optional stripePaymentIntentId property to the
payment parameter type in the JSDoc for the relevant payment-recording function,
matching the property accessed via payment.stripePaymentIntentId while
preserving the existing fields.
In `@ghost/core/test/unit/server/services/machine-payments/adapters.test.js`:
- Around line 14-16: Update the test case for TEMPO_USDC to assert equality with
the approved Tempo USDC contract address, rather than accepting any hexadecimal
address; retain the existing 20-byte format validation only as an optional
additional check.
In `@ghost/core/test/unit/server/services/machine-payments/service.test.js`:
- Around line 126-138: Extend the machine-payments service tests around
challengeOrFulfill with a rejected-credential case by making mppAdapter.fulfill
reject with a 403-status error and asserting a 403 response, plus a non-403
rejection asserting 503. Reuse the existing service setup and request context
from the “returns 503 when no adapter can challenge” test to cover both branches
of `#paymentCredentialErrorResponse`.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: c2632d91-bf1f-412e-be4b-923b901498ce
⛔ Files ignored due to path filters (2)
ghost/core/test/e2e-api/admin/__snapshots__/config.test.js.snapis excluded by!**/*.snappnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (47)
apps/admin-x-framework/src/api/settings.tsapps/admin-x-framework/src/test/msw-utils.tsapps/admin/src/settings/app/components/settings/advanced/labs/private-features.tsxapps/admin/src/settings/app/components/settings/membership/tiers.tsxapps/ember-admin/package.jsonghost/core/core/frontend/helpers/ghost_head.jsghost/core/core/frontend/services/llms/service.jsghost/core/core/frontend/services/routing/controllers/entry.tsghost/core/core/frontend/services/routing/controllers/entry/markdown.tsghost/core/core/frontend/web/site.jsghost/core/core/server/api/endpoints/utils/serializers/input/settings.jsghost/core/core/server/data/exporter/table-lists.jsghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-20-50-add-machine-payments-settings.jsghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.jsghost/core/core/server/data/schema/default-settings/default-settings.jsonghost/core/core/server/data/schema/schema.jsghost/core/core/server/models/index.jsghost/core/core/server/models/machine-payment-event.jsghost/core/core/server/models/settings.jsghost/core/core/server/services/machine-payments/README.mdghost/core/core/server/services/machine-payments/adapters/mpp-adapter.jsghost/core/core/server/services/machine-payments/adapters/x402-adapter.jsghost/core/core/server/services/machine-payments/content-loader.jsghost/core/core/server/services/machine-payments/eligibility.jsghost/core/core/server/services/machine-payments/events/machine-payment-event-repository.jsghost/core/core/server/services/machine-payments/events/machine-payment-event.jsghost/core/core/server/services/machine-payments/index.jsghost/core/core/server/services/machine-payments/pricing.jsghost/core/core/server/services/machine-payments/service.jsghost/core/core/server/services/machine-payments/stripe/deposit-address-store.jsghost/core/core/server/services/machine-payments/stripe/payment-recorder.jsghost/core/core/shared/config/defaults.jsonghost/core/core/shared/labs.jsghost/core/core/shared/machine-payments.jsghost/core/package.jsonghost/core/test/e2e-api/admin/settings.test.jsghost/core/test/integration/exporter/exporter.test.jsghost/core/test/legacy/models/model-settings.test.jsghost/core/test/unit/server/data/schema/integrity.test.jsghost/core/test/unit/server/services/machine-payments/adapters.test.jsghost/core/test/unit/server/services/machine-payments/content-loader.test.jsghost/core/test/unit/server/services/machine-payments/deposit-address-store.test.jsghost/core/test/unit/server/services/machine-payments/eligibility.test.jsghost/core/test/unit/server/services/machine-payments/service.test.jsghost/core/test/utils/fixtures/default-settings.jsonpackages/testing/test-data/src/fixtures/data/settings.tspnpm-workspace.yaml
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #29840 +/- ##
==========================================
- Coverage 75.45% 75.35% -0.11%
==========================================
Files 1605 1618 +13
Lines 140458 141794 +1336
Branches 17424 17565 +141
==========================================
+ Hits 105988 106849 +861
- Misses 33393 33864 +471
- Partials 1077 1081 +4
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:
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
ghost/core/test/unit/frontend/services/llms/service.test.js (1)
554-557: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAssert that the paid body remains absent.
The assertion proves that
Paid Postincludes the teaser and notice. It does not prove that the paid body is omitted. Add a negative assertion for a unique body marker from the paid fixture. Also assert that the excluded members-only body is absent when the fixture provides one.🤖 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/frontend/services/llms/service.test.js` around lines 554 - 557, Strengthen the assertions in the getLlmsFullTxt test by verifying that a unique marker from the paid post body is absent, while retaining the existing teaser and paywall notice checks. When the fixture includes an excluded members-only body marker, add a corresponding negative assertion for it as well.
🤖 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/frontend/services/llms/service.test.js`:
- Around line 554-557: Strengthen the assertions in the getLlmsFullTxt test by
verifying that a unique marker from the paid post body is absent, while
retaining the existing teaser and paywall notice checks. When the fixture
includes an excluded members-only body marker, add a corresponding negative
assertion for it as well.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 9cc2573e-79ae-4570-afb1-19b3b3065490
⛔ Files ignored due to path filters (2)
ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snapis excluded by!**/*.snapghost/core/test/unit/frontend/helpers/__snapshots__/ghost-head.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (6)
ghost/core/core/server/data/exporter/table-lists.jsghost/core/test/e2e-api/admin/settings.test.jsghost/core/test/integration/exporter/exporter.test.jsghost/core/test/legacy/models/model-settings.test.jsghost/core/test/unit/frontend/services/llms/service.test.jsghost/core/test/unit/server/data/exporter/index.test.js
🚧 Files skipped from review as they are similar to previous changes (4)
- ghost/core/core/server/data/exporter/table-lists.js
- ghost/core/test/legacy/models/model-settings.test.js
- ghost/core/test/e2e-api/admin/settings.test.js
- ghost/core/test/integration/exporter/exporter.test.js
There was a problem hiding this comment.
♻️ Duplicate comments (1)
ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js (1)
55-61: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winRequire a 2xx status before accepting payment fulfillment.
Both adapters accept 1xx and 3xx responses as successful fulfillment. These statuses do not confirm payment success. A response with a receipt header can then grant paid content without a successful payment result.
ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js#L55-L61: reject statuses below 200 and statuses from 300 onward.ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js#L42-L52: reject statuses below 200 and statuses from 300 onward.Proposed fix
- if (!payment.status || payment.status >= 400) { + if (!payment.status || payment.status < 200 || payment.status >= 300) {- if (response.status === 402 || response.status >= 400) { + if (response.status < 200 || response.status >= 300) {For the MPPX and `@x402` package versions declared by this repository, which HTTP response status ranges indicate a successfully fulfilled server-side payment?🤖 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/core/server/services/machine-payments/adapters/mpp-adapter.js` around lines 55 - 61, Require a 2xx payment status before accepting fulfillment: update the status validation in mpp-adapter.js lines 55-61 and x402-adapter.js lines 42-52 to reject statuses below 200 or at least 300, while preserving receipt handling for valid responses.
🤖 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.
Duplicate comments:
In `@ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js`:
- Around line 55-61: Require a 2xx payment status before accepting fulfillment:
update the status validation in mpp-adapter.js lines 55-61 and x402-adapter.js
lines 42-52 to reject statuses below 200 or at least 300, while preserving
receipt handling for valid responses.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 19bdb55f-869e-48c0-92f5-9c933333881f
📒 Files selected for processing (18)
ghost/core/core/frontend/helpers/ghost_head.jsghost/core/core/frontend/services/proxy.jsghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.jsghost/core/core/server/data/schema/schema.jsghost/core/core/server/services/machine-payments/adapters/mpp-adapter.jsghost/core/core/server/services/machine-payments/adapters/x402-adapter.jsghost/core/core/server/services/machine-payments/events/machine-payment-event-repository.jsghost/core/core/server/services/machine-payments/events/machine-payment-event.jsghost/core/core/server/services/machine-payments/index.jsghost/core/core/server/services/machine-payments/pricing.jsghost/core/core/server/services/machine-payments/service.jsghost/core/core/server/services/machine-payments/stripe/deposit-address-store.jsghost/core/core/server/services/machine-payments/stripe/payment-recorder.jsghost/core/core/shared/machine-payments.jsghost/core/test/unit/server/data/schema/integrity.test.jsghost/core/test/unit/server/services/machine-payments/adapters.test.jsghost/core/test/unit/server/services/machine-payments/deposit-address-store.test.jsghost/core/test/unit/server/services/machine-payments/service.test.js
🚧 Files skipped from review as they are similar to previous changes (11)
- ghost/core/core/frontend/helpers/ghost_head.js
- ghost/core/test/unit/server/data/schema/integrity.test.js
- ghost/core/core/server/data/schema/schema.js
- ghost/core/test/unit/server/services/machine-payments/adapters.test.js
- ghost/core/core/server/services/machine-payments/events/machine-payment-event.js
- ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js
- ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js
- ghost/core/test/unit/server/services/machine-payments/service.test.js
- ghost/core/core/server/services/machine-payments/index.js
- ghost/core/test/unit/server/services/machine-payments/deposit-address-store.test.js
- ghost/core/core/server/services/machine-payments/pricing.js
ref #29840 - SettingValue now includes number for machine_payments_amount, which broke a narrow type in use-member-views - Core settings allowlist needed the new secret/deposit-address keys Co-authored-by: Cursor <cursoragent@cursor.com>
ref #29840 - Codecov e2e-tests only counts server e2e/integration hits, so unpaid markdown/controller branches were dragging project coverage below the 0.2% threshold Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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.
Inline comments:
In `@ghost/core/test/e2e-frontend/machine-payments-markdown.test.js`:
- Around line 82-151: Extend the payment e2e coverage around the existing
markdown tests to verify that HTML permalinks and relevant Content API routes
reject a Payment Authorization header for gated posts, rather than invoking
machine-payment orchestration. Add cases for explicit HTML requests and Accept:
text/markdown requests, covering the applicable API endpoints while preserving
the existing gated-content status and response assertions.
In
`@ghost/core/test/integration/services/machine-payments/content-loader.test.js`:
- Around line 57-70: Extend the idempotency test around the existing payload and
repository.save calls to save a second payload with the same reference but
protocol set to x402, then assert its event ID differs from first.id. Preserve
the existing assertion that replaying the identical mpp payload returns the same
ID.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 1e6b750e-d117-43cb-b097-7b45a27dd30d
⛔ Files ignored due to path filters (1)
ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snapis excluded by!**/*.snap
📒 Files selected for processing (4)
ghost/core/test/e2e-api/admin/settings.test.jsghost/core/test/e2e-frontend/llms-routes.test.jsghost/core/test/e2e-frontend/machine-payments-markdown.test.jsghost/core/test/integration/services/machine-payments/content-loader.test.js
|
Reference PR: #28291 |
ref #29840 - CodeRabbit: Payment credentials must not unlock HTML, Accept-markdown, or Content API for gated posts - Composite protocol+reference uniqueness and llms discoverability branches need e2e/integration hits for Codecov Co-authored-by: Cursor <cursoragent@cursor.com>
ref #29840 - The temporary paid post was left in the DB and shifted later browse snapshots (total 17 → 18) Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
9larsons
left a comment
There was a problem hiding this comment.
Sent you suggestions separately.

Summary
Adds pay-per-request Stripe Machine Payments for paid-members markdown (
.md) URLs, so AI agents can unlock premium content without creating a member session.canHandle/challenge/fulfillboundarycontent-gatingchanges.mdURLs only — Accept-header markdown, HTML theme views, and the Content API stay membership-gatedmachinePayments+llms_enabled+machine_payments_enabled+ Stripe connected/llms.txtand get markdown alternate links when enabledmachine_payment_eventsledger after successful fulfillThis supersedes the earlier exploration in #28291 with a Ghost-shaped architecture and product fences documented in
ghost/core/core/server/services/machine-payments/README.md.Test plan
machine_payments_*settings andmachine_payment_eventstable existcurl -i http://localhost:2368/<slug>.mdreturns 402 with a Payment challenge (not 403)/llms.txtwhen enabled, and free-members-only posts do not.mdstill returns 200; Accept-header markdown on paid HTML URLs is unchangednpx mppx@latest validate http://localhost:2368/<slug>.md(Stripe sandbox / testnet config)cd ghost/core && pnpm run test:single test/unit/server/services/machine-payments/