接入 Taria Pay(给 coding agent)
这页是商家应用接入 Taria Pay 的唯一契约,给 coding agent 和工程师共用。不要自造字段名、路径或结账 URL。
人工文档:快速接入 · 创建支付订单 · 接收支付结果 · API 参考
环境
| 项 | 值 |
|---|---|
| 生产 API origin | https://api.tariapay.com |
| 本地 API origin | http://127.0.0.1:3003 |
| Hosted checkout | https://checkout.tariapay.com(本地 http://127.0.0.1:3004) |
| 密钥环境变量 | TARIAPAY_SECRET_KEY(商家后台 → Developers)。别名:TARIAPAY_API_KEY |
| Webhook 密钥 | TARIAPAY_WEBHOOK_SECRET |
| API origin | TARIAPAY_API_BASE — 只写 origin,不要带路径 |
商户接口:
| 调用方 | URL |
|---|---|
| 规范路径 | {origin}/v1/payment-intents |
| 兼容别名(同一组 handler) | {origin}/merchant/v1/payment-intents |
SDK baseUrl | 只传 {origin}。SDK 会追加 /v1。 |
不要叠前缀(/merchant/v1 再加一层 /v1)。公开结账接口仍在 /checkout/v1/...,商户服务端不要调。
最小流程
- 服务端用密钥创建 payment intent。
- 浏览器跳转到
paymentIntent.checkoutUrl(或用CheckoutButton打开)。 - 只在 验签通过 的 webhook(
payment_intent.confirmed)或服务端retrieve之后履约。 - 不要因为买家回到了
successUrl就履约。
Node / Workers 骨架
ts
import { TariaPay, TariaPayError, constructWebhookEvent } from "@tariapay/sdk";
const tariapay = new TariaPay({
secretKey: process.env.TARIAPAY_SECRET_KEY!,
baseUrl: process.env.TARIAPAY_API_BASE, // 只传 origin
});
export async function createCheckout(order: {
orderId: string;
amount: string;
email?: string;
appUrl: string;
}) {
try {
return await tariapay.paymentIntents.create({
orderId: order.orderId,
amount: order.amount,
currency: "USDC",
acceptedCurrencies: ["USDC", "USDT"],
successUrl: `${order.appUrl}/orders/${order.orderId}?pay=success`,
cancelUrl: `${order.appUrl}/orders/${order.orderId}?pay=cancel`,
customer: order.email ? { email: order.email } : undefined,
});
} catch (error) {
if (error instanceof TariaPayError && error.type === "timeout_error") {
return tariapay.paymentIntents.retrieveByOrderId(order.orderId);
}
throw error;
}
}
export function verifyWebhook(rawBody: string, headers: Headers) {
return constructWebhookEvent(rawBody, headers, process.env.TARIAPAY_WEBHOOK_SECRET!);
}Cloudflare Workers:
- 打开
nodejs_compat。 - SDK 会绑定
fetch。如果你自己注入 fetch,写成(input, init) => fetch(input, init)。 constructWebhookEvent不需要商户 secret key。- 密钥放
wrangler secret/.dev.vars,不要放进[vars]。
acceptedCurrencies
currency 是主币种,也是你自己订单上应记录的币。
acceptedCurrencies 控制结账页币种选择。不传则页面 只显示 currency。
ts
await tariapay.paymentIntents.create({
orderId: "order_1001",
amount: "49.99",
currency: "USDC",
acceptedCurrencies: ["USDC", "USDT"],
});paymentMethods 管的是钱包 vs 转账,不是币种。
Webhook 契约
请求头(规范名与别名都要认):
| 规范名 | 别名 | 含义 |
|---|---|---|
x-tariapay-signature | x-pay-signature | ${timestamp}.${rawBody} 的 hex HMAC-SHA256 |
x-tariapay-timestamp | x-pay-signature-timestamp | Unix 秒 |
x-tariapay-event | x-pay-event | 事件类型 |
x-tariapay-delivery-id | x-pay-delivery-id | 投递 ID(幂等键) |
json
{
"id": "evt_123",
"type": "payment_intent.confirmed",
"createdAt": "2026-03-29T12:00:00.000Z",
"data": {
"paymentIntent": {
"paymentId": "b1985d0b-2026-4f8c-bf75-59ea53fe5466",
"orderId": "order_1001",
"status": "confirmed",
"currency": "USDC",
"amount": "49.99",
"checkoutUrl": "https://checkout.tariapay.com/pi_b1985d0b-2026-4f8c-bf75-59ea53fe5466",
"txHash": "0x..."
}
}
}从 data.paymentIntent 读字段。用 x-tariapay-delivery-id 或 id 去重。只在 type === "payment_intent.confirmed" 时履约。
本地 E2E(命令)
在 Taria Pay 仓库:
bash
npm run dev:backend # :3003
npm run dev:checkout # :3004
./scripts/dev-backfill-loop.sh在商家应用:
bash
TARIAPAY_API_BASE=http://127.0.0.1:3003
TARIAPAY_SECRET_KEY=tpk_...
# Webhook:用公网 HTTPS 隧道指向本地 handler,再到后台 Developers → Webhooks 注册已付款的 intent 会停在 processing,直到有人 POST /internal/backfill。不跑 backfill loop,本地不会发商户 webhook。
托管测试网上的测试模式支付需要测试代币:合约地址与领取方式见测试 → 测试代币。
错误对照
| 现象 | 处理 |
|---|---|
| 创建返回 401 | 密钥错,或 baseUrl 不是 origin(或旧 listener 没有 /v1 别名)。 |
| 创建超时 | 调 retrieveByOrderId(orderId),不要换新的 orderId。 |
| 创建 409 | orderId 已存在,改为查询。 |
| 结账页只有一个币 | 传 acceptedCurrencies。 |
| Webhook 验签失败 | HMAC 必须用原始 body;secret 必须和已注册 endpoint 一致。 |
| 本地已付款无 webhook | 启动 dev-backfill-loop.sh,并用隧道暴露 handler。 |
Workers 报 Illegal invocation | 不要传入未绑定的 globalThis.fetch;用 SDK 默认或包一层。 |
前端
托管跳转:window.location.assign(checkoutUrl)。
React 按钮:
tsx
import { CheckoutButton } from "@tariapay/checkout-panel/button";
import "@tariapay/checkout-panel/styles.css";Vite 额外配置:optimizeDeps.include: ["@tariapay/checkout-panel/button"];如果包是 workspace 源码链接,还要加 server.fs.allow。按钮不会拉钱包栈。
不要做
- 把 secret key 放进浏览器包。
- 用
successUrl履约。 - 自己拼接
checkout.tariapay.com/...。 - 创建超时时换一个新的
orderId再试。 - 猜测 webhook 字段路径(
data.payment/ snake_case)。上面的 envelope 就是契约。