Catalog npm
PACKAGE EXPLORERView in registry

@0xinfrax/payments

npm

0xinfrax Payments — zero-business-coupling payment engine (chain / x402 / a2a / escrow / stablecoin). Embedded service or standalone library.

infraxpaymentsstablecoinweb3x402a2ampppayment-channelstripe
Versions collected9
Direct dependencies11From release 0.3.0
Last observed21 Oct, 08:56 UTC

README

Current package documentation

@0xinfrax/payments

零业务耦合的通用支付引擎(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 的内部引擎(AgentX 即此形态的参考实现,见 gateway/src/services/payments.ts)
  • 独立库:可被任意项目依赖,以「调用方自持 store」的形态独立运行
  • 独立服务(微服务形态):仓库内置部署入口 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(链上读写)。


目录


依赖配置(迁移到其他项目)

1. package.json

{
  "dependencies": {
    "@0xinfrax/payments": "^0.3.0" // 公开 npm registry 安装(推荐);或 file:/git/私有 registry,见下
    // pg / viem 是模块的传递依赖,npm install 会自动安装,宿主无需显式声明
  }
}

若宿主自身也要用 viem 发交易(链上 / x402 付款),可另行声明自己版本的 viem——模块会从自身 node_modules 解析,互不冲突。

2. 四种安装来源

场景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)

3. 使用前构建

发布物是 tsc 产物(dist/)。file: 引用前需确保已构建:

cd payments && npm install && npm run build

数据库迁移

模块拥有自己的 payment_* 表(12 个迁移文件,随包发布在 db/migrations/):

迁移表用途
001_payment_intents.sqlpayment_intents统一支付意图(chain / x402 / a2a)
002_payment_credits.sqlpayment_credits / payment_balances / payment_access入账台账、余额、通用访问登记表
003_payment_sessions.sqlpayment_sessions / payment_vouchers已废弃:MPP 通道会话 / 凭证(由 011 删除)
004_payment_events.sqlpayment_events归一化事件回放(credit 出站)
005_payment_authorizations.sqlpayment_authorizations(+ payment_intents.payee)period 授权(订阅周期计费)
006_payment_batches.sqlpayment_batchesbatch 批量收款(一次 N 个 a2a 意图)
007_payment_invites.sqlpayment_invitesinvite 收费邀请(payer/payee/amount/due_at/memo + 状态机)
008_payment_transfers.sqlpayment_transferstransfer 账本内原子划转(reference 幂等键)
009_payment_access_log.sqlpayment_access_log访问扣费审计日志(ref_id 幂等)
010_payment_credits_method.sqlpayment_credits.method入账 rail 归属(x402 / escrow / stablecoin)
011_drop_mpp.sql—退役 MPP:删除 payment_sessions / payment_vouchers
012_payment_a2a_direct.sqlpayment_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) // 模块账本余额

链上订阅详情(0.2.1)

/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 一致)。
  • HTTP 端点: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' })

可选:现成 Express router(版本 A 推荐)

@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 就不需要它。

能力层(可插拔 rail)

每个通道是一个可插拔能力(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..." } }, "...": {} } }
    
  • 未启用能力的端点仍存在但返回 503(显式 "not enabled" 而非 404),便于调用方识别配置缺失。
  • 新增能力不破坏旧调用: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 覆盖)。
  • a2a direct(0.3.0):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)

仓库内最早跑通独立服务形态的调用方:waas 用户套餐支付(MQ-12,代码见 projects/waas/routes/subscriptionRoutes.ts + projects/waas/services/paymentsClient.ts)。以下以其真实用法为准的端到端样例。

① 轻量 HTTP 客户端

独立服务 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'),
}

② 订阅创建(pending)——按 rail 取支付信息

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`

③ 支付确认——两种 rail 统一落到 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 + 续期)完全在调用方,支付引擎只负责「钱」与出站事件。


嵌入式形态(宿主自带 store / 回调)

模块的所有持久化都走 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 → 模块配置)
  • 自定义 store + 事件桥:gateway/src/services/payments-bridge.ts(AgentxPaymentStore 覆盖 x402_* 表,PaymentsBridge 把模块 credit 事件落到业务订阅表)

业务上下文透传规则

  1. metadata:业务参数(agentId / planId / 订单号…)放这里,模块原样落 payment_intents.metadata(JSONB),不解释、不校验、不消费。0.2.1 起出站也会带上它:链上 onCredit 事件补 metadata(能关联到 intent 才带,取不到为 undefined,不新增失败路径)。
  2. 访问策略:resolveAccess 的语义由注入的 store 决定(通用 PgPaymentStore 只查 payment_access;AgentX 的 store 是「链上 OR x402」)。模块不感知。

API 参考

成员说明
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.chainChainAdapter:getPlan / hasActiveSubscription / getSubscriptionDetail(0.2.1,两步读:金额 + 实付币种)/ platformFeeBps
PaymentsService.x402X402Adapter: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 / recoverPermit2SignerEIP-712 协议 helper(稳定币双机制)
buildPaymentMessage / encodeHeader / decodeHeaderx402 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 事件入账验证

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 9

Collected releases
VersionPublishedDirect dependenciesOpen version
0.3.0Registry latest18 Oct 202611
0.2.218 Sept 202611
0.2.117 Sept 202611
0.2.017 Sept 202611
0.1.417 Aug 202611
0.1.310 Aug 202611
0.1.29 Aug 202611
0.1.19 Aug 202611
0.1.08 Aug 20268

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
sftgroup/infrax

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 08:56 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:de453dd04c8bb425d65e376581f567b738a920461ca5334927eea28e9043b48d