REST API

所有公开接口在 https://pay.35team.com 下,使用 JSON 编码。鉴权用Authorization: Bearer <API_KEY>

鉴权

控制台 → API Keys 创建 Secret Key。Key 形如sk_test_xxxsk_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 '{...}'
Secret Key 仅服务端使用。不要在浏览器、移动端或公开仓库里出现。泄露后请立刻在控制台撤销。

创建支付会话

POST/api/v1/checkout

创建一个支付会话,得到收银台 URL。把用户重定向到 url 即可。

请求体

amount
integerrequired
金额,最小单位。CNY/USD/EUR/HKD 是分(¥99.00 → 9900),JPY 等无小数币种是整数本位(¥100 → 100)。详见 下文
currency
'CNY' | 'USD' | 'EUR' | 'JPY' | 'HKD'required
货币代码。
description
string
订单描述,最多 200 字。
metadata
object
透传给 webhook 的自定义键值对。建议放你自己的 orderId
successUrl
string (URL)
客户付款成功后跳回的地址。不传则落到我们的收据页 /r/{recordId}(适合 PaymentLink / 不需要业务回调的场景)。传了则启用 sync 模式,客户落地时本地业务已 paid,无需 polling。
expiresInSeconds
integer
会话有效期(秒),最多 86400。
providerHints
object<provider, label>
多 ProviderConfig 路由,详见 下文。test 模式忽略。

示例

curl
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 — 配过但已禁用

查询会话

GET/api/v1/sessions/{id}

查询某次会话的当前状态。已支付的会话会带 record 字段。会话只能被创建它的 Key 所属 merchant + 同环境(test/live)查询。

示例

curl
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 未支付
每个 session 是一次性的:一旦进入 paid / refunded / failed / canceled / expired 任一终态, 客户再点同一个 url 收银台只会展示当前结果,不能再发起新支付。 要再收一次款 → 创建新 session。

amount 的最小单位约定

所有金额(请求 amount、响应 amount / refundedAmount、 webhook data.amount)都是整数最小单位。 是否乘 100 取决于币种:

CNY / USD / EUR / HKD
×100
有 2 位小数。¥99.00 / $1.50 → 9900 / 150
JPY
×1
无小数(日元最小单位是 1 円)。¥100 → 100不要乘 100 写 10000
最常见的接错:amount: 10000 给 JPY 会被当成 ¥10,000 真的扣给客户。 客户端展示金额时也别 amount / 100,用 amount 直接展示日元。 SDK 后续会把"按币种格式化"的辅助函数放出来,目前请按上表自己判断。

发起退款

POST/api/v1/refunds

对一笔已支付的 PaymentRecord 发起全额或部分退款。退款只能由 创建这笔交易的同一 merchant + 同环境(sk_test_ 不能退 live 单,反之亦然)的 Key 发起。

请求体

recordId
stringrequired
要退款的 PaymentRecord ID(rec_xxx),见 查询会话 返回的 record.id
amount
integer
退款金额,最小单位(与原单 amount 同单位,详见 最小单位约定)。留空表示退剩余可退全额。多次部分退款累计不能超过原单金额。
reason
string
退款原因,最多 80 字。

示例

curl
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

返回更新后的 PaymentRecordrefundedAmountstatus 已反映本次退款), 以及本次退款单 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 同步更新为 refundedpartially_refunded
  • pending — provider 异步处理中(如微信),record.status 暂为 refunding,最终结果会通过 webhook 通知
  • failed — 退款失败,原单不变
全额退款(不论本次接口同步成功还是 webhook 异步确认) 会把对应 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 — 资源不存在或不属于当前 Key
  • 500 — 服务端错误