blockwallets文档English商户后台

文档

blockwallets 帮你收 USDT / USDC,以及 BTC / LTC(按锁定汇率折成美元入账):用户付款,我们在链上确认到账,然后通知你。接口、签名和回调与 Cryptomus 相同,接过 Cryptomus 的换个域名就能用。下面先讲怎么用,再讲接口。

两种收款方式

两种都能收任意金额:固定价格的订单、用户自己填金额的充值都可以,收到之后你给用户发货或加余额,让他慢慢用。区别只在于每次付款是不是新开一个地址。

推荐 · 大多数接入用这个账单

用户在你的页面选好商品或填好金额,你建一张账单,用户在收银台按显示的金额转账。每张账单一个新地址、有倒计时,到账后按实际收到的金额回调你。

POST /v1/payment → 回调 type: payment

可选静态钱包

不用先填金额:每个用户在每个网络一个长期地址,用户存进钱包随时转。每到一笔单独回调一次。

POST /v1/wallet → 回调 type: wallet

拿不准就用账单。不写代码的话,商户后台的收款链接就是一张张自动生成的账单。还没开通商户?在首页申请;开通后登录商户后台,建议先到「体验收款」付一小笔走一遍(钱进你自己的余额)。

一张账单是怎么走的

一笔收款牵涉四方:用户、你的网站、blockwallets 和区块链。下图从上往下按时间排,右边是账单在每一步的状态。

① 建单② 付款③ 确认④ 通知⑤ 收尾 用户浏览器 + 钱包你的网站页面 + 服务器blockwallets收银台 + 扫链区块链TRON 等 12 个网络 账单状态status 1点「充值」2POST /v1/payment3uuid + urlcheck4跳转到 url5选币种和网络6专属地址 + 金额7转账到这个地址8每分钟扫链9看到转账,等确认数confirm_check10第二家节点核对paid11POST url_callback12返回 2xx13验签,发货 / 加余额14跳回 url_success15记入余额

图比较宽,可以左右滑动查看。

15 步,从上往下按时间走。点「播放」看一遍,或点图里任意一步看说明。

简单说就是五步:

  1. 你创建账单(收款链接自动帮你建,或你的服务器调 /v1/payment),拿到收银台地址 url,把用户跳过去。
  2. 用户在收银台选币种(USDT / USDC / BTC / LTC)和网络,得到一个专属收款地址和金额,用钱包或交易所转账。
  3. 我们在链上看到转账,等够确认数,再找第二家节点核对一遍(防止节点作假)。
  4. 账单变成 paid,我们马上 POST 一条签名回调到你的 url_callback;收银台几秒后把用户送回你的 url_success。
  5. 钱记入你的余额,在商户后台申请提现到你自己的钱包。

账单状态

status意思你要做什么
check等待付款—
confirm_check看到转账了,在等确认—
wrong_amount_waiting已到账但还差一点,用户可以往同一地址补转(is_payment_multiple 为 true 时;只回调一次)—(补齐后会再回调 paid)
paid已收款按 payment_amount_usd 发货 / 加余额
paid_over已收款,而且多付了同上(payment_amount_usd 是实收金额)
wrong_amount到期了,收到的钱不够(is_payment_multiple 为 false 时,第一笔到账不够就是它)充值类业务可以按实收的 payment_amount_usd 入账;卖货的联系用户补款
cancel到期没收到钱—(之后才到的钱仍会入账并回调你)

is_final 为 true 时状态不会再变(晚到的钱除外)。

商户后台 →「收款链接」→ 填收款说明和金额(可以固定网络,也可以让用户自己选)→ 创建。每个链接有一个网址和一个二维码。

API 接入

1. 凭据与签名

商户后台 →「接入与开发者」→ 生成 merchant(UUID)和 API key。Live 和 Test 各一套:Live 收真钱,Test 走测试网。再填上你的网站域名:url_callback、url_return、url_success 都必须在这些域名下。API key 只显示一次,请保存好,只放在服务器上。

基础地址 https://blockwallets.io,全部接口都是 POST + JSON,请求头带:

