Ghost 的 Machine Payments:面向 AI Agent 的按次付费 Markdown 访问机制详解
2026/9/8 19:28:09 网站建设 项目流程

Ghost 的 Machine Payments:面向 AI Agent 的按次付费 Markdown 访问机制详解

【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost

AI Agent 在抓取网页时会受到内容会员门槛的阻隔,而传统订阅制又无法覆盖"单个 Agent 单次请求一小块内容"的场景。Ghost(本仓库 README)在核心服务中引入了Machine Payments服务:通过 Stripe 的 Machine Payments 等源码与配置实现,讲解这一功能的协议选择、产品边界、x402 配置方式、发布者前置条件,以及适配器驱动的内部架构,帮助你理解如何在 Ghost 站点上安全开放"机器可付费"内容。

Machine Payments 要解决什么问题

在默认情况下,Ghost 的 HTML 主题视图与 Content API 都受会员门槛保护。当 AI Agent(爬虫、LLM 数据采集器)请求一篇付费文章时,只能收到 402/403 一类拒绝信号,站点也拿不到任何收益。Machine Payments 的目标是:在不动摇会员体系与内容门槛的前提下,把"单篇付费文章的纯 Markdown 正文"作为可售卖资源,开放给符合协议的机器客户端。

值得注意的是,正文明确指出该能力对接的是 Stripe Machine Payments(Machine Payments Protocol,MPP),代码实现位于ghost/core/core/server/services/machine-payments/目录,内部按适配器模式拆分为 mpp-adapter.ts 与 x402-adapter.ts 两个支付通道。

v1 产品围栏(Product Fences)

README 用一组"产品围栏"精确划定了 v1 的能力边界,理解它们是配置与排查的前提。

Protocol(协议栈)

  • 主协议为MPP(Machine Payments Protocol),支持Tempo USDC稳定币与Shared Payment Tokens(SPT)(卡 / Link Agent Wallet)两类通道。
  • x402(Base 上的 USDC,经由 ExactEvmScheme)作为第二个适配器挂在同一"支付授权边界"之后;不识别该协议的 Agent 直接忽略即可。
  • 两条通道都不会改变会员状态

Access model(访问模型)

单次请求的一次性解锁:只为这一次请求返回 Markdown 字节。不产生会员会话、不授予层级(tier)、不影响content-gating与 Portal。

Surface(暴露面)

只暴露显式的.mdURL。规范化 HTML URL 永远返回 HTML(忽略 Accept 头);HTML 主题视图与 Content API 依旧保持会员门槛。

Pricing(定价)

全站统一金额。SPT 通道按配置的法币(fiat)计费(需遵守 Stripe 卡片最小金额);Tempo 通道以同一最小货币单位金额收取 USDC。对发布者而言,crypto 通道应视为"USDC",而不是链上自有货币。

从源码可见,定价逻辑在 pricing.ts:默认金额DEFAULT_AMOUNT = 100(最小单位)、默认币种DEFAULT_CURRENCY = 'USD',实际值由设置项machine_payments_amountmachine_payments_currency决定,币种缺省时回退到活动付费 tier 的货币(见getDefaultTiersCurrency)。forSpt/forTempoUsdc两个方法把最小单位金额换算为majorAmountamount / 100)供各自通道使用,而assertValidAmount要求金额必须是大于 0 的安全整数。

Eligibility(内容准入)

只有满足下述条件的内容可售:

  • visibility: paid;或
  • visibility: tiers所有关联 tier 均为付费 tier。

仅限免费会员(visibility: members)的内容不在范围内。

这一规则对应共享模块 ghost/core/core/shared/machine-payments.ts 中的isPurchasableEntryvisibility === 'paid'直接放行;visibility === 'tiers'时需要非空的 tiers 数组且每个 tier 的type均为paid。服务在 service.ts 的isPurchasable()中把启用检查与条目准入合并返回。

Enablement(功能开关)

Machine Payments 只有在以下条件同时满足时才生效:

  • Labs 实验开关machinePayments开启;
  • llms_enabled保持开启(Agent 发现与.md路由依赖它);
  • 设置项machine_payments_enabledtrue
  • Stripe 已连接。

完整判断见isMachinePaymentsEnabledlabs.isSet('machinePayments') && settingsCache.get('machine_payments_enabled') === true && settingsCache.get('llms_enabled') !== false && isStripeConnected()MachinePaymentsService.isEnabled()在每次请求处理入口都会调用它。

发布者前置条件(Publisher Prerequisites)

