Ghost 的 Machine Payments:面向 AI Agent 的按次付费 Markdown 访问机制详解
AI Agent 在抓取网页时会受到内容会员门槛的阻隔,而传统订阅制又无法覆盖"单个 Agent 单次请求一小块内容"的场景。Ghost(本仓库 README)在核心服务中引入了 Machine Payments 服务:通过 Stripe 的 Machine Payments 能力,允许 Agent 对已付费会员专属的 .md(Markdown)URL 做逐请求(pay-per-request)付费解锁。本文以该 README 为主体,结合 service.ts 等源码与配置实现,讲解这一功能的协议选择、产品边界、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(暴露面)
只暴露显式的 .md URL。规范化 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_amount 与 machine_payments_currency 决定,币种缺省时回退到活动付费 tier 的货币(见 getDefaultTiersCurrency)。forSpt/forTempoUsdc 两个方法把最小单位金额换算为 majorAmount(amount / 100)供各自通道使用,而 assertValidAmount 要求金额必须是大于 0 的安全整数。
Eligibility(内容准入)
只有满足下述条件的内容可售:
visibility: paid;或visibility: tiers且所有关联 tier 均为付费 tier。
仅限免费会员(visibility: members)的内容不在范围内。
这一规则对应共享模块 ghost/core/core/shared/machine-payments.ts 中的 isPurchasableEntry:visibility === 'paid' 直接放行;visibility === 'tiers' 时需要非空的 tiers 数组且每个 tier 的 type 均为 paid。服务在 service.ts 的 isPurchasable() 中把启用检查与条目准入合并返回。
Enablement(功能开关)
Machine Payments 只有在以下条件同时满足时才生效:
- Labs 实验开关
machinePayments开启; llms_enabled保持开启(Agent 发现与.md路由依赖它);- 设置项
machine_payments_enabled为true; - Stripe 已连接。
完整判断见 isMachinePaymentsEnabled:labs.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 }):网络为 tempo 或 base(见 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/*模块请出进程)。network:eip155:8453(Base 主网)或eip155:84532(Base Sepolia 测试网)。源码校验其必须是 CAIP-2 形式的 EVM 网络(eip155:<chainId>),且只允许上述两个值。stripeNetwork:base。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 在金额/币种之外携带 description、method、mimeType(默认 text/markdown)与 url;Fulfillment 携带结算后的 method、reference(稳定结算引用,由 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 是主入口,处理顺序如下:
isEnabled()失败 → 404payment-unavailable(problem+json)。- 无可用适配器 → 503
payment-unavailable。 - 通过
ContentLoader.isPurchasable()做原始模型级别的准入检查(不依赖 Content API 序列化,避免其剥离免费 tier 导致混合内容的错误 402/403)——不可售 → 403payment-forbidden。 - 计算支付条款(
getTerms→Pricing)。 - 找出能处理该凭据的适配器(
canHandle);命中则走#handleFulfill,否则对所有适配器并行challenge(Promise.allSettled),把各自返回的 challenge 响应合并为 402 响应(保留每个WWW-Authenticate头,保证多协议可同时协商)。
先验证、再结算、后加载的内容交付路径
#handleFulfill(service.ts#L203-L292)刻意设计了"付费不可逆、交付必可达"的顺序:
- 先调用
ContentLoader.loadFullEntry加载完整帖子/页面(含作者、标签、tiers),确认可交付后再结算,避免"先扣 Agent 的钱、加载却失败"; - 再执行
adapter.fulfill,凭据被拒(403)→ 403payment-forbidden; - 随后写入账本:
machine_payment_events仓库保存{postId, amount, currency, protocol, method, stripePaymentIntentId, reference}。由于 Stripe 幂等键约 24h 过期,事件仓库的"协议 + reference"持久检查成为重放请求上创建 PaymentIntent 的闸门;若事件已存在(created === false)→ 403"凭据已使用";仓库写入失败 → 503; PaymentRecorder把结算同步到 Stripe 记录;- 最终返回 200,
Content-Type: text/markdown; charset=utf-8、Cache-Control: private, no-store(PAID_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_id 或 machinePayments: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。 - 开关与依赖:Labs
machinePayments+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、共享准入逻辑)作为第一手依据,按本仓库当前实现进行验证与排障。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00