Customers
代用户提现
先在本地冻结用户余额,再调用 POST /v1/user-withdrawals 提交链与收款地址;根据 withdrawal.complete 确认扣款,或在 withdrawal.failed 时解冻。
代用户提现
用户已经有 Customer,并且充值已进入你的商户资金池(同时你也在本地给用户记了余额)之后,可以通过 User Withdrawal 把资金打到用户自己的地址。
本文只讲 Integration 对接路径:一次请求里带上 chain 和 to_address。商户门户里的「Prepare → 邮件 → Hosted Claim」不在本文范围;对接接口调用 prepare 会得到 403 portal_only。
开始前请先完成:为用户创建客户并获取充值地址、Webhooks。
1. 整体流程
用户在 App 里申请提现
→ 你先冻结本地用户余额
→ POST /v1/user-withdrawals(Secret)
→ 状态变为 requested(商户资金池已扣减)
→ webhook:requested → submitted → complete 或 failed
→ complete:确认扣减本地冻结
→ failed:解冻本地用户余额
| 要点 | 说明 |
|---|---|
| 先有客户 | external_user_id 必须等于已存在 Customer 的 ref_id |
| 先动本地账 | 调 API 前,先在你的系统冻结 / 扣减用户余额 |
| 扣的是商户池 | 提现花费的是你的商户加密货币余额 |
| 不是商户自提出金 | 把钱提到你自己的冷钱包,是另一套 MerchantPayout |
2. 发起提现
POST /v1/user-withdrawals
鉴权: Secret + X-DoggiePay-Request-Id
强烈建议同时带 Idempotency-Key(若它是 UUID,有机会直接作为 withdrawal_id,方便和本地冻结单对齐)。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
external_user_id | string(UUID) | 是 | 等于 Customer 的 ref_id |
amount | string | 是 | 正数金额,例如 "10.00" |
currency | string | 是 | 例如 USDC |
chain | string | 是 | 到账链,例如 ETH |
to_address | string | 是 | 用户收款地址(每次可以不同) |
请求示例
curl -sS https://api.doggiepay.com/v1/user-withdrawals \
-H "Authorization: Bearer dp_live_sk_YOUR_SECRET" \
-H "X-DoggiePay-Request-Id: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
-H "Idempotency-Key: $(uuidgen | tr '[:upper:]' '[:lower:]')" \
-H "Content-Type: application/json" \
-d '{
"external_user_id": "6ded84e7-5449-4a2b-9c1d-0afcce047b29",
"amount": "10.00",
"currency": "USDC",
"chain": "ETH",
"to_address": "0xCustomerWallet..."
}'
成功时的 data
| 字段 | 类型 | 说明 |
|---|---|---|
withdrawal_id | uuid | 提现单 id |
status | string | 受理后通常为 requested |
常见错误
| HTTP | error.code | 含义 |
|---|---|---|
| 400 | invalid_external_user_id | 不是合法 UUID |
| 400 | invalid_to_address | 地址缺失或不合法 |
| 400 | below_min_withdrawal | 低于最低提现额 |
| 400 | unsupported_currency / chain_or_asset_disabled | 币种或链未就绪 |
| 404 | customer_not_found | 尚未创建对应 Customer |
| 422 | insufficient_balance | 商户资金池余额不足 |
| 422 | user_withdrawal_failed | 其他业务失败 |
3. 查询状态
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /v1/user-withdrawals/{id} | 单笔查询 |
GET | /v1/user-withdrawals?external_user_id=&status= | 按用户、状态筛选列表 |
鉴权:Secret + Request-Id。
4. Webhook 与本地账务怎么对齐
type | 时机 | 你建议怎么做 |
|---|---|---|
withdrawal.requested | 已受理,商户余额已扣 | 可记「处理中」;本地一般已先冻结 |
withdrawal.submitted | 已提交到通道 / 链上 | 可展示「广播中」 |
withdrawal.complete | 成功(常带 tx_hash) | 确认扣减本地冻结 |
withdrawal.failed | 失败,商户余额会退回 | 解冻本地用户余额 |
data 常见字段:withdrawal_id、external_user_id、amount、currency、chain、to_address;submitted / complete 可能还有链上相关字段;failed 含 failure_reason。
补充说明:
- 门户 Prepare 还会发
withdrawal.prepared(此时还没有最终收款地址)。纯 Integration 同步创建路径不会看到prepared。 - 务必验签,见 Webhooks。
5. Integration 不要调用这些接口
| 路径 | 原因 |
|---|---|
POST /v1/user-withdrawals/prepare | 仅门户可用(403 portal_only) |
POST /v1/user-withdrawals/{id}/resend-claim | 仅门户可用 |
公开 Claim 页 /w/:id | 面向门户邮件 Claim,不是服务端对接主路径 |
商户把自己的钱提到冷钱包,请用 MerchantPayout,不是本接口。
6. 上线前检查
- Customer 已创建,且
external_user_id===ref_id - 调用
POST /v1/user-withdrawals之前已冻结本地余额 - 超时重试用稳定的 Idempotency-Key / Request-Id
- 处理好
insufficient_balance、below_min_withdrawal、customer_not_found -
complete时确认扣款,failed时解冻 - Secret Key 只放在服务端
接下来可以把充值、提现的 webhook 处理收拢到同一处(见 Webhooks)。
