Observed since 21 Oct, 08:56 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
0xinfrax Payments — zero-business-coupling payment engine (chain / x402 / a2a / escrow / stablecoin). Embedded service or standalone library.
零业务耦合的通用支付引擎(chain / x402 / 稳定币 / a2a / period 订阅周期 / batch 批量收款 / invite 自动收费邀请 / transfer 账本内转账)。
维护:由 InfraX(GitHub sftgroup/infraX)团队维护;源码位于仓库
projects/payments/目录,npm 包@0xinfrax/payments由 InfraX 账号发布与维护。集成方可作为独立库使用,但问题的修复与演进统一由 InfraX 负责。
集成到独立项目:完整部署步骤(安装 / 数据库 / 合约 / 代码接入 / 验证 / 生产注意)见
DEPLOY.md。调用方自配收款:想配置「自己的收款」(自部署 SubscriptionManager / 自配 x402 钱包与稳定币收款地址),见
CALLER_SETUP.md。
gateway/src/services/payments.ts)server.ts(tsx 直跑)——Express + 统一鉴权(auth-express,Bearer/X-API-Key/X-Service-Key)+ PgPaymentStore(infrax_payments 独立库,启动自动跑迁移)+ /health + createPaymentsRouter(挂 /payments);链上读可配 CHAIN_RPC_READ_KEY 走 chain-rpc 网关(DC-10);WEBHOOK_FORWARD_URL 时事件出站转发(createWebhookForwarder,MQ-16 起支持逗号分隔多目标——如 http://127.0.0.1:9109/api/v2/subscription/payment-callback,http://127.0.0.1:9102/api/v2/data/payment-callback,每个业务方独立收事件、独立重试,按 reference 幂等对账)。systemd 模板见 deploy/systemd/keel-payments.service(:9132)核心设计:模块只懂钱(方法 / 资产 / 金额 / 凭证)。业务上下文(如 agentId、订单号)一律经 metadata 透传;持久化走注入的 PaymentStore 接缝;宿主业务(订阅注册、发货)只通过 onCredit 回调接入。模块不解释、不校验、不消费任何业务参数。
依赖仅有 pg(账本/凭证)与 viem(链上读写)。
CALLER_SETUP.md){
"dependencies": {
"@0xinfrax/payments": "^0.3.0" // 公开 npm registry 安装(推荐);或 file:/git/私有 registry,见下
// pg / viem 是模块的传递依赖,npm install 会自动安装,宿主无需显式声明
}
}
若宿主自身也要用
viem发交易(链上 / x402 付款),可另行声明自己版本的viem——模块会从自身node_modules解析,互不冲突。
| 场景 | package.json 写法 |
|---|---|
| 公开 npm registry(推荐,已发布) | "@0xinfrax/payments": "^0.3.0"(npm view @0xinfrax/payments version → 当前 latest) |
| Monorepo / 本地开发 | "@0xinfrax/payments": "file:../payments" |
| Git 仓库(打 tag 固定版本) | "@0xinfrax/payments": "git+https://github.com/<org>/payments.git#v0.3.0" |
| 私有 npm registry | "@0xinfrax/payments": "^0.3.0"(配合 .npmrc 指向 registry) |
发布物是 tsc 产物(dist/)。file: 引用前需确保已构建:
cd payments && npm install && npm run build
模块拥有自己的 payment_* 表(12 个迁移文件,随包发布在 db/migrations/):
| 迁移 | 表 | 用途 |
|---|---|---|
001_payment_intents.sql | payment_intents | 统一支付意图(chain / x402 / a2a) |
002_payment_credits.sql | payment_credits / payment_balances / payment_access | 入账台账、余额、通用访问登记表 |
003_payment_sessions.sql | payment_sessions / payment_vouchers | 已废弃:MPP 通道会话 / 凭证(由 011 删除) |
004_payment_events.sql | payment_events | 归一化事件回放(credit 出站) |
005_payment_authorizations.sql | payment_authorizations(+ payment_intents.payee) | period 授权(订阅周期计费) |
006_payment_batches.sql | payment_batches | batch 批量收款(一次 N 个 a2a 意图) |
007_payment_invites.sql | payment_invites | invite 收费邀请(payer/payee/amount/due_at/memo + 状态机) |
008_payment_transfers.sql | payment_transfers | transfer 账本内原子划转(reference 幂等键) |
009_payment_access_log.sql | payment_access_log | 访问扣费审计日志(ref_id 幂等) |
010_payment_credits_method.sql | payment_credits.method | 入账 rail 归属(x402 / escrow / stablecoin) |
011_drop_mpp.sql | — | 退役 MPP:删除 payment_sessions / payment_vouchers |
012_payment_a2a_direct.sql | payment_a2a_settlements(+ payment_intents 新列) | a2a direct:链上凭证与终局性字段 |
在新项目自己的数据库中执行(不要复用其他项目的表):
for f in node_modules/@0xinfrax/payments/db/migrations/*.sql; do
psql "$DATABASE_URL" -f "$f"
done
通用形态使用 PgPaymentStore(读这些表)。如果宿主已有自己的订阅/余额表,可自定义实现 PaymentStore 注入(见嵌入式形态),此时可不执行模块迁移。
import { Pool } from 'pg'
import { PaymentsService, PgPaymentStore } from '@0xinfrax/payments'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const payments = new PaymentsService({
store: new PgPaymentStore(pool),
// 链上:每个 slot 一条(可读链上套餐价 / 订阅状态)
chains: {
sepolia: {
rpcUrl: process.env.RPC_URL,
chainId: 11155111,
subscriptionManager: process.env.SUBSCRIPTION_MANAGER, // 部署好的 SubscriptionManager
},
},
// x402:不配则 verify 返回「未配置」
// 顶层 payTo/priceWei/escrow 描述**默认链**(chain);其它链写在 collectors 里,
// 多币写在 stablecoins[](各币独立定价,不换算)。见 CALLER_SETUP.md §1/§2。
x402: {
enabled: true,
payTo: process.env.X402_PAY_TO, // 默认链的收款钱包
priceWei: process.env.X402_PRICE_WEI, // 默认链的单次价格
chain: 'sepolia',
// AX-1/OE-1:Escrow 金库托管(可选)。配置后 verify 自动走 escrow 判定
// (解析 InfraXEscrow.deposit() 的 Deposited 事件入账,资金进金库而非 payTo 直收)。
// 部署指引见 projects/escrow(InfraXEscrow)与 docs/INFRAX_ESCROW_GUIDE.md。
escrow: { address: process.env.X402_ESCROW_ADDRESS },
// 按链收款人(可选):未声明的非默认链不可收款(402 / x402-verify → 409 NOT_CONFIGURED)
// collectors: { ethereum: { payTo, priceWei, maxAmountWei?, escrow? } },
// 稳定币(可多币并存;USDT 无 EIP-3009 → 只能直转入账或 Permit2)
// stablecoins: [{ enabled, chain, asset, symbol, decimals, priceWei, domainName? }],
},
// ── 宿主业务只写在这里 ──────────────────────────────────────────────
// 链上付款验证成功入账后(x402 轨)→ 你的业务
onCredit: async (credit) => {
await notifyUser(credit.payer, credit.amountWei)
},
})
// ① 链上:用户先在链上调用 SubscriptionManager.subscribe(planId),然后:
const active = await payments.chain.hasActiveSubscription('sepolia', subscriber, agentId)
const plan = await payments.chain.getPlan('sepolia', planId) // 定价、套餐详情(含计费周期)
const feeBps = await payments.chain.platformFeeBps('sepolia')
// ② x402:客户端自行广播资金 tx 后提交 base64 `PAYMENT-SIGNATURE` 验证入账(幂等)
const verified = await payments.verifyX402Payment(paymentSignature, accepts?)
// → { payer, settledAmount, creditedWei, accepted, chain } 或 null(非有效支付)
const balance = await payments.balanceOf(subscriber) // 模块账本余额
/subscription 只回答「有没有效」,不足以支撑「付的是不是目标套餐」这类反作弊校验;getSubscriptionDetail 两步读合约(getSubscription(subscriber, agentId) → getSubscriptionDetail(subscriptionId))补齐金额与币种:
const sub = await payments.chain.getSubscriptionDetail('bsc', subscriber, planId)
// → {
// active: true, // ← 唯一有效性判据(内部复用 hasActiveSubscription)
// subscriptionId: 12,
// status: 1, // 合约枚举透传(1 = active),仅供展示
// startedAt, expiresAt, period,
// payToken: '0x…', // 实付币种(大小写与合约一致,比对自己 toLowerCase())
// amountPaid: '20000000', // 实付金额,payToken 的原子单位(字符串,非浮点)
// trialActive, trialEndsAt, fundsReleased,
// }
{ active: false, subscriptionId: 0 }(HTTP 200,不抛错、不 404),调用方统一处理。subscriptionManager → 409 chain rail not configured(与 /price、/subscription 一致)。GET /subscription-detail/:chain/:subscriber/:resourceId(subscriber 统一小写、resourceId 为数字,缺失 → 400)。status 不得当作有效性判据(有效性一律看 active),否则调用方各判一套、口径分叉。// 通用 store 只查 payment_access 登记表;「链上 OR 链下」的统一策略由你的 store 实现决定
const access = await payments.resolveAccess(subscriber, { agentId }, { chain: 'sepolia' })
@0xinfrax/payments/router 提供了覆盖全部端点(/info /price /chain-info /subscription /subscription-detail /x402/supported /x402/verify /balance /access /capabilities /orders/:paymentId/status /a2a/* /period/* /batch/* /invites/* /transfers/*)的现成 router,挂载即用:
import { createPaymentsRouter } from '@0xinfrax/payments/router'
app.use(express.json())
app.use('/payments', createPaymentsRouter(payments))
express 是 optional peer 依赖:不调用 createPaymentsRouter 就不需要它。
每个通道是一个可插拔能力(chain / x402 / a2a / period / batch / invite / transfer),由构造参数(+ ENV 开关)决定启用与否:
GET /capabilities 返回能力清单(enabled / endpoints / config),调用者先探测再使用:
{ "capabilities": { "a2a": { "enabled": true, "endpoints": ["POST /a2a", "POST /a2a/settle", "POST /a2a/:paymentId/settle", "POST /a2a/:paymentId/cancel"], "config": { "modes": ["collector", "direct"], "defaultPayee": "0x..." } }, "...": {} } }
createPayment(method) 增加 a2a(两阶段意图)与 batch(一次向 N 个 payee 收款);chargePeriod / getAuthorization 走 period 授权;createInvite / payInviteByBalance 走 invite(agent 自动发收费邀请);createTransfer / confirmTransfer 走 transfer(账本内原子划转)。POST /a2a/settle 支持两种结算模式(AX-8/A2A-1):mode: 'tx'(默认,校验链上 tx 入账)与 mode: 'balance'(从 payer 预存余额直接 deduct 结算,无新链上 tx,适用于服务端→服务端 A2A 编排;结算原子、ref 幂等,金额默认取 intent 记录,可被 amountWei 覆盖)。createPayment({ method:'a2a', settlementMode:'direct', payee }) 后,payer 直接向 payee 转账;a2aDirectSettle(paymentId, txHash) 按 intent 的 payer/payee/asset/精确金额核验链上交易,并按链确认数判终局(未终局 202)。平台不入账、不经手资金;a2a:/a2a/:paymentId/cancel 可幂等取消。direct 模式不依赖 x402。a2a 默认开启(可 A2A_ENABLED=false 关闭;collector 模式需 x402,direct 不需要);period / batch / invite / transfer 需注入对应 store seam 才启用(微服务形态另需 PERIOD_ENABLED / BATCH_ENABLED / INVITE_ENABLED / TRANSFER_ENABLED=true)。仓库内最早跑通独立服务形态的调用方:waas 用户套餐支付(MQ-12,代码见
projects/waas/routes/subscriptionRoutes.ts+projects/waas/services/paymentsClient.ts)。以下以其真实用法为准的端到端样例。
独立服务 router 挂载在 /payments(区别于 SDK 自带 PaymentsClient 的 gateway 前缀 /api/v1/payments),鉴权用统一契约 X-Service-Key。waas 即采用此轻量封装:
import { config } from '../config' // PAYMENTS_URL / PAYMENTS_API_KEY
async function call<T = any>(path: string, init?: RequestInit): Promise<T> {
const base = config.payments.baseUrl.replace(/\/$/, '')
const resp = await fetch(`${base}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
'X-Service-Key': config.payments.apiKey, // 统一服务间鉴权
...(init?.headers ?? {}),
},
signal: AbortSignal.timeout(10_000),
})
if (!resp.ok) {
const body = await resp.json().catch(() => null)
throw new Error(body?.error ?? `payments ${path} failed (${resp.status})`)
}
return resp.json()
}
export const paymentsApi = {
/** 链上套餐定价(GET /price?planId=&chain=) */
price: (planId: number, chain: string) => call(`/payments/price?planId=${planId}&chain=${chain}`),
/** 链槽信息:chainId + SubscriptionManager(GET /chain-info/:chain) */
chainInfo: (chain: string) => call(`/payments/chain-info/${chain}`),
/** 链上 escrow 订阅状态(GET /subscription/:chain/:subscriber/:resourceId) */
hasActiveSubscription: (chain: string, subscriber: string, resourceId: number) =>
call(`/payments/subscription/${chain}/${subscriber.toLowerCase()}/${resourceId}`),
/** 验证 x402 v2 凭证并入账(POST /x402/verify,幂等;paymentPayload 为 base64 `PAYMENT-SIGNATURE`) */
verify: (paymentPayload: string, paymentRequirements?: X402Accept) =>
call('/x402/verify', { method: 'POST', body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements }) }),
/** x402 rail 探测(GET /info):payTo / priceWei / network */
info: () => call('/payments/info'),
}
if (plan.price === 0) { /* free 直通 active,无支付意图 */ return }
const inserted = await pool.query(
`INSERT INTO subscriptions (user_id, plan_id, plan_name, price, billing_cycle, status, payment_method, payment_status)
VALUES ($1,$2,$3,$4,'monthly','pending',$5,'pending') RETURNING *`,
[userId, planId, plan.name, plan.price, rail] // rail: chain | x402
)
const subscriptionId = inserted.rows[0].id
const payment: any = { rail, chain }
if (rail === 'chain') {
const [planInfo, info] = await Promise.all([
paymentsApi.price(resourceId, chain),
paymentsApi.chainInfo(chain),
])
payment.price = planInfo.price // 链上套餐价(wei)
payment.period = planInfo.period
payment.payToken = planInfo.payToken
payment.subscriptionManager = info.subscriptionManager // 前端链上 subscribe 用
payment.chainId = info.chainId
} else if (rail === 'x402') {
const info = await paymentsApi.info()
payment.payTo = info.x402.payTo // 前端向此地址转账
payment.priceWei = info.x402.priceWei
payment.network = info.x402.network
}
// → 前端:chain 引导用户链上 subscribe;x402 向 payTo 转账后提交 base64 `PAYMENT-SIGNATURE`
pending → active(幂等)// chain:轮询链上 escrow(SubscriptionManager.hasActiveSubscription)→ active 则激活
const { active } = await paymentsApi.hasActiveSubscription(chain, wallet, resourceId)
if (active) await activateSubscription(subscriptionId) // cancel 旧 active + pending→active
// x402:前端提交 base64 `PAYMENT-SIGNATURE` → POST /x402/verify(幂等入账)→ payer 匹配当前钱包 → 激活
const { isValid, payer } = await paymentsApi.verifyPayment(paymentSignature)
if (isValid && payer?.toLowerCase() === wallet.toLowerCase()) await activateSubscription(subscriptionId)
状态机约定:
pending(支付意图,模块已记payment_intents)→active(任一 rail 确认支付)→cancelled / failed。业务激活逻辑(activateSubscription:cancel 旧订阅 → 置 active + 续期)完全在调用方,支付引擎只负责「钱」与出站事件。
模块的所有持久化都走 PaymentStore 接口,宿主可注入自己的表:
import { PaymentsService } from '@0xinfrax/payments'
import type { PaymentStore, PaymentCredit } from '@0xinfrax/payments'
class MyStore implements PaymentStore {
balanceOf(address, asset) { /* 读你的余额表 */ }
credit(credit: PaymentCredit) { /* 幂等入账 */ }
isCreditRecorded(reference) { /* 幂等探针 */ }
deduct(address, amount, asset) { /* 扣减,返回是否充足 */ }
resolveAccess(subscriber, resource, opts) { /* 你的统一访问策略 */ }
}
const payments = new PaymentsService({ store: new MyStore(), chains, x402, ... })
AgentX 自身即此形态的参考实现:
gateway/src/services/payments.ts(env → 模块配置)gateway/src/services/payments-bridge.ts(AgentxPaymentStore 覆盖 x402_* 表,PaymentsBridge 把模块 credit 事件落到业务订阅表)metadata:业务参数(agentId / planId / 订单号…)放这里,模块原样落 payment_intents.metadata(JSONB),不解释、不校验、不消费。0.2.1 起出站也会带上它:链上 onCredit 事件补 metadata(能关联到 intent 才带,取不到为 undefined,不新增失败路径)。resolveAccess 的语义由注入的 store 决定(通用 PgPaymentStore 只查 payment_access;AgentX 的 store 是「链上 OR x402」)。模块不感知。| 成员 | 说明 |
|---|---|
PaymentsService.createPayment(input) | 创建支付意图(a2a / batch / escrow / chain) |
PaymentsService.verifyX402Payment(header, expected?) | 验证 x402 v2 PAYMENT-SIGNATURE(base64)并幂等入账(验签 + 验链上资金 tx,原生优先、失败回退 stablecoin EIP-3009) |
PaymentsService.resolveAccess(subscriber, resource, opts?) | 委托 store 的访问检查 |
PaymentsService.balanceOf / deduct | 余额查询 / 原子扣减(委托 store) |
PaymentsService.chain | ChainAdapter:getPlan / hasActiveSubscription / getSubscriptionDetail(0.2.1,两步读:金额 + 实付币种)/ platformFeeBps |
PaymentsService.x402 | X402Adapter:verifyAndCredit / balanceOf / deduct / paymentRequiredHeaders |
PgPaymentStore | 通用 Postgres store(payment_* 表) |
updateIntentStatus(paymentId, status, opts?) | 推进 intent 生命周期(created→paid/failed/closed、paid→closed);x402 由 verifyX402Payment 自动置 paid,a2a / batch 由对应 create 流程初始化。opts.requireExisting(0.2.1)为 opt-in:开启且 store 提供 getIntent 时,未知 id → 404、同值重复写幂等静默返回(HTTP 端点 POST /orders/:paymentId/status 即用此开关;不注入 getIntent 的嵌入宿主跳过判空,行为与旧版一致) |
PaymentError / isPaymentError | 带 code + 建议 status 的类型化错误(宿主按码映射 HTTP) |
createPaymentsRouter | 现成 Express router(@0xinfrax/payments/router) |
X402Client / PaymentsClient | 面向任意部署点的 HTTP 客户端 |
recoverEIP3009Signer / recoverPermit2Signer | EIP-712 协议 helper(稳定币双机制) |
buildPaymentMessage / encodeHeader / decodeHeader | x402 v2 协议 helper(PaymentRequired / PaymentPayload / PaymentResponse) |
类型集中在 types.ts:PaymentMethod、CreatePaymentInput / CreatePaymentResult、SubscriptionDetail(0.2.1)、PaymentCredit、VerifiedPayment、PlanInfo、X402Info 等。
仓库内置两套本地 harness(需 docker,无需真实链):
| 脚本 | 验证对象 | 说明 |
|---|---|---|
scripts/local-payments/run.sh | 嵌入式形态(B) | 起 postgres+anvil → 部署合约(含 MockUSDC)→ 起 gateway → FLOWS="f1 f4" 全绿(F1 链上订阅 / F4 x402 v2) |
scripts/local-payments/run-decouple.sh | 独立库形态(A) | 只 import 模块自身 + 独立 agentx_payments 库,证明零 AgentX 耦合,19 项断言 |
npm test | 单测 | 14 个文件 219 项断言(协议 / 适配器 / service / router / 错误码 / 能力层 / invite+transfer 状态机与原子划转 / 订阅详情 + intent 状态回写) |
解耦验证断言示例:模块入口必须从自身 dist/ 解析、依赖仅 pg,viem、src/dist 无 fiat_subscriptions / x402_* / @agentxv2/sdk 等业务 token、DB 仅 payment_* 表。
payments/
├── package.json # @0xinfrax/payments, deps: pg + viem
├── tsconfig.json
├── db/migrations/ # 001-012(模块自有 payment_* 表;011 退役 MPP,012 a2a direct)
├── src/
│ ├── index.ts # 公共入口
│ ├── types.ts # 通用类型(metadata 透传约定)
│ ├── errors.ts # PaymentError{code,status}
│ ├── service.ts # PaymentsService(引擎 + 回调接缝)
│ ├── store.ts # PaymentStore 接口 + PgPaymentStore + PgAuthorizationStore + PgBatchStore + PgInviteStore + PgTransferStore
│ ├── client.ts # X402Client / PaymentsClient
│ ├── router.ts # createPaymentsRouter(express 为 optional peer)
│ ├── protocol/
│ │ ├── x402-v2.ts # PaymentRequired / PaymentPayload / PaymentResponse(EIP-712)
│ │ └── stablecoin.ts # EIP-3009 + Permit2 验签 helper
│ └── adapters/
│ ├── chain.ts # 链上只读(getPlan / hasActiveSubscription)
│ ├── x402.ts # 原生代币付款验证 + 入账(stablecoin fallback)
│ └── stablecoin.ts # EIP-3009 Transfer 事件入账验证
Download counts and popularity will appear here as the collector receives them.
This describes our collected data; it is not a security assessment.
Observed since 21 Oct, 08:56 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:de453dd04c8bb425d65e376581f567b738a920461ca5334927eea28e9043b48d