@emdash-cms/x402 为部署在 Cloudflare 上的任意 Astro 站点增加 x402 支付协议 支持。可独立使用,不依赖 EmDash 核心;与 EmDash 的 CMS 字段配合,便于按页面定价。
x402 是原生基于 HTTP 的支付协议。客户端在未付款的情况下请求付费资源时,服务器返回 402 Payment Required 及机器可读的支付说明。支持 x402 的代理与浏览器可自动完成支付并重试请求。
适用场景
最常见的是 仅机器人付费:对 AI 代理与爬虫收取内容访问费用,人类访客免费阅读。借助 Cloudflare Bot Management 区分人与机器人。
也可要求所有访客付费,或仅检测支付请求头而不强制拦截(条件渲染)。
安装
pnpm
pnpm add @emdash-cms/x402 npm
npm install @emdash-cms/x402 yarn
yarn add @emdash-cms/x402 配置
在 Astro 配置中加入该集成:
import { defineConfig } from "astro/config";
import { x402 } from "@emdash-cms/x402";
export default defineConfig({
integrations: [
x402({
payTo: "0xYourWalletAddress",
network: "eip155:8453", // Base 主网
defaultPrice: "$0.01",
botOnly: true,
botScoreThreshold: 30,
}),
],
});
添加类型引用,使 TypeScript 识别 Astro.locals.x402:
/// <reference types="@emdash-cms/x402/locals" />
基本用法
集成会在 Astro.locals.x402 上放置执行器。在页面 frontmatter 中调用 enforce(),将内容置于支付门槛之后:
---
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: "$0.05",
description: "付费文章",
});
// 若无有效支付,enforce() 返回 402 Response。
// 直接返回即可向客户端下发支付说明。
if (result instanceof Response) return result;
// 已验证支付(或在 botOnly 下已跳过)。通过响应头传递结算凭证。
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>付费内容</h1>
</article>
enforce() 的返回值要么是:
Response(402)— 需要付款,直接返回;EnforceResult— 应继续处理请求:已付款,或在仅机器人模式下对人类访客跳过校验。
仅机器人模式
当 botOnly 为 true 时,集成会读取 request.cf.botManagement.score:
- 分数低于阈值(默认 30)→ 视为机器人,强制付费;
- 分数达到或高于阈值 → 视为人类,跳过强制;
- 无机器人管理数据(本地开发、非 CF 部署)→ 视为人类。
EnforceResult 中的 skipped 可区分「无需付费」与「已付费」:
---
const result = await x402.enforce(Astro.request, { price: "$0.01" });
if (result instanceof Response) return result;
x402.applyHeaders(result, Astro.response);
// result.paid — 已验证支付时为 true
// result.skipped — 在 botOnly 下因人类而跳过时为 true
// result.payer — 付款人钱包地址(若已付)
---
与 EmDash 按页定价
使用 EmDash 时,可在集合中添加 number 字段表示页面价格,无需特殊 schema,普通 CMS 字段即可:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry } = await getEmDashEntry("posts", slug);
if (!entry) return Astro.redirect("/404");
const { x402 } = Astro.locals;
const result = await x402.enforce(Astro.request, {
price: entry.data.price || "$0.01",
description: entry.data.title,
});
if (result instanceof Response) return result;
x402.applyHeaders(result, Astro.response);
---
<article>
<h1>{entry.data.title}</h1>
</article>
不强制,仅检测是否带支付头
使用 hasPayment() 可检查请求是否包含支付相关头,而不验证或强制。适合条件渲染 — 向已付与未付访客展示不同内容:
---
const { x402 } = Astro.locals;
const hasPaid = x402.hasPayment(Astro.request);
---
{hasPaid ? (
<p>此处为完整付费内容。</p>
) : (
<p>订阅后可阅读全文。</p>
)}
配置项说明
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
payTo | string | 必填 | 收款钱包地址 |
network | string | 必填 | CAIP-2 网络标识(如 eip155:8453) |
defaultPrice | Price | — | 默认价格,可按页覆盖 |
facilitatorUrl | string | https://x402.org/facilitator | 支付协调服务 URL |
scheme | string | "exact" | 支付方案 |
maxTimeoutSeconds | number | 60 | 支付签名最长有效时间(秒) |
evm | boolean | true | 启用 EVM 链 |
svm | boolean | false | 启用 Solana(需 @x402/svm) |
botOnly | boolean | false | 仅对机器人强制付费 |
botScoreThreshold | number | 30 | 机器人分数阈值(1–99,越低越倾向判定为机器人) |
价格写法
价格可采用多种形式:
- 美元字符串 —
"$0.10"(会去掉$前缀,数值原样传递) - 数字字符串 —
"0.10" - 数值 —
0.10 - 对象 —
{ amount: "100000", asset: "0x...", extra: {} }显式指定资产与金额
网络标识
网络使用 CAIP-2 格式:
| 网络 | 标识符 |
|---|---|
| Base 主网 | eip155:8453 |
| Base Sepolia | eip155:84532 |
| 以太坊 | eip155:1 |
| Solana | solana:mainnet |
enforce 选项
针对单页覆盖全局配置:
await x402.enforce(Astro.request, {
price: "$0.25",
payTo: "0xDifferentWallet",
network: "eip155:1",
description: "文章:x402 如何工作",
mimeType: "text/html",
});
Solana 支持
Solana 为可选。安装 @x402/svm 并在配置中启用:
pnpm add @x402/svm
x402({
payTo: "YourSolanaAddress",
network: "solana:mainnet",
svm: true,
evm: false,
});
工作原理
x402()集成注册中间件,创建执行器并挂到Astro.locals.x402。- 配置通过 Vite 虚拟模块(
virtual:x402/config)传入中间件。 - 调用
enforce()时检查请求上的payment-signature头。 - 若无支付头,返回
402 Payment Required,并在PAYMENT-REQUIRED头中附带说明。 - 若有支付头,通过协调服务验证并完成结算。
- 结算后,
applyHeaders()在响应上设置PAYMENT-RESPONSE相关头。
资源服务器在首次请求时惰性初始化,并在 Worker 生命周期内缓存。