createWithdrawOrder 创建提现单
创建一笔提现订单。校验通过后平台在同一事务内扣减商户余额并建单,随后进入提现状态机,最终结果通过提现状态回调异步通知;也可随时用 checkWithdrawOrder 主动查询。
| 项目 | 值 |
|---|---|
| 接口地址 | POST https://api.anonypay.io/api/merchant/createWithdrawOrder |
| 请求体 | 业务 JSON 经 RSA-OAEP 加密后的 Base64 密文 |
| 请求头 | ANONY-APP-ID / ANONY-TOKEN / ANONY-TIMESTAMP / ANONY-NONCE / ANONY-SIGN(见协议规范) |
| 响应 | { "code": 10000, "data": "<密文>", "message": "success" },data 需验签后解密 |
请求参数
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
userOrder | string | 是 | 商户订单号(唯一;重复提交返回原订单,见幂等) |
address | string | 是 | 提现目标地址 |
amount | string | 是 | 提现金额;建议以字符串传递以避免精度问题;须 ≥ 该币种最低提现额 withdraw_min |
currency | string | 是 | 币种,见下方枚举 |
withdrawType | string | 是 | 提现类型:normal(普通)、financialConfirmation(财务确认) |
message | string | 否 | 备注 |
callbackUrl | string | 否 | 本单回调地址;不传则用商户默认(地址非法时报 invalidCallbackUrl) |
提现币种 currency 枚举(13 种):
trx trx.usdt eth eth.usdt eth.usdc solana solana.usdt solana.usdc bnb bnb.usdt bnb.usdc ton ton.usdt
各币种的手续费率与最低提现额可通过公共接口 getCurrencies 查询(withdraw_fixed_fee / withdraw_percent_fee / withdraw_min_fee / withdraw_max_fee / withdraw_min)。
TRON 链 USDC 充值已关闭
TRON 链上的 USDC 充值已关闭:向平台 TRON 地址转入 USDC 不入账、不回调。 提现币种枚举中也不包含 trx.usdc。TRON 链请仅使用 trx / trx.usdt;如需 USDC,请走 eth.usdc / solana.usdc / bnb.usdc。
响应字段(新建成功)
| 字段 | 类型 | 说明 |
|---|---|---|
apiOrder | string | 平台订单号 |
userOrder | string | 商户订单号 |
amount | string | 金额 |
currency | string | 币种 |
fee | string | 手续费 |
address | string | 目标地址 |
message | string | 备注 |
解密后的 data 示例:
{
"apiOrder": "202607081234567890",
"userOrder": "W20260708-0001",
"amount": "100.5",
"currency": "trx.usdt",
"fee": "1",
"address": "TXYZa1b2c3d4e5f6g7h8i9j0kLmNoPqRsT",
"message": "玩家提现"
}该响应仅代表下单成功(已扣款、订单已进入处理流程),不代表链上到账。提现最终结果以提现状态回调为准(见回调机制)。
幂等:重复提交同一 userOrder
幂等命中时返回的字段集不同
若 userOrder 已存在(重复提交),不会重复扣款,返回的是该原订单的当前状态——字段与提现状态回调一致(含 orderStatus / txid / toAddress 等),而不是上表的新建字段集(例如目标地址字段名是 toAddress 而非 address)。
请在代码中按 apiOrder / orderStatus 判断结果,勿假设字段集固定。这也意味着网络超时后用同一 userOrder 重试是安全的:要么创建新单,要么拿回原订单状态,不会重复扣款。
服务端校验链(按执行顺序)
下单请求依次通过以下校验,任一步失败即返回 code=9999 与对应 message:
- 平台自身地址拦截:目标地址命中平台自身热钱包/归集地址 →
withdrawAddressError - 幂等返回:
(userOrder, 商户号)已存在订单 → 直接返回原订单当前状态(不扣款,见上节) - withdrawType 合法性:不是
normal/financialConfirmation→withdrawTypeError - 财务确认前置条件:
withdrawType=financialConfirmation但商户尚未绑定审核 Telegram →auditorTelegramNotBound - 商户提现模式策略(模式由平台侧配置,见下节):模式
off一律拒绝 →merchantWithdrawDisabled;模式financialConfirmation强制财务确认,请求非财务确认 →onlySupportFinancialConfirmation;请求normal但商户模式不是normal→withdrawTypeNotAllowed - 币种:不在支持列表 →
currencyNotSupported;币种不存在 →currencyNotExist - 金额:格式/数值非法 →
invalidAmount;低于该币种最低提现额 →withdrawMinAmount - 地址格式:按币种规则校验目标地址 →
invalidAddress - 余额:商户余额 <
amount + fee→insufficientBalance - 日限额复检(仅
dailyLimit模式商户):当日累计 + 本单金额 > 日限额 →withdrawDailyLimitExceeded
全部通过后,在同一事务内扣款并创建订单;事务失败会整体回滚(不会出现"扣了款却没有订单"),返回 operationFailed。
实现细节
- 以上顺序来自服务端实现,可对照返回的
message定位卡在哪一步。个别检查的实际位置略有出入:第 5 步中"请求normal但模式非normal"的判断实际发生在币种校验之后;callbackUrl合法性校验(invalidCallbackUrl)位于模式策略与金额校验之间。排错时以message为准。 - 第 10 步日限额复检与扣款、建单加锁串行执行,并发下单无法绕过日限额;高并发时个别请求可能因等锁超时收到
operationFailed,重试(同一userOrder幂等)即可。
商户提现模式与 withdrawType 对应关系
商户账户有四种提现模式(平台侧配置,请求参数无法更改;如需调整请联系平台)。下单参数 withdrawType 能否通过,取决于当前模式:
| 商户提现模式 | 含义 | withdrawType=normal | withdrawType=financialConfirmation |
|---|---|---|---|
off | 提现关闭 | ❌ merchantWithdrawDisabled | ❌ merchantWithdrawDisabled |
normal | 普通模式 | ✅ 允许 | ✅ 允许(需已绑定审核 Telegram) |
financialConfirmation | 强制财务确认 | ❌ onlySupportFinancialConfirmation | ✅ 允许 |
dailyLimit | 日限额模式 | ❌ withdrawTypeNotAllowed | ✅ 允许(下单时复检当日累计不超日限额) |
无论商户处于哪种模式,只要请求
withdrawType=financialConfirmation,都要求商户已绑定审核 Telegram(否则auditorTelegramNotBound)。
提现状态机
订单创建后的状态流转(状态值即回调与 checkWithdrawOrder 中的 orderStatus):
normal(普通提现):
pendingConfirm ──► pendingTransfer ──► pendingCallback ──► success
(待确认) (转账中) (待回调) (成功)
financialConfirmation(财务确认提现):
pendingConfirm ──► pendingFinancialConfirmation ──► pendingCallback ──► success
(待确认) (待财务确认) (待回调) (成功)
旁路(未到终态的订单可能转入):
WithdrawRefund 拒绝并退款 fail 提现失败- 处于待确认阶段时,平台会向你回调
businessType=withdrawalPendingConfirm(无txid/orderStatus):应答SUCCESS放行、FAIL拒绝并退款(转入WithdrawRefund)。 - 到达终态后,平台回调
businessType=withdraw(含txid/orderStatus)。两类回调的报文与应答约定详见回调机制。
错误码
失败统一返回 code=9999,message 为下列错误标识:
| message | 含义 | 处理建议 |
|---|---|---|
withdrawAddressError | 目标地址为平台自身地址,拒绝 | 检查 address 是否填错 |
withdrawTypeError | withdrawType 不是 normal / financialConfirmation | 修正参数 |
auditorTelegramNotBound | 财务确认提现需先绑定审核 Telegram | 在商户后台绑定审核 Telegram 后重试 |
merchantWithdrawDisabled | 商户提现功能已关闭(模式 off) | 联系平台开启提现 |
onlySupportFinancialConfirmation | 商户被强制财务确认,仅接受 withdrawType=financialConfirmation | 改用财务确认提现 |
withdrawTypeNotAllowed | 当前商户模式不允许 normal 提现 | 对照上方模式表,改用允许的类型 |
currencyNotSupported | currency 不在支持列表(13 种枚举) | 修正币种 |
currencyNotExist | 币种不存在 | 修正币种 |
invalidAmount | 金额格式/数值非法 | 用字符串传合法正数金额 |
withdrawMinAmount | 低于最低提现额(message 含 最低提现金额为 :amount :symbol) | 金额提高至 withdraw_min 以上(可查 getCurrencies) |
invalidAddress | 地址不符合该币种格式 | 可先用公共接口 isAddress 预校验 |
insufficientBalance | 余额不足(需 ≥ amount + fee) | 查询 merchantInfo 余额,预留手续费 |
withdrawDailyLimitExceeded | 超出商户日限额(dailyLimit 模式) | 次日再试或联系平台调整限额 |
operationFailed | 扣款/建单失败,已整体回滚(未扣款) | 用同一 userOrder 重试(幂等安全),持续失败联系平台 |
鉴权、签名、防重放类通用错误(signatureFailed / tokenExpired 等)见错误与排查。
调用示例
require 'AnonyV2Client.php';
$client = new AnonyV2Client($appId, $serverPublicKeyB64, $merchantPrivateKeyB64);
$token = $client->getToken($secret); // 自动 md5 鉴权 + 验签解密取出 token
$res = $client->post('createWithdrawOrder', [
'userOrder' => 'W20260708-0001',
'address' => 'TXYZa1b2c3d4e5f6g7h8i9j0kLmNoPqRsT',
'amount' => '100.5',
'currency' => 'trx.usdt',
'withdrawType' => 'normal',
'message' => '玩家提现',
], $token);
// $res['data'] 已自动验签+解密
// 注意:若为幂等命中,字段集为回调结构(含 orderStatus/txid/toAddress)const { AnonyV2Client } = require('./anony_v2');
const c = new AnonyV2Client(appId, serverPubB64, merchantPrivB64);
const token = await c.getToken(secret); // Node ≥18:自动 md5 鉴权 + 验签解密取出 token
const req = c.buildRequest({
userOrder: 'W20260708-0001',
address: 'TXYZa1b2c3d4e5f6g7h8i9j0kLmNoPqRsT',
amount: '100.5',
currency: 'trx.usdt',
withdrawType: 'normal',
message: '玩家提现',
}, token);
const r = await fetch(c.baseUrl + 'createWithdrawOrder', {
method: 'POST', body: req.body, headers: req.headers,
});
const body = await r.json();
if (body.code === 10000) {
const data = c.verifyResponse(body.data, r.headers.get('anony-timestamp'),
r.headers.get('anony-nonce'), r.headers.get('anony-sign'));
// 幂等命中时 data 含 orderStatus/txid/toAddress,按 apiOrder/orderStatus 判断
}from anony_v2 import AnonyV2Client
import requests
c = AnonyV2Client(app_id, server_pub_b64, merchant_priv_b64)
# token 获取见 getToken 页(auth_header + 验签解密)
req = c.build_request({
"userOrder": "W20260708-0001",
"address": "TXYZa1b2c3d4e5f6g7h8i9j0kLmNoPqRsT",
"amount": "100.5",
"currency": "trx.usdt",
"withdrawType": "normal",
"message": "玩家提现",
}, token)
r = requests.post(c.base_url + "createWithdrawOrder", data=req["body"], headers=req["headers"])
body = r.json()
if body["code"] == 10000:
data = c.verify_response(body["data"], r.headers["ANONY-TIMESTAMP"],
r.headers["ANONY-NONCE"], r.headers["ANONY-SIGN"])
# 幂等命中时 data 含 orderStatus/txid/toAddress,按 apiOrder/orderStatus 判断Go / Java 用法(Encrypt/Sign/Canonical 等导出函数自行组包)见 SDK 总览。
相关页面
- getToken 获取令牌 — 业务接口前置步骤
- checkWithdrawOrder 查询提现单 — 主动查单
- 回调机制 —
withdraw/withdrawalPendingConfirm回调报文与应答约定 - 公共接口 —
getCurrencies费率/最低额、isAddress地址预校验 - 错误与排查