成功返回 {"state": 0, "result": {…}},失败返回 {"state": 1, "message": "…"}(见错误)。

下面几个接口旁有「试一下」:填上 Test 的 merchant 和 API key,在浏览器里签名、直接调用,看到完整的请求和响应(凭据只留在页面内存里)。签名对不上时用在线验签逐步对比。

Node.js
import crypto from "node:crypto";

async function call(path, data) {
  const body = JSON.stringify(data);
  const sign = crypto.createHash("md5").update(Buffer.from(body).toString("base64") + process.env.BW_API_KEY).digest("hex");
  const res = await fetch("https://blockwallets.io" + path, {
    method: "POST",
    headers: { merchant: process.env.BW_MERCHANT, sign, "Content-Type": "application/json" },
    body,
  });
  return res.json();
}
PHP
function bw_call(string $path, array $data): array {
    $body = json_encode($data, JSON_UNESCAPED_UNICODE);
    $sign = md5(base64_encode($body) . getenv('BW_API_KEY'));
    $ch = curl_init('https://blockwallets.io' . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['merchant: ' . getenv('BW_MERCHANT'), 'sign: ' . $sign, 'Content-Type: application/json'],
    ]);
    return json_decode(curl_exec($ch), true);
}
Python
import base64, hashlib, json, os, requests

def bw_call(path: str, data: dict) -> dict:
    body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
    sign = hashlib.md5((base64.b64encode(body.encode()).decode() + os.environ["BW_API_KEY"]).encode()).hexdigest()
    headers = {"merchant": os.environ["BW_MERCHANT"], "sign": sign, "Content-Type": "application/json"}
    return requests.post("https://blockwallets.io" + path, data=body.encode(), headers=headers).json()

2. 创建账单 POST /v1/payment

用户下单,或在你的充值页填好金额后,调 https://blockwallets.io/v1/payment,把用户跳转到返回的 result.url。

json
{
  "amount": "42.00",
  "currency": "USD",
  "order_id": "order_1042",
  "url_callback": "https://your-site.com/webhooks/crypto",
  "url_return": "https://your-site.com/cart",
  "url_success": "https://your-site.com/thanks?order=1042"
}
参数必填说明
amount是金额,字符串,最多两位小数:"42.00"。可以是你定的价格,也可以是用户自己填的数;不能低于所选网络的最低额(见网络表)
currency是USD、USDT 或 USDC,三者 1:1
order_id是你的订单号,字母、数字、-、_,≤ 100 字。同一个 order_id 再调一次,原样返回已有的账单(防重复下单)
url_callback否回调地址。不传就不回调
url_return否用户付款前点「返回」去哪
url_success否付款成功后送回哪
network否预先指定网络:tron / bsc / eth / base / polygon / arbitrum / optimism / avalanche / xlayer / btc / ltc / sol;不传就让用户在收银台选
to_currency否配合 network 指定币种:USDT / USDC;btc / ltc 网络就是 BTC / LTC(可不传)
lifetime否账单有效期(秒),300 ~ 43200,默认 3600;不到 600 的按 600 算
accuracy_payment_percent否少付多少以内也算付清,0 ~ 5,默认 0。传 100 = 收多少算多少:到账确认后立即 paid(多了是 paid_over),按回调里的 payment_amount_usd 入账,适合充值。不传时按商户后台「收款金额规则」:选了「到多少记多少」就是 100
subtract否平台手续费里由用户承担的百分比,0 ~ 100,加进 payer_amount(平台费率 1% 起、按流水降档,按 1% 算传 100 时 100 美元的账单用户付 101.00;费率见 价格)。不传按商户后台「手续费与少付」,出厂 0
is_payment_multiple否少付后能不能补转,默认 true:状态 wrong_amount_waiting,用户往同一地址补齐就 paid(离到期不足 30 分钟时有效期顺延到 30 分钟后)。false:第一笔到账不够直接 wrong_amount。不传按商户后台设置
additional_data否任意字符串(≤ 255),原样出现在查询结果和回调里
json
{
  "state": 0,
  "result": {
    "uuid": "6f1c2a7e-…",
    "order_id": "order_1042",
    "amount": "42.00",
    "currency": "USD",
    "network": null,
    "address": null,
    "payer_amount": null,
    "payment_amount": null,
    "url": "https://blockwallets.io/pay/inv_01J8…",
    "expired_at": 1760965200,
    "status": "check",
    "is_final": false
  }
}

