跳转到内容

商户 SDK

@curless/agentbank-merchant-sdk 就是商户后端接入网关所需的全部。你用自己的目录给订单定价并开一笔结账;网关不保存你的任何目录。

Terminal window
npm install @curless/agentbank-merchant-sdk
import { 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,由它执行。

注册一个端点(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 —— 捕获它即可统一处理各种失败。