Merchant API

BRPAY Merchant API 接入文档

API:商户 API · Java / PHP SDK:版本见交付包 · 更新日期:2026-11-06

1. 快速接入

本接口用于游戏充值和提现。订单、余额、流水和回调均使用 BRC。1 BRC = 1 BRL 是平台计价关系;本接口处理 BRPAY 钱包内的 BRC 划转。文中域名、账号、数量和费率均为示例。

向平台获取服务端接入参数:

参数 说明
baseUrl 商户 API 的 HTTPS 根地址,不含路径、查询串或结尾斜线。以平台提供的地址为准。
merchantId 平台分配的商户号,格式为大写 M + 8 位大写字母或数字。
signingSecret 32–512 个 UTF-8 字节。只保存在商户服务端,用于 API 签名和回调验签。
  1. 准备接入资料。 先向平台领取上表三项。需要异步通知时,每笔订单都传 notifyUrl;不传就按商户单号主动查单。
  2. 先查一次余额。 同步服务端时间,运行签名请求示例。能拿到 availableBalance,再联调下单。
  3. 充值和提现各跑一笔。 充值:保存自己的订单 → 建代收单 → 打开 cashierUrl → 查单或验签确认到账后给用户充值。提现:保存自己的订单 → 核对收款钱包 → 提交代付 → 收到明确 SUCCESS 即可确认到账。
  4. 再试三种异常。 代收过期不充值;同一回调重复到达只处理一次;下单超时先查原商户单号。
功能 路径 SDK 方法
代收充值 /api/merchant/orders/payin payin(...)
代付提现 /api/merchant/orders/payout payout(...)
订单查询 /api/merchant/orders/query queryOrder(...) / querySystemOrder(...)
接单状态查询 /api/merchant/orders/availability queryAvailability(...)
商户余额查询 /api/merchant/account/balance queryBalance(...)
商户资金流水 /api/merchant/account/transactions queryTransactions(...)
钱包校验 /api/merchant/wallets/query queryWallet(...)

2. 公共协议

2.1 请求格式

项目 规则
协议 / 方法 HTTPS / POST;仅本机开发允许回环 HTTP
Content-Type application/json; charset=utf-8
字符编码 / 正文上限 UTF-8 / 8192 字节;必须是 JSON 对象,拒绝重复键与未知字段
数量 正十进制字符串,最多 17 位有效整数位,必须精确到分。允许前导零和小数末尾多余的零,例如 "00030.1200" 按 "30.12" 建单;不接受 JSON 数字、科学计数法或不足一分的尾数。
时间 UTC RFC3339,以 Z 结尾,例如 2026-11-05T04:00:00Z
协议演进 忽略新增可选响应/回调字段,按现行回调结构校验

所有接口使用同一组请求头:

请求头 格式 说明
X-Merchant-Id 大写 M 加 8 位大写字母或数字 平台商户号
X-Timestamp 10 位 Unix 秒 当前时间,允许 300 秒偏差
X-Request-Id 小写 UUID 每次发送 HTTP 请求时生成一个,用于排查。同一笔订单的商户单号保留不变。
X-Signature 64 位小写 hex HMAC-SHA256;同名认证头不允许重复

2.2 请求签名

签名只在商户服务端做。先把 JSON 转成 UTF-8 字节,对这份正文求 SHA-256;签名和 HTTP 发送使用同一份正文。把下面 7 行用 \n(LF)连接,最后一行之后不加换行:

BP-MERCHANT
POST
<full-request-path>
<merchantId>
<epochSeconds>
<requestId>
<sha256Hex(rawBodyBytes)>

<full-request-path> 填实际请求路径,例如 /api/merchant/orders/payin,不带域名、查询串或额外的斜线。不要把 JSON 解析后重新排版再签,也不要把签名中的 LF 换成 Windows 的 CRLF。

可运行示例:签名并查询余额(只读)

将下方保存为 merchant-balance.mjs。使用支持内置 fetch 的 Node.js(18+),在服务端设置 BRPAY_BASE_URL、BRPAY_MERCHANT_ID、BRPAY_SIGNING_SECRET 后运行 node merchant-balance.mjs。成功时打印可用 BRC 余额。使用平台发给你的新版 API 地址;本机 8109 是 DEMO 页面端口。

import { createHash, createHmac, randomUUID } from 'node:crypto';

const { BRPAY_BASE_URL, BRPAY_MERCHANT_ID, BRPAY_SIGNING_SECRET } = process.env;
if (!BRPAY_BASE_URL || !/^M(?!0{8}$)[0-9A-Z]{8}$/.test(BRPAY_MERCHANT_ID ?? '') ||
    !BRPAY_SIGNING_SECRET) {
  throw new Error('Set BRPAY_BASE_URL, BRPAY_MERCHANT_ID and BRPAY_SIGNING_SECRET');
}
const origin = new URL(BRPAY_BASE_URL);
const loopback = origin.protocol === 'http:' &&
  ['127.0.0.1', 'localhost', '[::1]'].includes(origin.hostname);
if ((!loopback && origin.protocol !== 'https:') || origin.pathname !== '/' ||
    origin.search || origin.hash || origin.username || origin.password) {
  throw new Error('BRPAY_BASE_URL must be the API origin (HTTPS or loopback HTTP)');
}
const path = '/api/merchant/account/balance';
const body = Buffer.from(JSON.stringify({}), 'utf8');
const timestamp = String(Math.floor(Date.now() / 1000));
const requestId = randomUUID();
const bodyHash = createHash('sha256').update(body).digest('hex');
const text = ['BP-MERCHANT', 'POST', path, BRPAY_MERCHANT_ID,
  timestamp, requestId, bodyHash].join('\n');
const signature = createHmac('sha256', BRPAY_SIGNING_SECRET).update(text, 'utf8').digest('hex');