network、address、payer_amount 在用户选好网络之后才有。

3. 查询账单

回调是首选方式,查询用来兜底(比如你的服务器宕机错过了回调)。

4. 静态钱包 POST /v1/wallet

给一个用户在某个网络开一个长期地址。同一个 order_id(一般就是你的用户 id)+ 网络永远是同一个地址;所有 EVM 链(BNB Chain、Ethereum、Base、Polygon、Arbitrum、Optimism、Avalanche、X Layer、Solana)上是同一个地址,TRON、BTC、LTC 各一个。用户转多少到账多少,每笔都单独回调;BTC / LTC 每笔按那笔被看到时的汇率折成美元(payment_amount_usd)。

json
{
  "order_id": "user_42",
  "currency": "USDT",
  "network": "tron",
  "url_callback": "https://your-site.com/webhooks/crypto"
}
json
{
  "state": 0,
  "result": {
    "wallet_uuid": "0b9d…",
    "uuid": "5e2f…",
    "order_id": "user_42",
    "currency": "USDT",
    "network": "tron",
    "address": "TXy4…8kQ2",
    "url": "https://blockwallets.io/deposit/dac_01J8…"
  }
}

result.url 是托管的充值页(地址 + 二维码 + 最近到账),可以直接给用户。不到 $1 的转账不自动入账,在商户后台人工处理。暂不支持封禁地址(/v1/wallet/block-address)。

回调通知

账单状态变化(paid、paid_over、wrong_amount_waiting、wrong_amount、cancel)和静态钱包每到一笔,我们 POST 一条 JSON 到 url_callback。你的接口返回 2xx 就算送达;否则按 1 分钟、5 分钟、30 分钟、2 小时、12 小时、24 小时、24 小时重试,在商户后台或用 /v1/payment/resend 也能重发。

json
{
  "type": "payment",
  "uuid": "6f1c2a7e-…",
  "order_id": "order_1042",
  "amount": "42.00",
  "payment_amount": "42",
  "payment_amount_usd": "42.00",
  "merchant_amount": "41.832",
  "commission": "0.168",
  "is_final": true,
  "status": "paid",
  "from": "TQ8m…",
  "wallet_address_uuid": null,
  "network": "tron",
  "currency": "USD",
  "payer_currency": "USDT",
  "payer_amount": "42",
  "payer_amount_exchange_rate": "1",
  "additional_data": null,
  "txid": "9c1e…",
  "sign": "5f0c9b…"
}
type什么时候入账
payment账单状态变了,看 statuspaid / paid_over 时按 payment_amount_usd 入账,按 uuid 去重
wallet静态钱包到账一笔(status 总是 paid,order_id 是开钱包时给的)按 payment_amount_usd 入账;每笔 uuid 不同,按 uuid 去重

验证签名(必须做)

签名在回调体的 sign 字段里:去掉 sign,剩下的按 PHP json_encode 的格式重新编码(紧凑、/ 转义成 \/、中文不转义),md5(base64(编码结果) + API key) 应当等于 sign。用的是同一个 mode 的 API key。同一条回调可能重复,按 uuid + status 去重。

验不过时把收到的原始回调体贴进在线验签,它会列出每一步的结果并标出和你的不同之处(全程在浏览器里算,API key 不上传)。商户后台「回调日志」里每条回调都能直接在验签工具里打开。

Node.js
import crypto from "node:crypto";