要真正接受机器支付,站点需要:

  • 在站点上配置Stripe Connect(或直连密钥)。
  • 若走SPT / 卡通道:发布者需为美国或加拿大法律实体,并配置 Stripe 商业档案(networkId/ profile id)。
  • 若走Tempo 稳定币通道:需在 Stripe 中获批 Stablecoins and Crypto 支付方式。纽约州企业不可用,其他地区可能需要 Stripe 开启访问权限。
  • 若走x402(Base USDC)通道:同样需要获批 Stablecoins and Crypto 支付方式(Base 存款地址与其他 crypto 通道一致)。
  • llms.txt必须保持开启——Agent 发现内容与.md路由都依赖它。

从适配器实现看,SPT/Tempo 的资金接收依赖 deposit-address-store.ts 的getOrCreateAddress({ network }):网络为tempobase(见 mpp-adapter.ts 的config.get('machinePayments:mpp:stripeNetwork'))。Stripe 客户端选项集中在 stripe-client-options.ts,而 mpp 适配器使用STRIPE_MACHINE_PAYMENTS_API_VERSION对应的 API 版本构造客户端。

x402 配置说明

x402 通道的默认值瞄准Base 主网eip155:8453),通过公共 xpay facilitator 完成真实 USDC 结算——无需账号或 API Key。可通过machinePayments.x402.facilitatorUrl覆盖为其他提供方,例如 Coinbase CDP(具备托管合规筛查能力,但需要 API Keys;Ghost 目前尚未接入)。

启动时校验的配置项