const response = await fetch(origin.origin + path, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
    'X-Merchant-Id': BRPAY_MERCHANT_ID,
    'X-Timestamp': timestamp,
    'X-Request-Id': requestId,
    'X-Signature': signature
  },
  body,
  redirect: 'error',
  signal: AbortSignal.timeout(30_000)
});
const reply = await response.json();
if (response.status !== 200 || reply?.success !== true || reply.code !== 'OK' ||
    reply.requestId !== requestId || reply.data?.currency !== 'BRC' ||
    typeof reply.data.availableBalance !== 'string') {
  throw new Error(`HTTP ${response.status}; code=${reply?.code ?? '(missing)'}; requestId=${requestId}`);
}
console.log(reply.data.availableBalance);

固定签名值: 下面两段用于比较你自己代码算出的正文哈希和签名。

signingSecret = example-only-signing-secret-32-bytes

BP-MERCHANT
POST
/api/merchant/orders/payin
M00000325
1793851200
aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee
74cbca80326f6565ae3a2f5c16607f96b8f42d13e30b7c99290ef75c8a05c07a

X-Signature: 8c5374d38568c2c84d1ca15d101b6c902f32398f83a45f9edbc30ac45b7b5c8c

完整 HTTP 请求(离线验签用): 使用上面的示例密钥。请求正文是下面的单行 JSON,不含结尾换行。固定时间只用于比对签名。

POST /api/merchant/orders/payin HTTP/1.1
Host: api.example.com
Content-Type: application/json; charset=utf-8
X-Merchant-Id: M00000325
X-Timestamp: 1793851200
X-Request-Id: aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee
X-Signature: 8c5374d38568c2c84d1ca15d101b6c902f32398f83a45f9edbc30ac45b7b5c8c
Content-Length: 86

{"merchantOrderNo":"RECHARGE-001","orderAmount":"30.00","description":"Game recharge"}

2.3 统一响应

成功时返回 HTTP 200、success: true 和 code: "OK"。每个接口下方都有完整响应示例;业务字段放在 data 里。对下单接口,HTTP 200 只说明请求成功处理,订单是否已完成要看 data.status。

失败时 success: false、data: null,按 HTTP 状态和 code 处理。message 给人看,不用于程序分支。requestId 回显本次请求的追踪号(未提供时可能为 null),排查时把它和商户单号一并提供给平台。timestamp 是平台返回响应的 UTC 时间。

例如签名错误:HTTP 401 / INVALID_SIGNATURE。

{
  "success": false,
  "code": "INVALID_SIGNATURE",
  "message": "Invalid signature",
  "requestId": "99999999-aaaa-4bbb-8ccc-dddddddddddd",
  "timestamp": "2026-11-05T04:06:00Z",
  "data": null
}

3. 订单、费用与状态

下单回执、支付结果、通知状态是三件事。 代收建单成功时通常是 PENDING,此时未扣款;代付返回 SUCCESS 即已到账。notificationStatus 只表示回调送达情况,即使是 EXHAUSTED,也不改变资金结果。

业务 用户 BRC 余额 商户 BRC 余额 何时确认成功
代收(PAYIN) 付款时扣除订单数量 入账订单数量减手续费 查单返回 SUCCESS,或收到验签通过的成功回调
代付(PAYOUT) 入账订单数量 扣除订单数量加手续费 代付成功响应、查单 SUCCESS 或验签通过的成功回调

手续费 = 取「最小手续费」和「订单数量 × 费率 + 固定费(四舍五入保留 2 位小数)」中的较大值。代收手续费必须小于订单数量。平台在建单时保存本单费用,后续调价不影响已建订单。通过接单状态查询读取当前费率和数量范围。

商户单号用于标识一笔业务,同一商户的代收和代付不能重用该单号。收到 ORDER_EXISTS 应查询原订单,核对方向、数量及代付收款钱包;冲突回执本身不代表本次请求成功。

4. 接口详情

商户代收订单统一为 PENDING(处理中/待支付)、SUCCESS(订单付款成功)、FAILED(订单已确认失败)。failureCode 在失败时必返,当前订单过期使用 ORDER_EXPIRED;非失败为 null。只有成功或失败终态生成回调,初次结果事件为 ORDER_SUCCEEDED 或 ORDER_FAILED;后续更正使用 ORDER_CORRECTED。钱包充值成功或失败、余额不足及取消自动付款授权均不直接结束仍可付款的商户订单;请求超时不等于订单失败,须查原订单确认。建单被拒且未创建订单时仅返回接口错误,不推送订单失败回调。

4.1 代收充值

POST /api/merchant/orders/payin · SDK payin(...)

用户要为游戏账户充值时调用。接口受理后返回待支付订单;请保存两个单号,把 cashierUrl 交给用户打开(原生 App 可用 appUrl)。建单时用户和商户余额都不变,验签并核对 ORDER_SUCCEEDED,或查单确认 SUCCESS 后,再给用户充值。

只建代收单时,最小请求为 {"merchantOrderNo":"RECHARGE-001","orderAmount":"30.00"}。

回调地址: 按需在本次请求中提供 notifyUrl 和 returnUrl,不再配置到商户资料。完整规则见结果回调。

请求

请求字段 类型 必填 说明
merchantOrderNo string 是 商户为本笔业务生成的单号。1–64 位,匹配 [A-Za-z0-9][A-Za-z0-9._-]{0,63},区分大小写。同一商户的代收和代付共用编号空间,每笔业务只用一个单号。
orderAmount string 是 正十进制字符串,最多 17 位有效整数位,必须精确到分。允许前导零和小数末尾多余的零,例如 "00030.1200" 按 "30.12" 建单;不接受 JSON 数字、科学计数法或不足一分的尾数。
description string / null 否 业务说明。可省略或传空字符串;省略时保存为空字符串。最多 160 个 UTF-16 代码单元,不含 ASCII 控制字符;null 同省略。
notifyUrl string / null 否 异步通知地址;省略不发送通知。完整 HTTPS URL,最多 512 字符。
returnUrl string / null 否 同步返回商户页面;省略不展示返回入口。完整 HTTPS URL,最多 512 字符。
{
  "merchantOrderNo": "RECHARGE-001",
  "orderAmount": "30.00",
  "description": "Game recharge",
  "notifyUrl": "https://merchant.example.com/webhooks/brpay",
  "returnUrl": "https://merchant.example.com/orders/RECHARGE-001"
}