function verify(rawBody, apiKey) {
  const { sign, ...data } = JSON.parse(rawBody);
  const json = JSON.stringify(data).replaceAll("/", "\\/");
  const expected = crypto.createHash("md5").update(Buffer.from(json).toString("base64") + apiKey).digest("hex");
  return typeof sign === "string" && sign.length === expected.length && crypto.timingSafeEqual(Buffer.from(sign), Buffer.from(expected));
}
PHP
$data = json_decode(file_get_contents('php://input'), true);
$sign = $data['sign'] ?? '';
unset($data['sign']);
$expected = md5(base64_encode(json_encode($data, JSON_UNESCAPED_UNICODE)) . $apiKey);
if (!hash_equals($expected, $sign)) {
    http_response_code(401); exit;
}
Python
import base64, hashlib, hmac, json

def verify(raw_body: bytes, api_key: str) -> bool:
    data = json.loads(raw_body)
    sign = data.pop("sign", "")
    encoded = json.dumps(data, separators=(",", ":"), ensure_ascii=False).replace("/", "\\/")
    expected = hashlib.md5((base64.b64encode(encoded.encode()).decode() + api_key).encode()).hexdigest()
    return hmac.compare_digest(expected, sign)

测试模式

用 Test 的 merchant 和 API key 建的账单走测试网,和真钱无关。收银台上会多出「模拟付款」按钮(付全款 / 只付一半 / 多付),不用领测试币就能看到状态变化和回调。

从 Cryptomus 迁移

  1. 商户后台「接入与开发者」→ 生成 merchant 和 API key。
  2. 把代码里的 https://api.cryptomus.com 换成 https://blockwallets.io,换上新的 merchant 和 API key。
  3. 在「网站域名」里填上你的域名(url_callback / url_return / url_success 只接受这些域名)。

支持:/v1/payment、/v1/payment/info、/v1/payment/list、/v1/payment/services、/v1/payment/resend、/v1/wallet、/v1/test-webhook/payment、/v1/test-webhook/wallet。 不同之处:账单只按 USD / USDT / USDC 计价(三者 1:1);commission 是我们的平台手续费(实收 × 建单时商户的费率,标准 1%、按流水最低 0.5%;付款人承担的那部分不再收),merchant_amount = 实收 − commission;用户选 BTC / LTC 付时,按多家交易所的中位数报价、锁定 30 分钟,payer_amount 是要付的币数,payer_amount_exchange_rate 是 1 个币值多少美元,到账按锁定汇率折成 payment_amount_usd(币留在我们这里,不做兑换);网络 tron / bsc / eth / base / polygon / arbitrum / optimism / avalanche / xlayer / btc / ltc / sol;账单有效期最短 10 分钟;不支持 is_refresh 和封禁静态钱包;回调来源 IP 不固定,请用 sign 验签,不要按 IP 白名单。

从 NOWPayments 或 Coinbase Commerce 过来?我们也有这两家的兼容接口,换 base URL 和密钥即可,见迁移指南。

网络与金额

币种网络network 参数最低金额到账
USDTTRONtron$1约 1 分钟
USDTBNB Chainbsc$1约 1 分钟
USDCBNB Chainbsc$1约 1 分钟
USDTEthereumeth$5约 10 分钟
USDCEthereumeth$5约 10 分钟
USDCBasebase$1约 3 分钟
USDTPolygonpolygon$1约 1 分钟
USDCPolygonpolygon$1约 1 分钟
USDTArbitrumarbitrum$1约 12 分钟
USDCArbitrumarbitrum$1约 12 分钟
USDTOptimismoptimism$1约 5 分钟
USDCOptimismoptimism$1约 5 分钟
USDTAvalancheavalanche$1约 1 分钟
USDCAvalancheavalanche$1约 1 分钟
USDTX Layerxlayer$1约 2 分钟
USDCX Layerxlayer$1约 2 分钟
BTCBitcoinbtc$5约 10 分钟
LTCLitecoinltc$1约 8 分钟
TRXTRONtron$1约 1 分钟
BNBBNB Chainbsc$1约 1 分钟
ETHEthereumeth$5约 10 分钟
ETHBasebase$1约 3 分钟
ETHArbitrumarbitrum$1约 12 分钟
ETHOptimismoptimism$1约 5 分钟
POLPolygonpolygon$1约 1 分钟
AVAXAvalancheavalanche$1约 1 分钟
OKBX Layerxlayer$1约 2 分钟
USDTSolanasol$1约 1 分钟
USDCSolanasol$1约 1 分钟
SOLSolanasol$1约 1 分钟