README 列出的可取值在服务启动时就会被校验(x402-adapter.ts 中init()前的配置解析即为此逻辑):

  • enabled:默认true——只要 Machine Payments 开启,x402 通道就随之生效。设为false可在 MPP 保持开启的同时关掉 x402 通道(并把@x402/*模块请出进程)。
  • networkeip155:8453(Base 主网)或eip155:84532(Base Sepolia 测试网)。源码校验其必须是 CAIP-2 形式的 EVM 网络(eip155:<chainId>),且只允许上述两个值。
  • stripeNetworkbase
  • facilitatorUrl:HTTPS URL;主网不能使用 x402.org 的 testnet facilitator(源码会校验 URL 必须为 HTTPS,并在 Base 主网上拒绝 testnet facilitator 地址)。

需要留意的是,@x402/*运行时模块是懒加载的——只在第一次真实 x402 challenge 时载入,而不是启动时。这意味着从未收到 x402 支付的站点永远不会承担其 import 成本,运行时切换 Machine Payments 开关也无需重启;而一旦配置无效,x402 通道会在启动时被禁用(MPP 仍正常工作)。

本地开发:对接 x402.org 测试网

config.local.json中覆盖为 Base Sepolia + testnet facilitator:

{ "machinePayments": { "x402": { "network": "eip155:84532", "facilitatorUrl": "https://x402.org/facilitator" } } }

生产环境:替换主网 facilitator

{ "machinePayments": { "x402": { "facilitatorUrl": "https://your-mainnet-facilitator.example/facilitator" } } }

故障排查

如果在 MPP 正常工作时,402 响应中却看不到 x402 challenge,请检查 Ghost 日志里的 x402 警告——网络或 facilitator 不匹配是最常见原因。这与上述校验逻辑呼应:Base 主网(eip155:8453)搭配公共测试网 facilitator、或 network 值拼错,都会在启动/初始化时被标记为无效配置,从而静默关闭 x402 通道,只保留 MPP。

架构边界与"协议无关"编排器

README 的收尾部分交代了最重要的架构原则:会员体系与内容门槛保持冻结(frozen)

  • 适配器只需实现canHandle/challenge/fulfill三个方法。
  • 编排器只在一次成功的fulfill之后,才加载完整帖子 HTML 并写入machine_payment_events

这套边界在源码中有非常清晰的落地。目录入口 index.js 组装MppAdapter(MPP)与可选的X402Adapter,并注入内容加载器、事件仓库、支付记录器与 Stripe 连接状态;仅在服务启用时才在请求路径之外预生成 Tempo/Base 存款地址(符合 Stripe 指引,失败只退化为"仅 SPT"challenge)。

统一的 PaymentAdapter 契约

核心契约定义在 types.ts:

export type PaymentAdapter = { name?: string; canHandle: (request: Request) => boolean; challenge: (request: Request, terms: PaymentTerms) => Promise<Response | null | undefined>; fulfill: (request: Request, terms: PaymentTerms) => Promise<Fulfillment>; };

PaymentTerms在金额/币种之外携带descriptionmethodmimeType(默认text/markdown)与urlFulfillment携带结算后的methodreference(稳定结算引用,由MachinePaymentEvent.create()强制要求)、可选的protocol/amount/currency/stripePaymentIntentId/receiptHeaders

以 MPP 适配器为例:canHandle通过检查Authorization头是否以Payment开头来识别携带机器支付凭据的请求;challenge内部执行tempo.charge(USDC,TEMPO_USDC合约、6 位小数)与stripe.charge(SPT,卡/Link,2 位小数),两者都有则用compose同时发起;fulfill成功后解析Payment-Receipt头(base64url 编码的{method, reference, status, timestamp}JSON,见parseReceipt),把reference作为幂等键返回,并在method === 'stripe'时把引用记为stripePaymentIntentId

编排器的请求处理流程

MachinePaymentsService.challengeOrFulfill 是主入口,处理顺序如下:

  1. isEnabled()失败 → 404payment-unavailable(problem+json)。
  2. 无可用适配器 → 503payment-unavailable
  3. 通过ContentLoader.isPurchasable()原始模型级别的准入检查(不依赖 Content API 序列化,避免其剥离免费 tier 导致混合内容的错误 402/403)——不可售 → 403payment-forbidden
  4. 计算支付条款(getTermsPricing)。
  5. 找出能处理该凭据的适配器(canHandle);命中则走#handleFulfill,否则对所有适配器并行challengePromise.allSettled),把各自返回的 challenge 响应合并为 402 响应(保留每个WWW-Authenticate头,保证多协议可同时协商)。

先验证、再结算、后加载的内容交付路径

#handleFulfill(service.ts#L203-L292)刻意设计了"付费不可逆、交付必可达"的顺序:

  1. 先调用ContentLoader.loadFullEntry加载完整帖子/页面(含作者、标签、tiers),确认可交付后再结算,避免"先扣 Agent 的钱、加载却失败";
  2. 再执行adapter.fulfill,凭据被拒(403)→ 403payment-forbidden
  3. 随后写入账本:machine_payment_events仓库保存{postId, amount, currency, protocol, method, stripePaymentIntentId, reference}。由于 Stripe 幂等键约 24h 过期,事件仓库的"协议 + reference"持久检查成为重放请求上创建 PaymentIntent 的闸门;若事件已存在(created === false)→ 403"凭据已使用";仓库写入失败 → 503;
  4. PaymentRecorder把结算同步到 Stripe 记录;
  5. 最终返回 200,Content-Type: text/markdown; charset=utf-8Cache-Control: private, no-storePAID_MARKDOWN_CACHE_CONTROL)、Content-Location,并附上适配器返回的收据头。

内容加载器 content-loader.ts 是"特权解锁路径":它直接基于模型查询(仅published的 post/page),有意绕过 Content API 的会员门槛——因为机器支付解锁的是单次 Markdown 字节交付,而不是授予会员身份。同时它包含 URL 可交付性门禁:当解析出的绝对 URL 为空或以/404/结尾时,判定为不可售,杜绝"无法送达却发起 challenge/计费"。

运行时装配与事件模型

服务装配见 index.js:默认adapters = [new MppAdapter(...)],若X402Adapter.init()成功(配置有效)则追加 x402 适配器;MachinePaymentEventRepository与 machine-payment-event.ts 负责事件持久化。事件通过设置缓存读取machine_payments_amount/machine_payments_currency、读取 Stripe profile(machine_payments_stripe_profile_idmachinePayments:mpp:networkId)以及machine_payments_secret/machinePayments:mpp:secretKey完成完整计费闭环。

小结与适用边界

  • 能力边界:仅限显式.mdURL 的一次性解锁,只针对paid/全付费 tier 内容;HTML 页面与 Content API 依旧保持会员门槛;支付不改变会员状态、不触碰 Portal。
  • 通道选择:MPP(Tempo USDC + SPT 卡/Link)与 x402(Base USDC)并存,均实现同一canHandle/challenge/fulfill契约;x402 可用machinePayments.x402配置独立开/关与切换网络、facilitator。
  • 开关与依赖:LabsmachinePayments+llms_enabled+machine_payments_enabled+ Stripe 已连接,四者缺一不可;付费 tier 的货币与全站machine_payments_currency决定计费币种。
  • 安全与一致:先验可交付、再fulfill结算、后写machine_payment_events账本、用"协议 + reference"防重放,返回内容一律private, no-store

若你在自建 Ghost 站点上为 AI 内容消费开启按次计费,可将以上配置与源码路径(README、service.ts、pricing.ts、共享准入逻辑)作为第一手依据,按本仓库当前实现进行验证与排障。

【免费下载链接】GhostIndependent technology for modern publishing, memberships, subscriptions and newsletters.项目地址: https://gitcode.com/GitHub_Trending/gh/Ghost

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询