成功响应 data

初次建单通常返回 PENDING,示例费率为 1% + 0.10 BRC。以 expiresAt 为准,勿在商户端写死有效期。查询发现订单已过期时停止引导付款。

响应字段 类型 说明
orderNo string 平台代收单号,RC + 14 位数字。
merchantOrderNo string 原请求的商户单号。
direction string 固定 PAYIN。
currency string 固定 BRC。
orderAmount string 用户应支付的 BRC 数量。
feeAmount string 建单时确定的手续费,单位 BRC,可为 "0.00"。
merchantAmount string 成功后商户净入账数量:orderAmount - feeAmount,单位 BRC。
actualAmount string / null 待支付时为 null;付款成功后为用户实际扣除的 BRC 数量。
status string 建单后为 PENDING(待支付)。最终结果看查单或回调。
failureCode string / null 失败原因;FAILED 时必返,当前过期为 ORDER_EXPIRED;其他状态为 null。
walletId string / null 待支付时为 null;付款成功后为实际付款的钱包账号(W + 8 位大写字母或数字)。
createdAt string 建单时间,UTC。
expiresAt string 付款截止时间,UTC。到期后不能支付。
completedAt string / null 待支付时为 null;付款或过期后可查到终态时间。
orderRevision integer 订单结果修订号;初始为 0,更正后递增。
notificationStatus string / null 待支付时为 null,尚无结果回调。
cashierUrl string BRPAY 收银页,用于 Web 付款、打开 App 或在 App 内扫码。
appUrl string 原生 App 付款链接,brazilpay://cashier/<token>,凭证为 22 个 Base64URL 字符,与 cashierUrl 一致。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee",
  "timestamp": "2026-11-05T04:00:00Z",
  "data": {
    "orderNo": "RC00000000128364",
    "merchantOrderNo": "RECHARGE-001",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": null,
    "status": "PENDING",
    "walletId": null,
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": null,
    "orderRevision": 0,
    "notificationStatus": null,
    "cashierUrl": "https://payments.example.com/pay/aaaaaaaaaaaaaaaaaaaaaA",
    "appUrl": "brazilpay://cashier/aaaaaaaaaaaaaaaaaaaaaA",
    "failureCode": null
  }
}

异常处理: ORDER_EXISTS、超时或无有效回执时先按原商户单号查单;数量不合规时核对接单范围和费用。

4.2 代付提现

POST /api/merchant/orders/payout · SDK payout(...)

用户从游戏提现到 BRPAY 钱包时调用。先用钱包校验确认 exists=true、canReceive=true,并核对用户选定的收款账号。HTTP 200 表示订单已受理,初始状态为 PENDING,同时占用本金和手续费。只有查单或回调的 SUCCESS 才表示到账;FAILED 表示失败且占用已释放。请求被拒且未建单不会占用资金。

回调地址: 按需在本次请求中提供 notifyUrl 和 returnUrl,不再配置到商户资料。完整规则见结果回调。

请求

请求字段 类型 必填 说明
merchantOrderNo string 是 商户为本笔业务生成的单号。1–64 位,匹配 [A-Za-z0-9][A-Za-z0-9._-]{0,63},区分大小写。同一商户的代收和代付共用编号空间,每笔业务只用一个单号。
orderAmount string 是 正十进制字符串,最多 17 位有效整数位,必须精确到分。允许前导零和小数末尾多余的零,例如 "00030.1200" 按 "30.12" 建单;不接受 JSON 数字、科学计数法或不足一分的尾数。
recipientWalletId string 是 钱包校验返回的 walletId,格式为 W + 8 位大写字母或数字。
description string / null 否 业务说明。可省略或传空字符串;省略时保存为空字符串。最多 160 个 UTF-16 代码单元,不含 ASCII 控制字符;null 同省略。
notifyUrl string / null 否 异步通知地址;省略不发送通知。完整 HTTPS URL,最多 512 字符。
returnUrl string / null 否 同步返回商户页面;省略不展示返回入口。完整 HTTPS URL,最多 512 字符。
{
  "merchantOrderNo": "WITHDRAW-001",
  "orderAmount": "20.00",
  "description": "Game withdrawal",
  "recipientWalletId": "W00012836",
  "notifyUrl": "https://merchant.example.com/webhooks/brpay",
  "returnUrl": "https://merchant.example.com/orders/WITHDRAW-001"
}

成功响应 data

示例费率为 2% + 0.20 BRC。受理返回 PENDING;下方是最终查单成功的示例。确认 SUCCESS 即表示已到账,不需要再等待回调。代付不返回 cashierUrl 或 appUrl。

响应字段 类型 说明
orderNo string 平台代付单号,PY + 14 位数字。
merchantOrderNo string 原请求的商户单号。
direction string 固定 PAYOUT。
currency string 固定 BRC。
orderAmount string 用户应收到的 BRC 数量。
feeAmount string 建单时确定的手续费,单位 BRC,可为 "0.00"。
merchantAmount string 商户需占用及结算的数量:orderAmount + feeAmount,单位 BRC。
actualAmount string / null 仅 SUCCESS 为用户实际到账数量;PENDING / FAILED 为 null。
status string PENDING 已受理处理中、SUCCESS 已到账、FAILED 已失败且占用已释放。
failureCode string / null FAILED 的明确失败原因;其余状态为 null。
walletId string 本单指定的收款钱包(W + 8 位大写字母或数字),各状态均返回并对应 recipientWalletId;只有 SUCCESS 才表示到账。
createdAt string 建单时间,UTC。
expiresAt null 固定 null,代付没有付款期限。
completedAt string / null 终态完成时间,UTC;PENDING 为 null。
orderRevision integer 订单结果修订号;初始为 0,更正后递增。
notificationStatus string / null PENDING 待发送或重试、DELIVERED 已收到 2xx、EXHAUSTED 自动尝试耗尽。与资金状态独立。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "22222222-3333-4444-8555-666666666666",
  "timestamp": "2026-11-05T04:05:00Z",
  "data": {
    "orderNo": "PY00000000128365",
    "merchantOrderNo": "WITHDRAW-001",
    "direction": "PAYOUT",
    "currency": "BRC",
    "orderAmount": "20.00",
    "feeAmount": "0.60",
    "merchantAmount": "20.60",
    "actualAmount": "20.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:05:00Z",
    "expiresAt": null,
    "completedAt": "2026-11-05T04:05:00Z",
    "failureCode": null,
    "orderRevision": 0,
    "notificationStatus": "PENDING"
  }
}

