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

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

2026-09-07 20:06:49作者:齐添朝

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.tsx402-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_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.tsisPurchasable() 中把启用检查与条目准入合并返回。

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.tsgetOrCreateAddress({ network }):网络为 tempobase(见 mpp-adapter.tsconfig.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.tsinit() 前的配置解析即为此逻辑):

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

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

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

  1. 先调用 ContentLoader.loadFullEntry 加载完整帖子/页面(含作者、标签、tiers),确认可交付后再结算,避免"先扣 Agent 的钱、加载却失败";
  2. 再执行 adapter.fulfill,凭据被拒(403)→ 403 payment-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 适配器;MachinePaymentEventRepositorymachine-payment-event.ts 负责事件持久化。事件通过设置缓存读取 machine_payments_amount/machine_payments_currency、读取 Stripe profile(machine_payments_stripe_profile_idmachinePayments:mpp:networkId)以及 machine_payments_secret/machinePayments:mpp:secretKey 完成完整计费闭环。

小结与适用边界

  • 能力边界:仅限显式 .md URL 的一次性解锁,只针对 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 内容消费开启按次计费,可将以上配置与源码路径(READMEservice.tspricing.ts共享准入逻辑)作为第一手依据,按本仓库当前实现进行验证与排障。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388