Z Zise Developers
POST /v1/cards/{id}/topups

充值到卡(两段异步:到卡以 webhook 为准)

Scope cards:write 代会员调用 · 必带 x-on-behalf-of x-idempotency-key 动钱 · 扣会员源资产可用余额 + 冻结商户预付;到卡由 webhook 确认
这个端点会动钱

失败处置见下方响应表。超时(504)用同一把幂等键重试——我方可能已经处理完;业务失败要换新键,同键会原样返回那次失败。

── 这个端点的成功不等于钱到卡 ──

上游 recharge 回 succeed 只表示「下单收到了」。钱真的到卡要等

card.topup.credited 这条 webhook。所以这里回的 status 绝大多数时候是

processing你不能据此给用户放行任何东西;同理,卡的限额也必须

等到账确认之后才下发,提前放开等于限额已开而钱没到。

── 失败与「结果不明」是两件事 ──

上游明确拒绝 → 我方当场解冻,你拿到业务失败码,钱一分没少。

网络层失败/超时 → 单子落 processing 交给巡检查证,我方绝不解冻

你这一侧也不要重试成第二笔,用同一把幂等键重发即可拿回同一张单。

── 幂等回放的是「原始结论」,不是「永远成功」──

同一把 x-idempotency-key 打进来,第一次失败的单第二次仍然回失败。

要重新发起就换一把新键。(曾经这里一律回成功,表现是「用户第二次点

显示成功、钱没到卡、流水上写着失败」。)

⚠ 扣的是会员的可用余额,同时冻结你的预付备付金。你的预付不够时,

会员看到的只是「暂时不可用」——不许把原因转达给终端用户

前置条件

  • 卡状态在可充值集合内(active / frozen / pending过渡态一律不行
  • 会员不在资金保护态(限额与上游失步时我方会先锁住这条路)
  • 会员该资产可用余额 ≥ customer_total
  • 你的预付余额够付这一单的批发成本

路径参数

字段类型必填说明
id string 必填 卡 id
字段类型必填说明
x-on-behalf-of string 必填 代哪个会员调用

请求体

字段类型必填说明
amount string 必填 基准额,源资产口径的十进制串(与试算同一个口径)。实际扣款是试算里的 customer_totalUSDT 为 6 位以内:"100.00"
source_asset string 可选 扣款资产。省略或不可用时按支付优先级自动挑(同试算:偏好而非指令)。

响应

201已受理。status 取值:processing(下单成功/结果待查证/排队中,都是这一档)· completed(共享额度卡当场结清才会出现)。看到 201 不等于到卡。
{
  "id": "ctp_4a8e5d21-90bc-4f37-b1e2-8d0c6a3f5719",
  "status": "processing"
}
400state_invalid 卡状态不允许充值 · product_not_available 产品或供应商不可用 · service_unavailable 发卡线未开通 / 你的资金账户被处置 · idempotency_key_required / idempotency_key_invalid余额不足、低于最低额、超单笔上限、上游拒绝、汇率不可用, 目前都以 500 api_error 返回(内部有码,未登记进对外目录)。 对 500 的正确处置是:用同一把键重发一次确认结论, 仍是 500 就当业务失败上报,不要循环重试。
409idempotency_key_reused · idempotency_in_progress
504upstream_timeout 结果不明 —— 用同一把幂等键重试,我方可能已经处理完

触发的事件

调用样例
curl -X POST 'https://api.zise.com/v1/cards/{id}/topups' \
  -H 'authorization: Bearer $TOKEN' \
  -H 'x-zise-merchant: $MERCHANT_ID' \
  -H 'x-on-behalf-of: $MEMBER_ID' \
  -H 'x-idempotency-key: $(uuidgen)' \
  -H 'content-type: application/json' \
  -d '{
    "amount": "100.00",
    "source_asset": "USDT"
  }'
const res = await fetch(
  "https://api.zise.com/v1/cards/{id}/topups",
  {
    method: "POST",
    headers: {
      "authorization": "Bearer $TOKEN",
      "x-zise-merchant": "MERCHANT_ID",
      "x-on-behalf-of": "MEMBER_ID",
      "x-idempotency-key": "crypto.randomUUID()",
      "content-type": "application/json"
    },
    body: JSON.stringify({
      "amount": "100.00",
      "source_asset": "USDT"
    }),
  },
);
// 金额一律按字符串读,不要 JSON.parse 成 number
const data = await res.json();
import requests

res = requests.post(
    "https://api.zise.com/v1/cards/{id}/topups",
    headers={
        "authorization": "Bearer $TOKEN",
        "x-zise-merchant": "$MERCHANT_ID",
        "x-on-behalf-of": "$MEMBER_ID",
        "x-idempotency-key": "$(uuidgen)",
        "content-type": "application/json"
    },
    json={
        "amount": "100.00",
        "source_asset": "USDT"
    },
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()