异常处理: WALLET_NOT_FOUND、ACCOUNT_DISABLED 或 WALLET_LIMIT_EXCEEDED 时核对收款钱包;MERCHANT_INSUFFICIENT_BALANCE 时查余额。ORDER_EXISTS、超时或无有效回执时先用原商户单号查单,核对方向、数量及收款钱包。

4.3 订单查询

POST /api/merchant/orders/query · SDK queryOrder(...)(商户单号)/querySystemOrder(...)(平台单号)

用商户单号或平台单号查询当前商户的订单。用于处理回调未到、下单超时或对账。以 status 判断资金结果,notificationStatus 只说明回调投递进度。

请求

请求字段 类型 必填 说明
merchantOrderNo string 二选一 原商户单号,1–64 位,匹配 [A-Za-z0-9][A-Za-z0-9._-]{0,63},区分大小写。
orderNo string 二选一 BRPAY 平台单号:代收 RC 或代付 PY + 14 位数字。

两种请求示例,每次只发送其中一种:

{
  "merchantOrderNo": "RECHARGE-001"
}
{
  "orderNo": "RC00000000128364"
}

成功响应 data

示例为已付款且回调已确认的代收订单。表内字段均返回;付款链接仅代收返回,即使订单已终态也可能带有链接,只有 PENDING 时才应引导付款。

响应字段 类型 说明
orderNo string 平台单号,代收 RC、代付 PY,前缀后接 14 位数字。
merchantOrderNo string 原请求的商户单号。
direction string PAYIN 代收或 PAYOUT 代付。
currency string 固定 BRC。
orderAmount string 原订单的 BRC 数量。
feeAmount string 建单时确定的手续费,单位 BRC,可为 "0.00"。
merchantAmount string 正数,单位 BRC:代收为成功后商户净入账,代付为商户含手续费支出。
actualAmount string / null 仅 SUCCESS 时返回实际从用户扣除(代收)或转给用户(代付)的 BRC 数量;其余状态为 null。
status string 代收为 PENDING(待支付)、SUCCESS(已付款)或 FAILED(已失败,原因见 failureCode);代付为 PENDING(处理中)、SUCCESS(已到账)或 FAILED(已失败且占用已释放)。
failureCode string / null 失败原因;FAILED 时必返,当前过期为 ORDER_EXPIRED;其他状态为 null。
walletId string / null 代付各状态均为收款钱包账号;代收返回实际付款钱包;未付款为 null,更正为失败时可能保留原钱包。格式为 W + 8 位大写字母或数字。
createdAt string 建单时间,UTC。
expiresAt string / null 代收付款截止时间(UTC);代付为 null。
completedAt string / null 成功时为完成时间(UTC);代收过期时等于 expiresAt,待支付时为 null。
orderRevision integer 订单结果修订号;初始为 0,更正后递增。
notificationStatus string / null PENDING 待发送或重试、DELIVERED 已收到 2xx、EXHAUSTED 自动尝试耗尽;null 表示尚无通知记录(如待支付或刚过期)。
cashierUrl string 仅代收返回。BRPAY 收银页;只有待支付时使用。
appUrl string 仅代收返回。原生 App 付款链接,凭证为 22 个 Base64URL 字符,与 cashierUrl 一致。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "33333333-4444-4555-8666-777777777777",
  "timestamp": "2026-11-05T04:03:00Z",
  "data": {
    "orderNo": "RC00000000128364",
    "merchantOrderNo": "RECHARGE-001",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": "30.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": "2026-11-05T04:02:00Z",
    "orderRevision": 0,
    "notificationStatus": "DELIVERED",
    "cashierUrl": "https://payments.example.com/pay/aaaaaaaaaaaaaaaaaaaaaA",
    "appUrl": "brazilpay://cashier/aaaaaaaaaaaaaaaaaaaaaA",
    "failureCode": null
  }
}

未查到: 返回 HTTP 404 / ORDER_NOT_FOUND。若正在处理下单超时,按结果未确认处理;一次 404 不代表仍在处理的原请求一定不会提交。

查不到订单的完整响应 · HTTP 404

{
  "success": false,
  "code": "ORDER_NOT_FOUND",
  "message": "Record not found",
  "requestId": "33333333-4444-4555-8666-888888888888",
  "timestamp": "2026-11-05T04:06:10Z",
  "data": null
}

4.4 接单状态查询

POST /api/merchant/orders/availability · SDK queryAvailability(...)

下单前可查当前是否接单、允许的 BRC 数量范围和费率。结果不预留额度,也不检查实际商户余额或收款钱包容量。维护期间不接新单,既有订单及账户仍可查询。

允许范围取手续费、存储容量、当前商户单笔上下限及当日剩余额度的交集。代收、代付分别按圣保罗自然日累计已受理订单的原始 BRC 金额;待支付、过期订单仍计入创建当天,创建失败并回滚的订单不计。商户单笔限额和单日限额配置为 0 表示不加该项限制,新订单仍须满足正数金额、手续费和余额要求。

请求

请求字段 类型 必填 说明
direction string 否 PAYIN 查代收,PAYOUT 查代付;省略则查两个方向。
{}

只查代付时,发送:

{
  "direction": "PAYOUT"
}

成功响应 data

请求 {} 返回 payin 和 payout 两个对象;指定 direction 时只返回对应对象。两个对象内的字段如下:

