商户 SDK
@curless/agentbank-merchant-sdk 就是商户后端接入网关所需的全部。你用自己的目录给订单定价并开一笔结账;网关不保存你的任何目录。
npm install @curless/agentbank-merchant-sdkimport { AgentbankMerchant } from '@curless/agentbank-merchant-sdk';
const agentbank = new AgentbankMerchant({ baseUrl: 'https://mcp.curless.ai', apiKey: process.env.AGENTBANK_MERCHANT_TOKEN!, merchantId: process.env.AGENTBANK_MERCHANT_ID!,});| 方法 | 协议 / 结算轨 |
|---|---|
checkout.openACP({ currency, items }) |
ACP — 卡 |
checkout.openUCP({ currency, items }) |
UCP — 卡 |
checkout.chargeSkyfire({ token, description? }) |
Skyfire — 对一个 KYAPay token 收费 |
items 是以最小单位表示的 { sku, name, quantity, unitPrice }:unitPrice 由你设置,总额据此推导。chargeSkyfire 则对 agent 呈递的 KYAPay token 收费,无需 session。
orders.list({ status, protocol, currency, from, to, limit, offset })—— 可筛选 + 分页,并带总数。orders.get(id)—— 完整的单笔订单(行项目 + 支付方式)。orders.summary({ … })—— 计数 + 按币种的总额 + 各项细分。balanceTransactions.list({ limit, offset })—— 一份分页的资金流水 (你各币种钱包的实时余额在 MCP 连接器的get_balance里)。
orders.listRefundRequests({ status })、orders.approveRefund(id)、
orders.rejectRefund(id, { note }),以及 orders.refund(orderId, { amount, reason })
(商户发起)。批准会把退款转给 Curless,由它执行。
Webhooks
Section titled “Webhooks”注册一个端点(POST /v1/webhook-endpoints,返回 whsec_ —— 只在这一次返回,请保存好),然后对投递做验签 —— HMAC-SHA256、常量时间比较、edge 兼容(Web Crypto)。verifyWebhook(secret, rawBody, signature) 返回一个布尔值;parseVerifiedWebhook(secret, rawBody, signature)
会验签并返回解析后的事件(或抛出异常)。
一个端点能收到的事件类型列表见 API 参考 → Webhook 事件。
import { parseVerifiedWebhook } from '@curless/agentbank-merchant-sdk';const event = await parseVerifiedWebhook(whsec, rawBody, signatureHeader);投递是至少一次的 —— 按 event.id 去重
Section titled “投递是至少一次的 —— 按 event.id 去重”投递会带退避重试,直到你的端点返回 2xx,所以同一个事件可能到达不止一次:如果你的 handler 已经提交,但响应在回传途中丢失,我们就没看到 2xx,于是会再发一次。这是正常现象,不是错误状态 —— 一个在每次投递时都记一笔退款或一次履约的 handler,早晚会重复做两遍。
event.id 就是去重键。它在事件入队时分配一次,之后每次重试都原样重发那份存好的
payload —— 所以它在多次重试间保持稳定,可以安全地持久化:
const event = await parseVerifiedWebhook(whsec, rawBody, signatureHeader);
// Unique index on event_id. Already seen → ack and stop.const fresh = await db.markSeen(event.id);if (!fresh) return new Response('ok', { status: 200 });
await handle(event);return new Response('ok', { status: 200 });像上面那样用 2xx 确认重复投递 —— 返回错误会把你已经处理过的事件重新入队。
如果你注册了不止一个端点,有一点要注意:同一个事件会分发到每一个端点,且携带相同的 event.id。这是有意为之的(它就是同一个事件),但如果两个端点喂给同一个 handler,请用端点加 event.id 作为去重键,而不是只用 event.id ——
否则第二个端点的投递会被当成重复而被丢弃。
每个非 2xx 都会抛出带 status + code 的类型化 AgentbankError —— 捕获它即可统一处理各种失败。