ERC-20 手续费贵,最低 $5;BTC 链上手续费也不低,最低 $5;其余 $1。用户要按收银台显示的网络转,转错网络的钱不会自动到账。 BTC / LTC 在内存池里看到就显示「已检测到」,够确认数才算到账;被替换(RBF)或掉出内存池的交易不算。 各链原生币(TRX、BNB、ETH、POL、AVAX、OKB)和 BTC / LTC 一样按锁定汇率报价、按美元入账;要写明网络和币种:建单 network: "bsc", to_currency: "BNB",静态钱包 network: "bsc", currency: "BNB";只写网络默认是这条链上的 USDT(Base 是 USDC)。 合约转出的(多签、批量出款)也能收到;个别链上拿不到这类转账的交易哈希,回调里 txid 为 null。

错误

json
{ "state": 1, "message": "amount must be a number like 10.28" }
HTTP常见原因
422参数缺失或格式不对;金额低于该网络最低额;回调 / 跳转地址不在你登记的域名下
401merchant 不对、sign 算错(Invalid Sign.)或 API key 已轮换
404账单不存在或不属于你(Payment not found);不支持的币种 / 网络组合(The service was not found)
503这个网络暂时不可用,换一个或不指定网络

签名错(Invalid Sign.)时,应答里多一个 debug:body_base64 是我们收到的请求体的 base64(就是你发的内容,不含 API key),body_bytes 是字节数。拿它和你签名时的 base64 比:不一样,说明发出去的 body 和签名的不是同一串(框架重新序列化、代理改了编码、多了换行);一样,就是 API key 不对。贴进在线验签能直接标出第一处不同。

json
{
  "state": 1,
  "message": "Invalid Sign.",
  "debug": {
    "body_base64": "eyJhbW91bnQiOiI0Mi4wMCIsImN1cnJlbmN5IjoiVVNEIiwi…",
    "body_bytes": 158,
    "sign_formula": "md5(body_base64 + API key)"
  }
}

常见问题

用户想充多少就充多少,用哪种?

用账单就行:你的充值页让用户填金额,用这个金额建账单,到账后按 payment_amount_usd 加余额。想让用户把一个地址存起来反复转,再用静态钱包。

用户少付了怎么办?

到期前用户可以在收银台补转差额(收银台会显示「还差多少」,状态 wrong_amount_waiting;第一次少付时离到期不足 30 分钟的,有效期顺延到 30 分钟后)。到期还不够,账单变成 wrong_amount,回调里的 payment_amount_usd 是实收金额;充值类业务直接按它入账即可。

用户付晚了、或者转了两次?

过期后才到的钱照样入账并回调你;付清后又到的钱也会记录,在商户后台能看到。

用户转错网络了?

比如账单是 TRON,用户从 BNB Chain 转:钱不会自动到账,联系我们人工处理。

收费多少?

每笔实收收 1% 平台手续费,上个月流水大的自动降档(满 $10,000 降到 0.9%、满 $25,000 降到 0.8%、满 $50,000 降到 0.7%、满 $100,000 降到 0.6%、满 $200,000 降到 0.5%),没有月费、开户费。链上网络费由付款人转账时自付;想让付款人承担手续费,建单传 subtract 或在商户后台设默认值。提现不另收手续费。详见 价格。

钱什么时候到我手里?

付款确认后马上记入余额。在「余额与提现」申请提现到你自己的钱包,我们审核通过后出款,出款后能在提现记录里看到链上交易。

也可以开自动提现:可提余额满你设的金额就自动提,或者每天 UTC 0 点(北京时间 8 点)自动提一次,每次提出当时的全部可提余额。自动开出的提现单一样要我们审核;每次修改设置,都会给你的邮箱发一封通知。