响应字段 类型 说明
acceptingOrders boolean 当前是否接单,受维护、当前商户方向开关、单笔范围、当日剩余额度及手续费要求影响。
minimumAmount string / null 订单 BRC 数量下限,含端点;无有效范围时为 null。
maximumAmount string / null 订单 BRC 数量上限,含端点;无有效范围时与下限同时为 null。
feeRate string 小数费率,范围 0–1,最多 6 位小数;"0.01" 表示 1%。
feeFixed string 每笔订单的固定手续费,单位 BRC,可为 "0.00"。
feeMin string 每笔订单的最低手续费,单位 BRC。

方向关闭时仍可能返回数量范围;能否提交以 acceptingOrders 为准。即使为 true,平台仍会在下单时重新检查。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "44444444-5555-4666-8777-999999999999",
  "timestamp": "2026-11-05T03:59:00Z",
  "data": {
    "payin": {
      "acceptingOrders": true,
      "minimumAmount": "1.00",
      "maximumAmount": "10000.00",
      "feeRate": "0.01",
      "feeFixed": "0.10",
      "feeMin": "0.00"
    },
    "payout": {
      "acceptingOrders": true,
      "minimumAmount": "1.00",
      "maximumAmount": "10000.00",
      "feeRate": "0.02",
      "feeFixed": "0.20",
      "feeMin": "0.00"
    }
  }
}

只查代付的完整响应 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "44444444-5555-4666-8777-aaaaaaaaaaaa",
  "timestamp": "2026-11-05T03:59:10Z",
  "data": {
    "payout": {
      "acceptingOrders": true,
      "minimumAmount": "1.00",
      "maximumAmount": "10000.00",
      "feeRate": "0.02",
      "feeFixed": "0.20",
      "feeMin": "0.00"
    }
  }
}

4.5 商户余额查询

POST /api/merchant/account/balance · SDK queryBalance(...)

同一次读取返回账面余额、占用余额和可用余额,均为 BRC 十进制字符串。代付受理时,订单本金加手续费必须不超过可用余额,并在同一事务预留。heldBalance 只累计实际占用,不累计代收待支付金额或通知待送达金额。

请求

无业务字段,发送空 JSON 对象 {},不要留空正文。

{}

成功响应 data

响应字段 类型 说明
currency string 固定 BRC。
balance string 账面总余额,包含占用资金,单位 BRC。
heldBalance string 通用占用余额,暂不可使用;代付受理时占用本金和手续费,成功结算、失败释放。不限定业务类型。
availableBalance string 可用于新业务的 BRC 余额:balance - heldBalance。
asOf string 余额读取时间,UTC。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "55555555-6666-4777-8888-aaaaaaaaaaaa",
  "timestamp": "2026-11-05T04:04:00Z",
  "data": {
    "currency": "BRC",
    "balance": "29.60",
    "heldBalance": "5.30",
    "availableBalance": "24.30",
    "asOf": "2026-11-05T04:04:00Z"
  }
}

查询失败: 保留错误提示,不要将查询失败显示为零余额。

4.6 商户资金流水

POST /api/merchant/account/transactions · SDK queryTransactions(...)

查询已入账的商户余额变动,可通过系统单号或商户单号关联业务。每页最多 50 条,按 createdAt 倒序,同一时间按 id 升序。实时分页可能因新流水而重复,按 id 识别同一条记录。

请求

请求字段 类型 必填 说明
page integer 否 从 0 开始,省略默认为 0,最大 2147483647。传 JSON 整数。
{
  "page": 0
}

成功响应 data

响应字段 类型 说明
page integer 当前页号。
hasMore boolean 为 true 时可继续请求 page + 1。
rows array 本页流水。无记录时为 []。

rows[] 的字段

响应字段 类型 说明
id string 流水记录的 UUID,用来识别同一条记录。
orderNo string 关联的平台单号:RC 或 PY + 14 位数字。
merchantOrderNo string 关联的商户单号。
direction string PAYIN 代收或 PAYOUT 代付。
amount string 带符号的商户账面余额变动。更正冲回与原流水符号相反;对账按本字段的正负,不按订单方向推算。
feeAmount string 此订单的手续费,单位 BRC;已反映在 amount 中。
balanceAfter string 本笔变动入账后的商户 BRC 余额。
createdAt string 入账记录时间,UTC。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "66666666-7777-4888-8999-bbbbbbbbbbbb",
  "timestamp": "2026-11-05T04:04:30Z",
  "data": {
    "page": 0,
    "hasMore": false,
    "rows": [
      {
        "id": "66666666-7777-4888-8999-aaaaaaaaaaaa",
        "orderNo": "RC00000000128364",
        "merchantOrderNo": "RECHARGE-001",
        "direction": "PAYIN",
        "amount": "29.60",
        "feeAmount": "0.40",
        "balanceAfter": "29.60",
        "createdAt": "2026-11-05T04:02:00Z"
      }
    ]
  }
}

这是实时流水查询;逐页读取不能视为同一时点的冻结对账单。

4.7 钱包校验

POST /api/merchant/wallets/query · SDK queryWallet(...)

代付前用用户提供的 BRPAY 钱包账号检查是否可收款。接口不返回用户姓名、余额或其他资料,商户仍需在自己的业务流程里确认收款人。

请求

请求字段 类型 必填 说明
account string 是 大写 W + 8 位大写字母或数字,例如 W00012836。
{
  "account": "W00012836"
}

成功响应 data

响应字段 类型 说明
exists boolean 钱包是否存在。false 时只返回此字段。
walletId string 仅钱包存在时返回。将此值传给代付的 recipientWalletId。
account string 仅钱包存在时返回。匹配的钱包账号(W + 8 位大写字母或数字)。
canReceive boolean 仅钱包存在时返回。true 表示当前允许收款;代付时仍检查账户和收款容量。

完整响应示例 · HTTP 200

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "77777777-8888-4999-8aaa-bbbbbbbbbbbb",
  "timestamp": "2026-11-05T04:04:45Z",
  "data": {
    "exists": true,
    "walletId": "W00012836",
    "account": "W00012836",
    "canReceive": true
  }
}

钱包不存在也返回 HTTP 200,此时请用户核对账号:

{
  "success": true,
  "code": "OK",
  "message": "",
  "requestId": "88888888-9999-4aaa-8bbb-cccccccccccc",
  "timestamp": "2026-11-05T04:04:50Z",
  "data": {
    "exists": false
  }
}

