REST API
所有公开接口在 https://pay.35team.com 下,使用 JSON 编码。鉴权用Authorization: Bearer <API_KEY>。
鉴权
在 控制台 → API Keys 创建 Secret Key。Key 形如sk_test_xxx 或 sk_live_xxx,前者走测试模式(mock provider,不产生真实交易), 后者走真实渠道。每次请求带 Authorization 头:
curl https://pay.35team.com/api/v1/checkout \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{...}'创建支付会话
创建一个支付会话,得到收银台 URL。把用户重定向到 url 即可。
请求体
orderId。/r/{recordId}(适合 PaymentLink / 不需要业务回调的场景)。传了则启用 sync 模式,客户落地时本地业务已 paid,无需 polling。示例
curl https://pay.35team.com/api/v1/checkout \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"amount": 9900,
"currency": "CNY",
"description": "追思视频套餐",
"metadata": { "orderId": "order_123" },
"successUrl": "https://your.site/orders/123"
}'响应 200
{
"id": "sess_01HXYZ...",
"status": "pending",
"url": "https://pay.35team.com/c/sess_01HXYZ...",
"amount": 9900,
"currency": "CNY",
"expiresAt": "2026-04-28T07:00:00.000Z",
"createdAt": "2026-04-28T06:30:00.000Z",
"mode": "test"
}successUrl 的 sync 模式
传了 successUrl 后,35pay 会在客户付完跳回前先做同步中转:
- 客户付完 → provider 跳回 35pay 的中转页
/c/{sessionId}/done - 中转页等内部 webhook 处理完毕(最多 5 秒)
- 处理完了 → 跳到你的
successUrl,附带 query 参数:
GET https://your.site/orders/123
?session_id=sess_xxx
&record_id=rec_xxx # 仅成功时有
&paid_confirmed=1 # 1=已确认 / 0=尚未确认(5s 超时或客户 cancel 跳回)这意味着你的 successUrl 在大多数情况下落地时本地数据库已经 paid,不需要前端 polling。 极少数情况(webhook 慢 / 商户网络抖动)会拿到 paid_confirmed=0, 这时再 fallback 到 GET /api/v1/sessions/{id} 查一下即可。
providerHints — 多 ProviderConfig 路由
每种 provider(如微信支付)允许商户配多份凭证(例如「主号」「个体户副号」), 每份用 label 区分。不传 providerHints时,每种 provider 走它的默认那份(在 控制台 → 支付渠道里标星的那个)。要把这次 session 显式绑到某份,传 providerHints:
{
"amount": 9900,
"currency": "CNY",
"providerHints": {
"wechat": "个体户副号"
}
}- key 是 provider id(
wechat / creem等),value 是该 provider 下你起的label - 每个 session 最多绑 1 份 config(v1 一对一);多个 hint 只取第一个
- test 模式忽略 hints(强制 mock)
- session 一旦绑定,退款会自动走同一份 config(资金从哪进就从哪退)
常见错误:
400 unknown provider in hints: xxx— provider id 拼错或不在 ENABLED 列表400 provider wechat/主号 not available: invalid_label— 该 label 没配过400 provider wechat/主号 not available: config_disabled— 配过但已禁用
查询会话
查询某次会话的当前状态。已支付的会话会带 record 字段。会话只能被创建它的 Key 所属 merchant + 同环境(test/live)查询。
示例
curl https://pay.35team.com/api/v1/sessions/sess_01HXYZ... \
-H "Authorization: Bearer sk_test_xxx"响应 200
{
"id": "sess_01HXYZ...",
"status": "paid",
"url": "https://pay.35team.com/c/sess_01HXYZ...",
"amount": 9900,
"currency": "CNY",
"mode": "test",
"chosenProvider": "mock",
"expiresAt": "2026-04-28T07:00:00.000Z",
"createdAt": "2026-04-28T06:30:00.000Z",
"record": {
"id": "rec_01HXYZ...",
"provider": "mock",
"providerRef": "mock_pay_xxx",
"amount": 9900,
"status": "paid",
"paidAt": "2026-04-28T06:31:12.000Z"
}
}status 取值
pending— 已创建,等待用户支付processing— 用户已选好渠道、provider 端处理中(QR / JSAPI / hosted 跳转之后未确认前)paid— 已支付成功refunded— 曾经 paid,但事后被全额退款(语义上不再算 paid,详见退款)failed— 支付失败canceled— 用户主动取消expired— 超过expiresAt未支付
url 收银台只会展示当前结果,不能再发起新支付。 要再收一次款 → 创建新 session。amount 的最小单位约定
所有金额(请求 amount、响应 amount / refundedAmount、 webhook data.amount)都是整数最小单位。 是否乘 100 取决于币种:
9900 / 150。100,不要乘 100 写 10000。amount: 10000 给 JPY 会被当成 ¥10,000 真的扣给客户。 客户端展示金额时也别 amount / 100,用 amount 直接展示日元。 SDK 后续会把"按币种格式化"的辅助函数放出来,目前请按上表自己判断。发起退款
对一笔已支付的 PaymentRecord 发起全额或部分退款。退款只能由 创建这笔交易的同一 merchant + 同环境(sk_test_ 不能退 live 单,反之亦然)的 Key 发起。
请求体
amount 同单位,详见 最小单位约定)。留空表示退剩余可退全额。多次部分退款累计不能超过原单金额。示例
curl https://pay.35team.com/api/v1/refunds \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"recordId": "rec_01HXYZ...",
"amount": 9900,
"reason": "用户取消"
}'响应 200
返回更新后的 PaymentRecord(refundedAmount 与 status 已反映本次退款), 以及本次退款单 refund。
{
"id": "rec_01HXYZ...",
"sessionId": "sess_01HXYZ...",
"provider": "mock",
"providerRef": "mock_pay_xxx",
"amount": 9900,
"refundedAmount": 9900,
"currency": "CNY",
"status": "refunded",
"mode": "test",
"paidAt": "2026-04-28T06:31:12.000Z",
"createdAt": "2026-04-28T06:31:12.000Z",
"refund": {
"id": "ref_01HXYZ...",
"amount": 9900,
"status": "succeeded",
"providerRef": "mock_refund_xxx",
"reason": "用户取消",
"createdAt": "2026-04-28T06:40:00.000Z"
}
}refund.status 取值
succeeded— 退款已成功,record.status同步更新为refunded或partially_refundedpending— provider 异步处理中(如微信),record.status暂为refunding,最终结果会通过 webhook 通知failed— 退款失败,原单不变
session.status 也从 paid 翻成 refunded。 所以 GET /api/v1/sessions/{id} 看到 status: "refunded"意味着这笔单子有过支付但已被退完,不要当成"没付"处理。退款相关错误
404{ "error": "Not found" }— record 不存在 / 不属于当前 Key / 环境不匹配400{ "error": "amount exceeds refundable", "refundable": 1234 }— 退款金额超过剩余可退
错误响应
所有错误以 JSON 返回 { "error": ... }。常见状态码:
400— 请求体不合法(含字段级别的 zod 错误信息)401— Authorization 头缺失 / API Key 不合法或已撤销404— 资源不存在或不属于当前 Key500— 服务端错误