支付方式

单次支付#

HTTP 卖家 重点看:业务流程 → HTTP 卖家接入(scheme 选型 → exact 路径 / charge 路径 → syncSettle 决策)→ 进阶(分账、同时支持 exact + charge

Agent 卖家 重点看:业务流程 → Agent 卖家接入(付款链接生成与分发)

定义、底层协议详见 核心概念 · 单次支付。本页聚焦接入


适用场景#

你的业务是否适合
单笔金额固定明确(一篇报告 / 一次推理 / 一次查询)
调用前价格已确定,调用后无后续消费
资源不可撤回(如文件下载、报告生成)✅(推荐 syncSettle: true
单次价格极低 + 调用频率极高⚠️ 改用 批量支付
单次消费量算不准⚠️ 改用 按量支付

业务流程#

下图是抽象层流程——HTTP 卖家是"客户端请求触发",Agent 卖家是"Agent 在对话里主动生成付款链接触发",但Challenge / Credential 消息语义两者一致。具体载体差异见下文卖家接入。

KYT(链上风险审查)是 Broker Verify 阶段内部的合规检查,不是独立服务。


HTTP 卖家接入#

exact 还是 charge#

exact 单收款方,同步 / 异步结算都支持。

charge 除了单收款方,还支持分账(Split),即单次支付多地址收款(≤10),默认且只支持同步结算。

维度exactcharge
收款方单收款方单收款方 / 单次支付多地址收款(≤10)
结算时序同步 / 异步均可默认且只支持同步

不确定走哪个?用 同时支持 exact + charge 同时挂上,让买家自己挑。

SDK 状态#

SchemeNode.jsRustGoJava
exact
charge💡

✅ 已上线 · 💡 即将推出

exact 路径#

每个 Tab 包含完整的安装命令 + 实现代码。架构由 4 个组件组合:Facilitator 客户端(带 OKX API Key)→ Resource Server(注册 scheme)→ Routes 配置accepts 数组)→ 中间件挂载

bash
npm install express @okxweb3/x402-express @okxweb3/x402-core @okxweb3/x402-evm
npm install -D typescript tsx @types/express @types/node
typescript
import express from "express";
import {
  paymentMiddleware,
  x402ResourceServer,
} from "@okxweb3/x402-express";
import { ExactEvmScheme } from "@okxweb3/x402-evm/exact/server";
import { OKXFacilitatorClient } from "@okxweb3/x402-core";

const app = express();
const NETWORK = "eip155:196";
const PAY_TO = process.env.PAY_TO_ADDRESS || "0xYourSellerWallet";

const facilitatorClient = new OKXFacilitatorClient({
  apiKey: "OKX_API_KEY",
  secretKey: "OKX_SECRET_KEY",
  passphrase: "OKX_PASSPHRASE",
});

const resourceServer = new x402ResourceServer(facilitatorClient);
resourceServer.register(NETWORK, new ExactEvmScheme());

app.use(
  paymentMiddleware(
    {
      "GET /api/premium": {
        accepts: [
          {
            scheme: "exact",
            network: NETWORK,
            payTo: PAY_TO,
            price: "$0.10",
            syncSettle: true,            // 同步结算:等链上确认
          },
        ],
        description: "Premium API",
        mimeType: "application/json",
      },
    },
    resourceServer,
  ),
);

app.get("/api/premium", (_req, res) => {
  res.json({ data: "付费资源内容" });
});

app.listen(4000, () => {
  console.log("[Seller] listening at http://localhost:4000");
});

多 scheme 声明都通过 accepts: [...] 数组完成;要追加 aggr_deferred(批量支付)时,在数组里再加一项即可,详见 批量支付

同步结算 vs 异步结算#

调用 /settle 后,Facilitator 将交易提交到链上。路由配置中的 syncSettle 字段控制结算行为:

模式参数值Facilitator 行为适用场景
同步true提交交易并等待链上确认后返回 txHash高价值交易,需确认到账后再交付资源
异步false提交交易后立即返回 txHash,不等确认中等价值,对响应速度有要求

异步结算存在时序风险:资源可能在链上支付最终确认前就已交付。高价值交易建议使用同步结算。charge scheme 默认且只支持同步。

charge 路径#

SDK 状态
Rust / Node.js / Go SDK 已上线;Java SDK 即将推出。

charge 不走 x402 的 accepts 数组,而是用一个 ChargeConfig 类型 + MppCharge<T> extractor 描述每个路由的价格——价格作为类型的关联函数返回,编译期就锁定,不会被路由配置层覆写。

package.json:

json
{
  "type": "module",
  "dependencies": {
    "@okxweb3/mpp": "^0.1.0"
  }
}
typescript
// server.ts
// 启动: node --env-file=.env --experimental-strip-types server.ts
// 或者:  npx tsx --env-file=.env server.ts
import * as http from "node:http";
import { Mppx } from "@okxweb3/mpp";
import { charge } from "@okxweb3/mpp/evm/server";
import { SaApiClient } from "@okxweb3/mpp/evm";

// SA-API client (broadcasts EIP-3009 in transaction mode).
const saClient = new SaApiClient({
  apiKey: process.env.OKX_API_KEY!,
  secretKey: process.env.OKX_SECRET_KEY!,
  passphrase: process.env.OKX_PASSPHRASE!,
});

const mppx = Mppx.create({
  methods: [charge({ saClient })],
  realm: "test realm",
  secretKey: process.env.MPP_SECRET_KEY!,
});

// Per-route price (base units; "100" = 0.0001 of a 6-decimal token).
// fee_payer = true → seller broadcasts the EIP-3009 (transaction mode).
const CHARGE = {
  amount: "100",
  currency: "0x...adb21711",                 // currency
  recipient: "0x...378211",                  // receipt
  description: "One premium API call",
  methodDetails: { chainId: 196, feePayer: true },  // X Layer
} as const;

// Runs only after verify + settle.
async function premium(request: Request): Promise<Response> {
  const result = await mppx.charge(CHARGE)(request);
  if (result.status === 402) return result.challenge;
  return result.withReceipt(Response.json({ data: "premium content" }));
}

// node:http ↔ Web Standards bridge (10 lines).
http.createServer(async (req, res) => {
  const url = `http://${req.headers.host ?? "localhost:4000"}${req.url}`;
  const webReq = new Request(url, {
    method: req.method,
    headers: new Headers(req.headers as Record<string, string>),
  });
  const webRes =
    new URL(url).pathname === "/api/premium"
      ? await premium(webReq)
      : new Response("not found", { status: 404 });
  res.statusCode = webRes.status;
  webRes.headers.forEach((v, k) => res.setHeader(k, v));
  res.end(await webRes.text());
}).listen(4000);

进阶#

仅适用于 HTTP 卖家——下方 SDK 代码块都挂在 HTTP 中间件上;Agent 卖家通过 Skill 生成付款链接,不涉及这一层。

1. 分账(仅 charge#

一笔支付自动拆给最多 10 个收款方。常见场景:平台抽成、多方分润、推广分佣。

硬约束

  • sum(splits.amount) < ChargeConfig::amount()(分账总额必须严格小于主金额)
  • splits.len() ≤ 10
  • 每个 recipient 必须是 EIP-55 校验过的 40-hex 地址

签名负担:买家会为每一路 split 各签一笔 EIP-3009——主收款方 1 笔 + 每个 split 各 1 笔,由卖家在 /settle 时一并提交。

package.json:

json
{
  "type": "module",
  "dependencies": {
    "@okxweb3/mpp": "^0.1.0"
  }
}
typescript
// server.ts
// 启动: npx tsx --env-file=.env server.ts
import * as http from "node:http";
import { Mppx } from "@okxweb3/mpp";
import { charge } from "@okxweb3/mpp/evm/server";
import { SaApiClient } from "@okxweb3/mpp/evm";

const saClient = new SaApiClient({
  apiKey: process.env.OKX_API_KEY!,
  secretKey: process.env.OKX_SECRET_KEY!,
  passphrase: process.env.OKX_PASSPHRASE!,
});

const mppx = Mppx.create({
  methods: [charge({ saClient })],
  realm: "test realm",
  secretKey: process.env.MPP_SECRET_KEY!,
});

// Total 100 base units; primary keeps 50, splits take 30 + 20.
// Constraints: sum(splits) < amount; splits.length <= 10;
//              recipient must be 40-hex EIP-55.
// Buyer signs one EIP-3009 per split (one to primary + one per entry).
const splits = [
  { amount: "30", recipient: "0x....321a1308", memo: "partner-a" },
  { amount: "20", recipient: "0x....d31a6608", memo: "partner-b" },
];

// Only difference vs single charge: methodDetails.splits.
const CHARGE = {
  amount: "100",
  currency: "0x...adb21711",                 // currency
  recipient: "0x...378211",                  // primary receipt
  description: "One premium API call (split)",
  methodDetails: { chainId: 196, feePayer: true, splits },
} as const;

async function premium(request: Request): Promise<Response> {
  const result = await mppx.charge(CHARGE)(request);
  if (result.status === 402) return result.challenge;
  return result.withReceipt(Response.json({ data: "premium content" }));
}

http.createServer(async (req, res) => {
  const url = `http://${req.headers.host ?? "localhost:4000"}${req.url}`;
  const webReq = new Request(url, {
    method: req.method,
    headers: new Headers(req.headers as Record<string, string>),
  });
  const webRes =
    new URL(url).pathname === "/api/premium"
      ? await premium(webReq)
      : new Response("not found", { status: 404 });
  res.statusCode = webRes.status;
  webRes.headers.forEach((v, k) => res.setHeader(k, v));
  res.end(await webRes.text());
}).listen(4000);

当前 charge split 落地为显式金额——每一路是绝对 base units,不是比例。

2. 同时支持 exact + charge#

package.json:

json
{
  "type": "module",
  "dependencies": {
    "@okxweb3/payment-router": "^0.1.0",
    "@okxweb3/mpp": "^0.1.0",
    "@okxweb3/x402-core": "^0.1.0",
    "@okxweb3/x402-evm": "^0.1.0"
  }
}
typescript
// server.ts
// 启动: npx tsx --env-file=.env server.ts
import * as http from "node:http";

import { Mppx } from "@okxweb3/mpp";
import { charge as mppCharge } from "@okxweb3/mpp/evm/server";
import { SaApiClient } from "@okxweb3/mpp/evm";

import { OKXFacilitatorClient } from "@okxweb3/x402-core";
import {
  x402HTTPResourceServer,
  x402ResourceServer,
} from "@okxweb3/x402-core/server";
import { ExactEvmScheme } from "@okxweb3/x402-evm/exact/server";

import {
  MppAdapter,
  X402Adapter,
  paymentRouter,
} from "@okxweb3/payment-router";

// —— MPP setup ——
const saClient = new SaApiClient({
  apiKey: process.env.OKX_API_KEY!,
  secretKey: process.env.OKX_SECRET_KEY!,
  passphrase: process.env.OKX_PASSPHRASE!,
});
const mppx = Mppx.create({
  methods: [mppCharge({ saClient })],
  realm: "test realm",
  secretKey: process.env.MPP_SECRET_KEY!,
});

// —— x402 setup (facilitator + scheme; routes are declared on the router) ——
const NETWORK = "eip155:196"; // X Layer Mainnet
const x402Server = new x402ResourceServer(
  new OKXFacilitatorClient({
    apiKey: process.env.OKX_API_KEY!,
    secretKey: process.env.OKX_SECRET_KEY!,
    passphrase: process.env.OKX_PASSPHRASE!,
  }),
).register(NETWORK, new ExactEvmScheme());

// Built-in priorities: MPP=10, x402=20 (MPP wins when both headers present).
// Custom adapters should start at priority ≥ 100.
const protect = paymentRouter({
  adapters: [
    new MppAdapter({ mppx }),
    new X402Adapter({
      resourceServer: x402Server,
      httpResourceServerCtor: x402HTTPResourceServer,
    }),
  ],
  routes: {
    "GET /generateImg": {
      description: "AI Image Generation Service",
      adapterConfigs: {
        mpp: {
          intent: "charge",
          amount: "10000",
          currency: "0x...adb21711",         // currency
          recipient: "0x...378211",          // receipt
          description: "AI Image Generation Service",
          methodDetails: { chainId: 196, feePayer: true },
        },
        x402: {
          scheme: "exact",
          network: NETWORK,
          payTo: "0x...378211",              // receipt
          price: "$0.01",
          description: "AI Image Generation Service",
          mimeType: "application/json",
        },
      },
    },
  },
});

// Protocol-agnostic. Runs only after one of the adapters has verified payment.
const handler = protect(async () =>
  Response.json({
    imageUrl: "https://placehold.co/512x512/png?text=AI+Generated",
    prompt: "a sunset over mountains",
  }),
);

http.createServer(async (req, res) => {
  const url = `http://${req.headers.host ?? "localhost:4000"}${req.url}`;
  const webReq = new Request(url, {
    method: req.method,
    headers: new Headers(req.headers as Record<string, string>),
  });
  const webRes =
    new URL(url).pathname === "/generateImg" && req.method === "GET"
      ? await handler(webReq)
      : new Response("not found", { status: 404 });
  res.statusCode = webRes.status;
  webRes.headers.forEach((v, k) => res.setHeader(k, v));
  res.end(await webRes.text());
}).listen(4000);

Agent 卖家接入#

Agent 在对话里主动生成付款链接#

Agent 卖家不是"被动挂中间件等买家请求",而是Agent 在对话里需要收费时主动生成付款链接,发到消息通道(XMTP / Telegram 等)。

本节聚焦付款链接的生成与分发;关于两个 Agent 如何通过 Telegram / XMTP 等消息通道建立会话,详见 快速开始 · 我是 Agent 卖家 — 配置消息通道网关

  1. 1
    安装 OnchainOS Skill

    把以下提示词发给你的 AI Agent,跟随引导完成安装:

    text
    请帮我安装 Onchain OS Payment Skill,让我的 Agent 能生成付款链接并对外收费。
    我的收款钱包地址:0xYourSellerWallet

    详见 Agent 卖家快速开始

  2. 2
    生成付款链接

    卖家 Agent 在需要收费时调用 Skill 生成一次性付款链接:

    text
    买家Agent:帮我翻译这份 3000 字文档
    卖家Agent:好的,翻译服务 50 USD₮0。
           [Skill 调用:createPayment({type:'charge', amount:'50000000', recipient:'0x...'})]
           付款链接:https://pay.okx.com/p/a2a_01HZX8Q9RK3JWYV7M2N5T8P4AB

    每条链接是一次性的,默认 30 分钟到期自动失效。

  3. 3
    发送付款链接

    Skill 返回的付款 URL(形如 https://pay.okx.com/p/a2a_xxx)作为文本消息发送给买家 Agent,对端解析 URL 后通过 Agentic Wallet 完成签名。

  4. 4
    轮询支付状态

    Skill 自动轮询 GET /payment/{paymentId}/status,状态变 completed 后通知 Agent 交付服务。

    text
    Skill: 支付已完成(tx: 0xabc...)
    Agent: 收到付款,开始翻译...

买家接入#

买家通过给 Agent 安装 Onchain OS Skill 完成接入——安装 Skill 时会自动配置 Agentic Wallet 作为底层签名钱包,无需单独安装。Skill 自动识别 HTTP 402 响应或消息通道里的付款 URL,调用钱包完成签名后重放请求,全程无需买家手动介入。完整接入步骤见 Agent 买家


边界与权衡#

什么时候不该用单次支付
  • 单笔价格极低 + 调用极高频:每笔都走链上结算成本不划算 → 用 批量支付
  • 单次消费量算不准(如 LLM 按 token):调用前定不了价 → 用 按量支付
  • 双方互不信任、需验收后放款:用 担保支付

下一步#