5. 结果回调

5.1 接收与验签

建单请求选填 notifyUrl(异步结果通知)和 returnUrl(同步返回商户页面),各随订单保存,最多 512 个 ASCII 字符;中文等非 ASCII 内容须按 URL 百分号编码。必须是完整 HTTPS URL,不含用户凭据、片段或控制字符;仅本地验证允许 http://127.0.0.1。省略 notifyUrl 不生成结果通知,notificationStatus 为 null;省略 returnUrl 不展示返回商户入口。显式 null 或空字符串等同省略;非空的无效 URL 被拒绝。两个方向均接受这两个字段;代付没有收银台,因此 returnUrl 仅保存,不触发跳转。代收终态结果页通过“返回商户”按钮打开原样保存的地址,不自动跳转、不追加结果参数。返回本身不代表成功,商户必须验签通知或服务端查单。

地址按订单生效。 代收、代付分别使用各次请求的地址;新订单地址不影响旧订单及其补发。平台仍须配置允许发送的 HTTPS 源地址。

BRPAY 使用 HTTP POST 和 UTF-8 JSON 投递。接收端应直接返回状态码,不要重定向。

请求头 说明
X-BrazilPay-Event-Id 小写 UUID,与正文 eventId 相同
X-BrazilPay-Timestamp 本次发送时 UTC RFC3339,以 Z 结尾;不是请求签名的 Unix 秒
X-BrazilPay-Signature 64 位小写 HMAC-SHA256 hex

签名输入为以下字节拼接,正文必须使用收到的原始 UTF-8 字节,末尾不追加换行:

canonicalBytes = utf8(timestamp + "\n" + eventId + "\n") + rawBodyBytes
signature = hmacSha256Hex(utf8(signingSecret), canonicalBytes)

使用常量时间比较;SDK 验证签名、头/正文事件号、订单字段和 300 秒发送时间窗口。occurredAt 可以是较早的业务事件时间,不能用它替代发送时间验证。

5.2 事件格式

代收付款成功、代付到账时发送 ORDER_SUCCEEDED。代收过期或代付明确失败时发送 ORDER_FAILED;PENDING 不发送终态通知。通知发送失败不改变资金状态,也不会重新占用或释放资金。

回调字段 类型 说明
eventId string 事件 UUID,与 X-BrazilPay-Event-Id 相同;重投保持不变。
eventType string ORDER_SUCCEEDED、ORDER_FAILED 或 ORDER_CORRECTED(订单更正)。
occurredAt string 订单进入终态的时间,等于 data.completedAt(UTC)。
data object 订单结果,字段见下表。

data 的字段

回调字段 类型 说明
orderNo string 平台单号,代收 RC、代付 PY,前缀后接 14 位数字。
merchantOrderNo string 原请求的商户单号。
direction string PAYIN 代收或 PAYOUT 代付。
currency string 固定 BRC。
orderAmount string 原订单的 BRC 数量。
feeAmount string 建单时确定的手续费,单位 BRC,可为 "0.00"。
merchantAmount string 正数,单位 BRC:代收为商户净入账,代付为商户含手续费支出。代收过期不会入账。
actualAmount string / null 成功时为实际从用户扣除(代收)或转给用户(代付)的 BRC 数量;过期为 null。
status string SUCCESS 表示已付款或到账;FAILED 表示明确失败终态,适用于代收过期或代付处理失败;原因见 failureCode。
failureCode string / null 失败原因;FAILED 时必返,当前过期为 ORDER_EXPIRED;其他状态为 null。
walletId string / null 代付各终态均为指定收款钱包;代收仅 SUCCESS 为实际付款钱包,其余为 null。钱包标识本身不代表到账。
createdAt string 建单时间,UTC。
expiresAt string / null 代收付款截止时间(UTC);代付为 null。
completedAt string 订单终态时间(UTC)。成功为完成时间;过期等于 expiresAt。

以下给出三种完整的回调正文。回调是平台发给商户的请求,外层直接是 eventId、eventType、occurredAt、data。

代收已付款 · ORDER_SUCCEEDED

{
  "eventId": "44444444-5555-4666-8777-888888888888",
  "eventType": "ORDER_SUCCEEDED",
  "occurredAt": "2026-11-05T04:02:00Z",
  "data": {
    "orderNo": "RC00000000128364",
    "merchantOrderNo": "RECHARGE-001",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": "30.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": "2026-11-05T04:02:00Z",
    "failureCode": null
  }
}

代收成功的 HTTP 请求头示例(离线验签)。使用 §2.2 的示例密钥和上面代收成功的 JSON(UTF-8、LF、正文无结尾换行),可以复算下面的签名。验时间窗口时,把测试时钟固定到 X-BrazilPay-Timestamp;真实接收端用当前时间。

POST /webhooks/br-pay HTTP/1.1
Host: merchant.example.com
Content-Type: application/json; charset=utf-8
X-BrazilPay-Event-Id: 44444444-5555-4666-8777-888888888888
X-BrazilPay-Timestamp: 2026-11-05T04:02:01Z
X-BrazilPay-Signature: be9644982cae18aead3d900226ca585e2d30e6ab62370147e6f049404ce858a0
Content-Length: 585

代付已到账 · ORDER_SUCCEEDED

{
  "eventId": "44444444-5555-4666-8777-bbbbbbbbbbbb",
  "eventType": "ORDER_SUCCEEDED",
  "occurredAt": "2026-11-05T04:05:00Z",
  "data": {
    "orderNo": "PY00000000128365",
    "merchantOrderNo": "WITHDRAW-001",
    "direction": "PAYOUT",
    "currency": "BRC",
    "orderAmount": "20.00",
    "feeAmount": "0.60",
    "merchantAmount": "20.60",
    "actualAmount": "20.00",
    "status": "SUCCESS",
    "walletId": "W00012836",
    "createdAt": "2026-11-05T04:05:00Z",
    "expiresAt": null,
    "completedAt": "2026-11-05T04:05:00Z"
  }
}

代收未付款到期 · ORDER_FAILED

