Skip to content

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 需验签后解密

请求参数

字段类型必填说明
userOrderstring商户订单号(唯一;重复提交返回原订单,见幂等
addressstring提现目标地址
amountstring提现金额;建议以字符串传递以避免精度问题;须 ≥ 该币种最低提现额 withdraw_min
currencystring币种,见下方枚举
withdrawTypestring提现类型:normal(普通)、financialConfirmation(财务确认)
messagestring备注
callbackUrlstring本单回调地址;不传则用商户默认(地址非法时报 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

响应字段(新建成功)

字段类型说明
apiOrderstring平台订单号
userOrderstring商户订单号
amountstring金额
currencystring币种
feestring手续费
addressstring目标地址
messagestring备注

解密后的 data 示例:

json
{
  "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

  1. 平台自身地址拦截:目标地址命中平台自身热钱包/归集地址 → withdrawAddressError
  2. 幂等返回(userOrder, 商户号) 已存在订单 → 直接返回原订单当前状态(不扣款,见上节)
  3. withdrawType 合法性:不是 normal / financialConfirmationwithdrawTypeError
  4. 财务确认前置条件withdrawType=financialConfirmation 但商户尚未绑定审核 Telegram → auditorTelegramNotBound
  5. 商户提现模式策略(模式由平台侧配置,见下节):模式 off 一律拒绝 → merchantWithdrawDisabled;模式 financialConfirmation 强制财务确认,请求非财务确认 → onlySupportFinancialConfirmation;请求 normal 但商户模式不是 normalwithdrawTypeNotAllowed
  6. 币种:不在支持列表 → currencyNotSupported;币种不存在 → currencyNotExist
  7. 金额:格式/数值非法 → invalidAmount;低于该币种最低提现额 → withdrawMinAmount
  8. 地址格式:按币种规则校验目标地址 → invalidAddress
  9. 余额:商户余额 < amount + feeinsufficientBalance
  10. 日限额复检(仅 dailyLimit 模式商户):当日累计 + 本单金额 > 日限额 → withdrawDailyLimitExceeded

全部通过后,在同一事务内扣款并创建订单;事务失败会整体回滚(不会出现"扣了款却没有订单"),返回 operationFailed

实现细节

  • 以上顺序来自服务端实现,可对照返回的 message 定位卡在哪一步。个别检查的实际位置略有出入:第 5 步中"请求 normal 但模式非 normal"的判断实际发生在币种校验之后;callbackUrl 合法性校验(invalidCallbackUrl)位于模式策略与金额校验之间。排错时以 message 为准。
  • 第 10 步日限额复检与扣款、建单加锁串行执行,并发下单无法绕过日限额;高并发时个别请求可能因等锁超时收到 operationFailed,重试(同一 userOrder 幂等)即可。

商户提现模式与 withdrawType 对应关系

商户账户有四种提现模式(平台侧配置,请求参数无法更改;如需调整请联系平台)。下单参数 withdrawType 能否通过,取决于当前模式:

商户提现模式含义withdrawType=normalwithdrawType=financialConfirmation
off提现关闭merchantWithdrawDisabledmerchantWithdrawDisabled
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=9999message 为下列错误标识:

message含义处理建议
withdrawAddressError目标地址为平台自身地址,拒绝检查 address 是否填错
withdrawTypeErrorwithdrawType 不是 normal / financialConfirmation修正参数
auditorTelegramNotBound财务确认提现需先绑定审核 Telegram在商户后台绑定审核 Telegram 后重试
merchantWithdrawDisabled商户提现功能已关闭(模式 off联系平台开启提现
onlySupportFinancialConfirmation商户被强制财务确认,仅接受 withdrawType=financialConfirmation改用财务确认提现
withdrawTypeNotAllowed当前商户模式不允许 normal 提现对照上方模式表,改用允许的类型
currencyNotSupportedcurrency 不在支持列表(13 种枚举)修正币种
currencyNotExist币种不存在修正币种
invalidAmount金额格式/数值非法用字符串传合法正数金额
withdrawMinAmount低于最低提现额(message 含 最低提现金额为 :amount :symbol金额提高至 withdraw_min 以上(可查 getCurrencies
invalidAddress地址不符合该币种格式可先用公共接口 isAddress 预校验
insufficientBalance余额不足(需 ≥ amount + fee查询 merchantInfo 余额,预留手续费
withdrawDailyLimitExceeded超出商户日限额(dailyLimit 模式)次日再试或联系平台调整限额
operationFailed扣款/建单失败,已整体回滚(未扣款)用同一 userOrder 重试(幂等安全),持续失败联系平台

鉴权、签名、防重放类通用错误(signatureFailed / tokenExpired 等)见错误与排查

调用示例

php
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)
js
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 判断
}
python
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 总览

相关页面