{
  "eventId": "44444444-5555-4666-8777-aaaaaaaaaaaa",
  "eventType": "ORDER_FAILED",
  "occurredAt": "2026-11-05T04:30:00Z",
  "data": {
    "orderNo": "RC00000000128366",
    "merchantOrderNo": "RECHARGE-002",
    "direction": "PAYIN",
    "currency": "BRC",
    "orderAmount": "30.00",
    "feeAmount": "0.40",
    "merchantAmount": "29.60",
    "actualAmount": null,
    "status": "FAILED",
    "walletId": null,
    "createdAt": "2026-11-05T04:00:00Z",
    "expiresAt": "2026-11-05T04:30:00Z",
    "completedAt": "2026-11-05T04:30:00Z",
    "failureCode": "ORDER_EXPIRED"
  }
}

5.3 确认与重复通知

  1. 先验签。 保留收到的原始正文,用三个回调头验签。验签失败时不更新订单、不返回 2xx。
  2. 找到自己的订单。 用 data.merchantOrderNo 查本地记录,核对方向、BRC 数量、已保存的平台单号,以及代付收款账号。若回调比下单响应先到、还没保存平台单号,先按原商户单号查单核实。
  3. 按结果处理。 ORDER_SUCCEEDED:已付款或已到账,只对尚未处理的本地订单做一次充值或确认提现。 ORDER_FAILED:代收失败不充值,代付失败不确认到账;ORDER_CORRECTED 按订单修订处理。
  4. 提交后再确认。 本地订单和必要的业务记录保存成功后,返回 204 No Content;其他 2xx 也可以,无需响应正文。收到已处理的同一事件并核对无误时,也返回 2xx。

平台会在订单结果保存后发送回调。重复投递使用相同的 eventId 和正文,但会重新生成发送时间和签名。收不到回调时可主动查单;通知失败不会撤销付款或到账。当前工程基线:最多 10 次自动尝试、5 秒 HTTP 超时、5 秒起指数退避,最高 300 秒;这些是可调工程值。应在 5 秒内持久化必要业务,再把耗时工作交给商户自己的后台。自动重试用完后,仍可查单,也可联系平台补发。

未配置发送范围时,平台保留待通知并记录 DELIVERY_DISABLED,不消耗 HTTP 发送次数。不要据此重新创建资金订单。

6. 错误处理与结果未确认

HTTP code 处理方式
400 MERCHANT_REQUEST_INVALID 字段/JSON/UUID 格式错误,修正输入
401 MERCHANT_AUTH_REQUIRED / INVALID_SIGNATURE / SIGNATURE_EXPIRED 检查商户 ID、密钥、原始字节、路径和服务器时间
403 MERCHANT_DIRECTION_DISABLED / ORIGIN_REJECTED 商户关闭方向时查询接单状态;Origin 拒绝时改由商户服务端调用
403 ACCOUNT_DISABLED 收款账户已禁用,停止向该账户代付并联系平台核对;重新校验钱包 canReceive,不靠更换商户单号重试
404 ORDER_NOT_FOUND / WALLET_NOT_FOUND 核对商户单号或钱包;钱包查询不存在通常返回 exists=false
405 METHOD_NOT_ALLOWED 改用 POST;响应带 Allow: POST
409 ORDER_EXISTS 查原单并核对业务,不把冲突当成功
409 MERCHANT_INSUFFICIENT_BALANCE / WALLET_LIMIT_EXCEEDED 商户余额不足或用户余额容量不足
409 CONFIG_CHANGED / MERCHANT_DAILY_LIMIT 配置变化或已用完日额度,重新核对接单状态
413 BODY_TOO_LARGE 正文超过 8192 字节
415 JSON_REQUIRED 发送 application/json
422 INVALID_AMOUNT / INVALID_FEE / MERCHANT_AMOUNT_OUT_OF_RANGE 核对数量字符串、费用和实际允许范围
503 MAINTENANCE_CONFIG_UNAVAILABLE 无法确认维护配置,服务拒绝接新单;联系平台恢复配置。资金回执按结果未确认处理,先查原单
503 CLIENT_MAINTENANCE 全站维护,不接新单;已有订单/账户仍可查询
503 MERCHANT_SCHEMA_REQUIRED / CLIENT_MAINTENANCE / SERVICE_UNAVAILABLE 服务或配置暂不可用,联系平台;资金结果未确认需先查单

HTTP 5xx、断线、超时、读不到响应,或响应里的单号对不上,都不能判断资金是否已经划转。先查原商户单号。错误码区分大小写。

例:代付 WITHDRAW-001 超时了。

  1. 保存原请求、商户单号 WITHDRAW-001 和请求追踪号;先不要改余额,也不要换一个单号再代付。
  2. 用 {"merchantOrderNo":"WITHDRAW-001"} 查原单。查到 SUCCESS 时核对方向、BRC 数量和收款钱包,核对一致再确认已到账。查到 PENDING 时仍在处理且资金继续占用;FAILED 时确认失败并已释放占用。查到代收单须核对方向,不能当作代付结果。
  3. 如果查询本身也超时或返回 5xx,等待后继续查原单。只有明确返回 404 / ORDER_NOT_FOUND 时,才可用原商户单号和原业务参数重新提交;原请求仍可能在处理,若收到 409 / ORDER_EXISTS,继续查同一单。

Java / PHP SDK 用 MerchantApiException 返回明确的业务拒绝,用 UnknownOutcomeException 表示结果未确认(含 HTTP 5xx)。默认请求总期限 30 秒,包含连接、响应头及正文,最多接收 256 KiB;可按实际环境调整期限。SDK 不自动跟随重定向。

7. SDK 使用

7.1 Java

Java 17+。使用交付包的 merchant-sdk.jar 及独立 POM,或构建 sdk/java。依赖 tools.jackson.core:jackson-databind:3.1.4。下例展示独立调用方式,数量与收款人须来自商户自己的业务校验,不能作为连续自动执行脚本:

import java.time.Duration;
import tech.brazilpay.merchant.sdk.MerchantClient;
import tech.brazilpay.merchant.sdk.MerchantWebhookVerifier;

// Load settings from your server-side configuration / secret store.
var client = new MerchantClient(baseUrl, merchantId, signingSecret, Duration.ofSeconds(30));
var balance = client.queryBalance();
String availableBalance = balance.body().path("data").path("availableBalance").asText();
var availability = client.queryAvailability("PAYOUT");
var wallet = client.queryWallet("W00012836");
// Check exists / canReceive and confirm the actual recipient before payout.
var payin = client.payin(new MerchantClient.PayinRequest("RECHARGE-001", "30.00", "Game recharge", notifyUrl, returnUrl));
var payout = client.payout(new MerchantClient.PayoutRequest("WITHDRAW-001", "20.00", "Game withdrawal", recipientWalletId, notifyUrl, returnUrl));
var order = client.queryOrder("RECHARGE-001");
var orderByPlatformNo = client.querySystemOrder("RC00000000128364");
var transactions = client.queryTransactions(0);

// Inside your HTTP callback receiver; rawBodyBytes must be the untouched body.
var event = new MerchantWebhookVerifier(signingSecret)
    .verifyAndParse(timestampHeader, eventIdHeader, signatureHeader, rawBodyBytes);
// Verify your business order, persist under its state conditions, then return 2xx.

7.2 PHP

PHP 8.1+,启用 cURL 和 JSON;Composer PSR-4 命名空间 BrazilPay\Merchant\,或加载 src/*.php。命令行 demo.php 默认只读余额,不自动下单。

use BrazilPay\Merchant\MerchantClient;
use BrazilPay\Merchant\MerchantWebhookVerifier;

// Require Composer autoload.php, or the supplied src/*.php classes first.
$client = new MerchantClient($baseUrl, $merchantId, $signingSecret, 30);
$balance = $client->queryBalance();
$availableBalance = $balance['data']['availableBalance'];
$availability = $client->queryAvailability('PAYOUT');
$wallet = $client->queryWallet('W00012836');
// Check exists / canReceive and confirm the actual recipient before payout.
$payin = $client->payin(['merchantOrderNo'=>'RECHARGE-001', 'orderAmount'=>'30.00', 'description'=>'Game recharge', 'notifyUrl'=>$notifyUrl, 'returnUrl'=>$returnUrl]);
$payout = $client->payout(['merchantOrderNo'=>'WITHDRAW-001', 'orderAmount'=>'20.00', 'description'=>'Game withdrawal', 'recipientWalletId'=>$recipientWalletId, 'notifyUrl'=>$notifyUrl, 'returnUrl'=>$returnUrl]);
$order = $client->queryOrder('RECHARGE-001');
$orderByPlatformNo = $client->querySystemOrder('RC00000000128364');
$transactions = $client->queryTransactions(0);

// Inside your HTTP callback receiver; reject failures before business changes.
$rawBody = file_get_contents('php://input');
$event = (new MerchantWebhookVerifier($signingSecret))->verifyAndParse(
    $_SERVER['HTTP_X_BRAZILPAY_TIMESTAMP'] ?? '',
    $_SERVER['HTTP_X_BRAZILPAY_EVENT_ID'] ?? '',
    $_SERVER['HTTP_X_BRAZILPAY_SIGNATURE'] ?? '',
    $rawBody
);
// Verify your business order, persist under its state conditions, then return 2xx.

8. App 付款与网页引导

  1. 商户服务端创建代收单,客户端打开 cashierUrl,或使用 Android ACTION_VIEW 打开 appUrl。
  2. 有有效 Web 会话时直达原单确认页;其余用户可选择网页支付、打开 App 或在 App 内扫描收银页的二维码。确认安装关联 App 时会优先提示打开。链接只携带随机订单凭证,不授权扣款。
  3. 登录/注册、基础资料和支付密码补齐后返回原订单。网页关闭时可从商户原订单再次打开。
  4. 以服务端 expiresAt 为准,安装不延长期限;以签名回调或服务端查单确认 SUCCESS,再完成商户业务。

App 拉起不是付款成功。正式 HTTPS App Links 域名关联、正式包签名和真机支付属于独立验收。配置的付款 origin 必须实际承载 /pay/*,不能仅指向没有付款入口的官网;SDK 仅接受当前 /pay/ 路径。

9. 本地 SDK DEMO

打开本机 DEMO。它是可选的调试工作台,网页上的 /api/* 是 DEMO 自己的地址,不应填进商户系统。接入请使用平台发给你的 baseUrl;要用 DEMO 联调新版交易,先确认它连接的是同版商户 API。OpenAPI 从本页顶部下载。

在测试环境联调回调前,请与平台确认该测试商户的回调地址及通知发送已启用。只读查询通过不等于回调送达。

10. 接入验收

后台订单更正通知

管理员在核验实际资金后,可将未完成订单改为成功/失败,也可更正原终态。更正沿用原本金和原手续费,通过追加账变完成;订单号及原始金额不变。

更正通知事件为 ORDER_CORRECTED,包含 correctionId(更正记录 UUID)、previousStatus(PENDING/SUCCESS/FAILED)和 data.orderRevision(递增正整数)。data 是更正后的完整终态快照;status 为 SUCCESS 或 FAILED,失败时 failureCode=MANUAL_CORRECTION。失败的代收订单可以保留原付款用户的 W 账号。查单也返回 orderRevision,原始结果为 0。

每次更正有新的事件 ID。该事件自身的重复投递保持原事件 ID 和正文;旧事件保留原正文和尝试审计,不被改写为新结果。商户须在自己的订单事务中保存最近已处理的终态修订:尚未处理终态时,接收第一次正常成功/失败事件(修订 0);已处理终态后,只处理更高修订。PENDING 查单中的修订 0 不表示已处理终态,不能因此丢弃随后到达的正常终态事件。旧修订晚到不能覆盖新结果,同一事件重复送达不能再次执行业务效果。已产生的游戏充值、发货等外部效果必须按更正事件对账,不能把更正当成第二次充值。

Java/PHP SDK 验签器识别该事件;Demo 展示更正后的快照与原状态。Demo 的内存通知列表不替代商户自身的持久业务账。