<BASE_URL> 占位)。v1(服务端版本 1.2.0;每次响应都带 X-Api-Version 头)注意:本文档不包含商户真实凭据与白名单信息,请使用占位符替换后对接。
/api/open/v1。/api/open/v2 并保留 v1 一段时间,商户可平滑迁移。GET <BASE_URL>/version 查询当前服务端版本(无需鉴权)。本次变更(1.2.0):
status 不再返回 expired:超时未支付统一归一为 pending(处理中),请以查单结果为准。success)时回调商户;失败/超时等非成功状态不回调,商户应通过查单接口获知最终结果(代付不受此限制)。POST<BASE_URL>/merchant/pay/createapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| out_order_no | string | 是 | 商户订单号(幂等键;同一商户不可重复) |
| amount | int | 是 | 订单金额(最小单位整数,精度 0.0001;需要放大10000倍,例如 1.2345 传 12345)。必须是 JSON 数字正整数,不接受字符串(如 "12345")或小数;非法返回 HTTP 400(code=400)。 |
| currency | string | 是 | 币种(如 PHP/USDT;用于校验与路由) |
| pay_method | string | 是 | 支付方式(由平台提供,如 gcash/maya;加密货币用链名如 trc20/erc20) |
| country | string|null | 否 | 国家 ISO 码(如 PH;法币必填,加密货币可省略) |
| notify_url | string | 是 | 回调地址(订单终态时平台会向该地址推送结果) |
| return_url | string|null | 否 | 前端回跳地址(支付完成后跳回商户页面;是否跳转取决于支付方式/场景) |
| subject | string|null | 否 | 订单标题(可用于展示或对账说明) |
| remark | string|null | 否 | 备注(可用于商户自定义说明/对账) |
| client_ip | string|null | 否 | 终端用户 IP(可用于风控;不传则平台无法感知终端来源) |
| extra | object|null | 否 | 扩展字段(顶层字段,参与签名;object 会按稳定 JSON 序列化)。⚠️ 强烈建议携带下单用户个人信息 extra.customer(见下方"扩展信息与渠道要求"说明) |
| extra.customer | object | 否 | 下单用户信息(选填)。平台侧不再因缺失而拒单(不再返回 300405);但 gcash/maya 等主流支付方式的上游渠道实际强制要求 name/phone,缺失会被上游拒单导致订单失败,故强烈建议携带。提供时平台做格式校验并自动映射到各渠道格式(整名 name 自动拆为 first/last):可填 name 整名(或 first_name+last_name)、email、phone |
| sign | string | 是 | 签名(按文档规则计算;pay 接口使用 api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/pay",
"return_url": "https://merchant.example.com/pay/result",
"subject": "订单标题(可选)",
"remark": "备注(可选)",
"client_ip": "1.2.3.4",
"extra": {
"user_id": "u123456",
"customer": {
"first_name": "San",
"last_name": "Zhang",
"email": "[email protected]",
"phone": "13800000000"
}
}
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| order_no | string | 是 | 平台订单号(用于后续查询与对账) |
| out_order_no | string | 是 | 商户订单号(原样回传,便于商户侧关联业务订单) |
| amount | int | 是 | 订单金额(最小单位整数,精度 0.0001;需要放大10000倍,例如 1.2345 传 12345) |
| currency | string | 是 | 币种(原样回传请求币种,如 PHP/USDT) |
| pay_url | string|null | 否 | 支付链接(下单成功 code=0 时,pay_url/qrcode_content/pay_params 至少一项非空;本字段可能为空,此时用其余字段唤起支付) |
| qrcode_content | string|null | 否 | 二维码内容(下单成功 code=0 时,pay_url/qrcode_content/pay_params 至少一项非空;本字段可能为空) |
| pay_params | string|null | 否 | 支付原生串(可选):部分支付方式返回 App 原生唤端参数;pay_url 为空而本字段非空时,请用它在客户端唤起支付 |
| expire_at | string|null | 否 | 过期时间(ISO8601;可空)。超时未支付不再单独表示为 expired 状态,对外统一归一为 pending(处理中),请以查单结果为准。 |
| status | string | 是 | 订单状态:pending(处理中)/success(成功)/failed(失败)。仅 success/failed 为终态;超时未支付不再单独返回 expired,统一归一为 pending。 |
{
"code": 0,
"message": "ok",
"data": {
"order_no": "P20250101000001",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_url": "https://pay.example.com/h5/P20250101000001",
"qrcode_content": "https://pay.example.com/h5/P20250101000001",
"pay_params": null,
"expire_at": "2025-01-01T12:30:00Z",
"status": "pending"
}
}
POST<BASE_URL>/merchant/pay/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| order_no | string|null | 否 | 平台订单号(与 out_order_no 二选一,至少提供一个) |
| out_order_no | string|null | 否 | 商户订单号(与 order_no 二选一,至少提供一个) |
| sign | string | 是 | 签名(按文档规则计算;pay 接口使用 api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"out_order_no": "202501010001",
"order_no": null
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| order_no | string | 是 | 平台订单号 |
| out_order_no | string | 是 | 商户订单号 |
| amount | int | 是 | 订单金额(最小单位整数,精度 0.0001;需要放大10000倍,例如 1.2345 传 12345) |
| currency | string | 是 | 币种 |
| status | string | 是 | 订单状态:pending(处理中)/success(成功)/failed(失败)。仅 success/failed 为终态;超时未支付不再单独返回 expired,统一归一为 pending。 |
| channel_order_no | string|null | 否 | 渠道侧订单号(可空) |
| paid_at | string|null | 否 | 支付成功时间(ISO8601;未成功时可能为空) |
| notify_status | string | 是 | 平台对商户回调推送状态:pending/success/failed(不等同于支付结果) |
{
"code": 0,
"message": "ok",
"data": {
"order_no": "P20250101000001",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "USDT",
"status": "success",
"channel_order_no": "CH20250101XXX",
"paid_at": "2025-01-01T12:15:00Z",
"notify_status": "success"
}
}
POST<BASE_URL>/merchant/payout/createapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| out_payout_no | string | 是 | 商户代付单号(幂等键;同一商户不可重复) |
| amount | int | 是 | 代付金额(最小单位整数,精度 0.0001;需要放大10000倍,例如 1.2345 传 12345)。必须是 JSON 数字正整数,不接受字符串(如 "12345")或小数;非法返回 HTTP 400(code=400)。 |
| currency | string | 是 | 币种(如 PHP/USDT;用于校验与路由) |
| pay_method | string | 是 | 支付方式(由平台提供,如 gcash/maya;加密货币用链名如 trc20/erc20) |
| country | string|null | 否 | 国家 ISO 码(如 PH;法币必填,加密货币可省略) |
| notify_url | string | 是 | 回调地址(代付终态时平台会向该地址推送结果) |
| account_name | string|null | 否 | 收款人姓名/账户名(按通道类型解释;链上地址类可空) |
| account_no | string | 是 | 收款账号/地址(按通道类型解释;例如银行卡号/链上地址) |
| bank_name | string|null | 否 | 银行/链名称(按通道类型解释;可空) |
| bank_code | string|null | 否 | 银行编码(对应「查询可用银行」接口返回的 code,平台转译为上游编码并回填 bank_name;无银行选择时可省略;如同时传 bank_name,以 bank_code 解析结果优先) |
| remark | string|null | 否 | 备注(可用于商户自定义说明/对账) |
| client_ip | string|null | 否 | 终端用户 IP(可用于风控) |
| extra | object|null | 否 | 扩展字段(参与签名;object 会按稳定 JSON 序列化)。⚠️ 强烈建议携带下单用户个人信息 extra.customer(见下方"扩展信息与渠道要求"说明) |
| extra.customer | object | 否 | 下单用户信息(选填)。平台侧不再因缺失而拒单(不再返回 300405);但 gcash/maya 等主流支付方式的上游渠道实际强制要求 name/phone,缺失会被上游拒单导致订单失败,故强烈建议携带。提供时平台做格式校验并自动映射到各渠道格式(整名 name 自动拆为 first/last):可填 name 整名(或 first_name+last_name)、email、phone |
| sign | string | 是 | 签名(按文档规则计算;payout 接口使用 api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"out_payout_no": "WD202501010001",
"amount": 5000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/payout",
"account_name": "张三(可选)",
"account_no": "09171234567",
"bank_name": "可选:银行卡代付时填银行名(gcash 等钱包类可省略)",
"client_ip": "1.2.3.4",
"remark": "商户提现(可选)",
"extra": {
"user_id": "u123456",
"customer": {
"first_name": "San",
"last_name": "Zhang",
"email": "[email protected]",
"phone": "13800000000"
}
}
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 平台代付单号(用于后续查询与对账) |
| out_payout_no | string | 是 | 商户代付单号(原样回传,便于商户侧关联业务) |
| amount | int | 是 | 代付金额(最小单位整数,精度 0.0001;需要放大10000倍,例如 1.2345 传 12345) |
| currency | string | 是 | 币种(原样回传请求币种,如 PHP/USDT) |
| status | string | 是 | 订单状态:创建时为 pending(处理中,代付一律先进入处理流程);success(成功)/failed(失败)为终态。 |
| review_status | string|null | 否 | 出款审批状态(启用商户出款审批时):pending(审批中)/approved(通过)/rejected(驳回);无需审批时为 null。 |
| fee_amount | int|null | 否 | 手续费(可空) |
| freeze_amount | int|null | 否 | 冻结金额(可空;建议口径 amount + fee_amount) |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"amount": 5000,
"currency": "PHP",
"status": "pending",
"review_status": "pending",
"fee_amount": 20,
"freeze_amount": 5020
}
}
POST<BASE_URL>/merchant/payout/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| payout_no | string|null | 否 | 平台代付单号(与 out_payout_no 二选一,至少提供一个) |
| out_payout_no | string|null | 否 | 商户代付单号(与 payout_no 二选一,至少提供一个) |
| sign | string | 是 | 签名(按文档规则计算;payout 接口使用 api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"payout_no": null,
"out_payout_no": "WD202501010001"
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 平台代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| amount | int | 是 | 代付金额(最小单位整数,精度 0.0001;需要放大10000倍,例如 1.2345 传 12345) |
| currency | string | 是 | 币种 |
| status | string | 是 | 订单状态:pending(处理中)/success(成功)/failed(失败),仅 success/failed 为终态。 |
| sub_state | string|null | 否 | 处理中子态(归一化):accepted 已受理 / reviewing 审核中 / processing 出款处理中 / verifying 出款结果核实中(非终态,请继续等待回调或轮询);终态时为 null |
| channel_order_no | string|null | 否 | 渠道侧订单号(可空) |
| finished_at | string|null | 否 | 完成时间(ISO8601;未终态时可能为空) |
| failed_reason | string|null | 否 | 失败原因摘要(仅失败时可能有值) |
| notify_status | string | 是 | 平台对商户回调推送状态:pending/success/failed(不等同于代付结果) |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"amount": 5000,
"currency": "USDT",
"status": "success",
"sub_state": null,
"channel_order_no": "CHW20250101XXX",
"finished_at": "2025-01-01T13:00:00Z",
"failed_reason": null,
"notify_status": "success"
}
}
POST<BASE_URL>/merchant/payout/banks/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| pay_method | string | 是 | 支付方式(银行类如 bank;钱包类如 gcash 通常返回空列表) |
| country | string|null | 否 | 国家 ISO 码(如 PH) |
| currency | string|null | 否 | 币种(如 PHP) |
| sign | string | 是 | 签名(按文档规则计算;payout 接口使用 api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"pay_method": "bank",
"country": "PH",
"currency": "PHP"
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| banks | array | 是 | 可用银行列表 [{code,name}];code 即代付下单 bank_code 的合法取值(跨渠道统一编码,平台自动转译上游) |
{
"code": 0,
"message": "ok",
"data": {
"banks": [
{
"code": "BDO",
"name": "BDO Unibank"
},
{
"code": "BPI",
"name": "Bank of the Philippine Islands"
},
{
"code": "GCASH",
"name": "GCash"
},
{
"code": "MAYA",
"name": "Maya"
}
]
}
}
POST<BASE_URL>/merchant/payout/proof/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| payout_no | string|null | 否 | 平台代付单号(与 out_payout_no 二选一,至少提供一个) |
| out_payout_no | string|null | 否 | 商户代付单号(与 payout_no 二选一,至少提供一个) |
| sign | string | 是 | 签名(按文档规则计算;payout 接口使用 api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"payout_no": null,
"out_payout_no": "WD202501010001"
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 平台代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| proof_url | string | 是 | 付款凭证地址(有访问时效,见 expires_in;请即取即用,勿持久化该 URL) |
| expires_in | int|null | 否 | 凭证地址有效期(秒,由渠道决定,如 1800=30 分钟;null 表示渠道未声明) |
| queried_at | string|null | 否 | 渠道返回的查询时间(可空) |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"proof_url": "https://proof.example.com/xxx.pdf",
"expires_in": 1800,
"queried_at": "2025-01-01 13:00:00"
}
}
POST<BASE_URL>/merchant/payout/receipt/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| payout_no | string|null | 否 | 平台代付单号(与 out_payout_no 二选一,至少提供一个) |
| out_payout_no | string|null | 否 | 商户代付单号(与 payout_no 二选一,至少提供一个) |
| lang | string|null | 否 | 收据语言(可选,枚举:en / zh-CN / zh-TW;不传时按订单国家自动派生) |
| inline | int|null | 否 | 传 1 时直接返回 base64 图片数据(image_base64 + mime);不传或传 0 时返回带时效签名的下载链接(receipt_url) |
| sign | string | 是 | 签名(按文档规则计算;payout 接口使用 api_secret_payout) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"payout_no": null,
"out_payout_no": "WD202501010001",
"lang": "zh-CN",
"inline": 0
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| payout_no | string | 是 | 平台代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| lang | string | 是 | 收据实际使用的语言(en / zh-CN / zh-TW) |
| receipt_url | string|null | 否 | 收据图片下载地址(相对路径,如 /api/open/v1/payout/receipt/file?token=…;商户需拼接自己的 openapi_base 即上级代理专有域名得到完整下载地址;GET 即可下载;仅 inline=0 或未传时返回;链接带时效,有效期见 expires_in;请即取即用,勿持久化该 URL) |
| expires_in | int|null | 否 | 下载链接有效期(秒,如 3600=1 小时;null 表示未声明有效期);仅 inline=0 或未传时有意义 |
| mime | string|null | 否 | 图片 MIME 类型(如 image/png);仅 inline=1 时返回 |
| image_base64 | string|null | 否 | 收据图片 Base64 编码数据(不含 data URI 前缀);仅 inline=1 时返回 |
{
"code": 0,
"message": "ok",
"data": {
"payout_no": "W20250101000001",
"out_payout_no": "WD202501010001",
"lang": "zh-CN",
"receipt_url": "/api/open/v1/payout/receipt/file?token=eyJhbGciOiJIUzI1NiJ9...",
"expires_in": 3600
}
}
POST<BASE_URL>/merchant/pay-methods/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| country | string|null | 否 | 国家 ISO 码(如 PH;不传返回全部) |
| sign | string | 是 | 签名(按文档规则计算;本接口使用 api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"country": "PH"
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| methods | array | 是 | 支付方式列表 [{pay_method,name,country,currency}];pay_method 即下单接口的合法取值 |
{
"code": 0,
"message": "ok",
"data": {
"methods": [
{
"pay_method": "gcash",
"name": "GCash",
"country": "PH",
"currency": "PHP"
},
{
"pay_method": "maya",
"name": "Maya",
"country": "PH",
"currency": "PHP"
}
]
}
}
POST<BASE_URL>/merchant/balance/queryapplication/json| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号(从商户后台获取) |
| api_key | string | 是 | API Key(从商户后台获取) |
| timestamp | int | 是 | Unix 时间戳(秒),用于过期校验(建议 ±300 秒窗口) |
| nonce | string|null | 否 | 防重放随机串;不使用时建议省略或传 null(不要传空字符串) |
| currency | string|null | 否 | 币种(如 PHP;不传返回全部币种) |
| sign | string | 是 | 签名(按文档规则计算;本接口使用 api_secret_pay) |
{
"merchant_no": "<YOUR_MERCHANT_NO>",
"api_key": "<YOUR_API_KEY>",
"timestamp": 1736073600,
"nonce": "random-xyz",
"sign": "<SIGN>",
"currency": "PHP"
}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| balances | array | 是 | 余额列表 [{currency,available,frozen}];金额为最小单位整数(10000=1 元) |
{
"code": 0,
"message": "ok",
"data": {
"balances": [
{
"currency": "PHP",
"available": 12345600,
"frozen": 50000
}
]
}
}
GET<BASE_URL>/versionapplication/json无
{}
签名说明:sign 需对"请求 JSON 顶层除 sign 以外的所有字段"参与计算;object/array 字段会按稳定 JSON 序列化。
{ code, message, data };业务失败通常仍为 HTTP 200。| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| major | string | 是 | 大版本(路径前缀,如 v1) |
| version | string | 是 | 语义化版本(major.minor.patch) |
| base_path | string | 是 | OpenAPI 路径前缀 |
{
"code": 0,
"message": "ok",
"data": {
"major": "v1",
"version": "1.2.0",
"base_path": "/api/open/v1"
}
}
通用字段(建议放在 JSON 请求体中):merchant_no、api_key、timestamp、nonce(可选)、sign。
建议算法:HMAC-SHA256。把所有参与签名字段(不含 sign)按字段名 ASCII 升序排序,拼成 key=value 并用 & 连接,末尾追加 &secret=...,对 raw string 做 HMAC-SHA256,结果转 16 进制小写作为 sign。
时间戳窗口建议 ±300 秒;若使用 nonce,服务端会在短时间内做防重放校验。
说明:示例已对 extra 等嵌套字段做稳定 JSON 序列化参与签名,可直接照抄(替换占位符与凭据后即可用)。
// 本 Demo 演示签名与下单。sign = HMAC-SHA256(参数按字段名升序拼成 k=v 用 & 连接、末尾追加 "&secret=密钥") 的 hex 值。嵌套对象(extra 等)按 key 升序紧凑 JSON 序列化后参与签名。请把占位符替换为商户实际凭据。
const crypto = require('crypto');
const baseUrl = '<BASE_URL>';
const merchantNo = '<YOUR_MERCHANT_NO>';
const apiKey = '<YOUR_API_KEY>';
const apiSecret = '<API_SECRET>';
const payload = {
merchant_no: merchantNo,
api_key: apiKey,
timestamp: Math.floor(Date.now() / 1000),
nonce: 'random-xyz',
out_order_no: '202501010001',
amount: 10000,
currency: 'PHP',
pay_method: 'gcash',
country: 'PH',
notify_url: 'https://merchant.example.com/api/notify/pay',
extra: {
customer: {
first_name: '<FIRST_NAME>',
last_name: '<LAST_NAME>',
email: '<EMAIL>',
phone: '<PHONE>',
},
},
};
const stableStringify = (v) => {
if (v === null) return 'null';
if (typeof v !== 'object') return JSON.stringify(v);
if (Array.isArray(v)) return '[' + v.map(stableStringify).join(',') + ']';
return '{' + Object.keys(v).sort().map((k) => JSON.stringify(k) + ':' + stableStringify(v[k])).join(',') + '}';
};
const signPayload = (data, secret) => {
const keys = Object.keys(data).filter((k) => k !== 'sign' && data[k] != null).sort();
const raw = keys.map((k) => `${k}=${typeof data[k] === 'object' && data[k] !== null ? stableStringify(data[k]) : data[k]}`).join('&') + `&secret=${secret}`;
return crypto.createHmac('sha256', secret).update(raw).digest('hex');
};
const sign = signPayload(payload, apiSecret);
const body = { ...payload, sign };
fetch(`${baseUrl}/merchant/pay/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
.then((r) => r.json())
.then((d) => console.log(d));
<?php
// 本 Demo 演示签名与下单。sign = HMAC-SHA256(参数按字段名升序拼成 k=v 用 & 连接、末尾追加 "&secret=密钥") 的 hex 值。嵌套对象(extra 等)按 key 升序紧凑 JSON 序列化后参与签名。请把占位符替换为商户实际凭据。
$baseUrl = "<BASE_URL>";
$merchantNo = "<YOUR_MERCHANT_NO>";
$apiKey = "<YOUR_API_KEY>";
$apiSecret = "<API_SECRET>";
$payload = [
'merchant_no' => $merchantNo,
'api_key' => $apiKey,
'timestamp' => time(),
'nonce' => 'random-xyz',
'out_order_no' => '202501010001',
'amount' => 10000,
'currency' => 'PHP',
'pay_method' => 'gcash',
'country' => 'PH',
'notify_url' => 'https://merchant.example.com/api/notify/pay',
'extra' => [
'customer' => [
'first_name' => '<FIRST_NAME>',
'last_name' => '<LAST_NAME>',
'email' => '<EMAIL>',
'phone' => '<PHONE>',
],
],
];
function ksort_recursive(&$a) {
if (is_array($a)) { ksort($a); foreach ($a as &$v) { ksort_recursive($v); } }
}
function stable_value($v) {
if (is_array($v)) {
ksort_recursive($v);
return json_encode($v, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return (string)$v;
}
ksort($payload);
$parts = [];
foreach ($payload as $k => $v) {
if ($k === 'sign' || $v === null) { continue; }
$parts[] = $k . "=" . stable_value($v);
}
$raw = implode("&", $parts) . "&secret=" . $apiSecret;
$sign = hash_hmac("sha256", $raw, $apiSecret);
$payload["sign"] = $sign;
$ch = curl_init($baseUrl . "/merchant/pay/create");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$resp = curl_exec($ch);
curl_close($ch);
echo $resp;
// 本 Demo 演示签名与下单。sign = HMAC-SHA256(参数按字段名升序拼成 k=v 用 & 连接、末尾追加 "&secret=密钥") 的 hex 值。嵌套对象(extra 等)按 key 升序紧凑 JSON 序列化后参与签名。请把占位符替换为商户实际凭据。
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
String baseUrl = "<BASE_URL>";
String merchantNo = "<YOUR_MERCHANT_NO>";
String apiKey = "<YOUR_API_KEY>";
String apiSecret = "<API_SECRET>";
Map<String, Object> payload = new TreeMap<>();
payload.put("merchant_no", merchantNo);
payload.put("api_key", apiKey);
payload.put("timestamp", System.currentTimeMillis() / 1000);
payload.put("nonce", "random-xyz");
payload.put("out_order_no", "202501010001");
payload.put("amount", 10000);
payload.put("currency", "PHP");
payload.put("pay_method", "gcash");
payload.put("country", "PH");
payload.put("notify_url", "https://merchant.example.com/api/notify/pay");
Map<String, Object> extra = new TreeMap<>();
Map<String, Object> customer = new TreeMap<>();
customer.put("first_name", "<FIRST_NAME>");
customer.put("last_name", "<LAST_NAME>");
customer.put("email", "<EMAIL>");
customer.put("phone", "<PHONE>");
extra.put("customer", customer);
payload.put("extra", extra);
// signMapper:key 升序序列化嵌套对象,与服务端稳定签名口径一致
ObjectMapper signMapper = new ObjectMapper();
signMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
StringBuilder raw = new StringBuilder();
for (Map.Entry<String, Object> e : payload.entrySet()) {
if ("sign".equals(e.getKey()) || e.getValue() == null) { continue; }
Object val = e.getValue();
String strVal = (val instanceof Map || val instanceof java.util.Collection)
? signMapper.writeValueAsString(val) : String.valueOf(val);
raw.append(e.getKey()).append("=").append(strVal).append("&");
}
raw.append("secret=").append(apiSecret);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String sign = HexFormat.of().formatHex(mac.doFinal(raw.toString().getBytes(StandardCharsets.UTF_8)));
payload.put("sign", sign);
ObjectMapper mapper = new ObjectMapper();
String body = mapper.writeValueAsString(payload);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/merchant/pay/create"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
// 本 Demo 演示签名与下单。sign = HMAC-SHA256(参数按字段名升序拼成 k=v 用 & 连接、末尾追加 "&secret=密钥") 的 hex 值。嵌套对象(extra 等)按 key 升序紧凑 JSON 序列化后参与签名。请把占位符替换为商户实际凭据。
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"time"
)
func main() {
baseUrl := "<BASE_URL>"
merchantNo := "<YOUR_MERCHANT_NO>"
apiKey := "<YOUR_API_KEY>"
apiSecret := "<API_SECRET>"
payload := map[string]any{
"merchant_no": merchantNo,
"api_key": apiKey,
"timestamp": time.Now().Unix(),
"nonce": "random-xyz",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/pay",
"extra": map[string]any{
"customer": map[string]any{
"first_name": "<FIRST_NAME>",
"last_name": "<LAST_NAME>",
"email": "<EMAIL>",
"phone": "<PHONE>",
},
},
}
// stableJSON:关闭 HTML 转义(Go 默认转义 <>&),与 JS JSON.stringify 语义一致
stableJSON := func(v any) string {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
enc.Encode(v)
return strings.TrimRight(buf.String(), "\n")
}
keys := make([]string, 0, len(payload));
for k := range payload {
if k == "sign" || payload[k] == nil { continue }
keys = append(keys, k)
}
sort.Strings(keys)
var b strings.Builder
for i, k := range keys {
if i > 0 { b.WriteString("&") }
b.WriteString(k)
b.WriteString("=")
switch payload[k].(type) {
case map[string]any, []any:
b.WriteString(stableJSON(payload[k]))
default:
b.WriteString(fmt.Sprint(payload[k]))
}
}
b.WriteString("&secret=")
b.WriteString(apiSecret)
mac := hmac.New(sha256.New, []byte(apiSecret))
mac.Write([]byte(b.String()))
sign := hex.EncodeToString(mac.Sum(nil))
payload["sign"] = sign
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseUrl+"/merchant/pay/create", bytes.NewBuffer(body))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
}
# 本 Demo 演示签名与下单。sign = HMAC-SHA256(参数按字段名升序拼成 k=v 用 & 连接、末尾追加 "&secret=密钥") 的 hex 值。嵌套对象(extra 等)按 key 升序紧凑 JSON 序列化后参与签名。请把占位符替换为商户实际凭据。
import time
import hmac
import hashlib
import json
import requests
base_url = "<BASE_URL>"
merchant_no = "<YOUR_MERCHANT_NO>"
api_key = "<YOUR_API_KEY>"
api_secret = "<API_SECRET>"
payload = {
"merchant_no": merchant_no,
"api_key": api_key,
"timestamp": int(time.time()),
"nonce": "random-xyz",
"out_order_no": "202501010001",
"amount": 10000,
"currency": "PHP",
"pay_method": "gcash",
"country": "PH",
"notify_url": "https://merchant.example.com/api/notify/pay",
"extra": {
"customer": {
"first_name": "<FIRST_NAME>",
"last_name": "<LAST_NAME>",
"email": "<EMAIL>",
"phone": "<PHONE>",
},
},
}
def _sv(v):
"""稳定序列化:嵌套对象/数组 → key 升序紧凑 JSON;标量 → str(v)。"""
if isinstance(v, (dict, list)):
return json.dumps(v, sort_keys=True, separators=(',', ':'), ensure_ascii=False)
return str(v)
keys = sorted(k for k in payload if k != 'sign' and payload[k] is not None)
raw = "&".join(f"{k}={_sv(payload[k])}" for k in keys) + f"&secret={api_secret}"
sign = hmac.new(api_secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
payload["sign"] = sign
resp = requests.post(f"{base_url}/merchant/pay/create", json=payload)
print(resp.text)
所有金额统一使用整数表示"最小记账单位",当前精度约定为 0.0001;需要放大 10000 倍传值,例如 1.2345 传 12345,避免浮点误差。
平台会按订单创建时的 notify_url 以 POST application/json 方式回调支付/代付结果。
回调同样携带签名字段,请商户侧按对应业务(pay/payout)使用各自密钥验签。
代收回调时机:代收仅在订单成功(status=success)时回调商户;失败/超时等非成功状态不回调,商户应通过查单接口(pay/query)获知最终结果。
(代付不受此限制:代付在终态 success/failed 均会回调。)
成功应答规则:商户处理成功后,必须返回 HTTP 200 且响应体为 success 或 ok(大小写不敏感);
也接受 JSON 格式:{"success":true} / {"code":0} / {"message":"success"} / {"message":"ok"}。
未按此返回(即使 HTTP 200 但 body 为其他内容,如空响应、HTML 错误页等)平台视为回调失败,
将按策略重试,连续失败达阈值会触发告警。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号 |
| order_no | string | 是 | 平台订单号 |
| out_order_no | string | 是 | 商户订单号 |
| amount | int | 是 | 下单参考金额(最小单位整数,×10000) |
| actual_amount | int|null | 否 | 用户实际支付金额(反查上游所得,成功入账依据;未知时为 null) |
| fee_amount | int|null | 否 | 手续费(按实付重算;未知时为 null) |
| net_amount | int|null | 否 | 净入账额 = 实付 − 手续费(未知时为 null) |
| currency | string | 是 | 币种 |
| status | string | 是 | 归一状态;代收仅在成功时回调,故此处恒为 success |
| channel_order_no | null | 否 | 上游订单号不对商户暴露,恒为 null |
| paid_at | string|null | 否 | 支付成功时间(ISO8601;未成功时可能为空) |
| sign | string | 是 | 签名(代收回调用 api_secret_pay 计算) |
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| merchant_no | string | 是 | 商户号 |
| payout_no | string | 是 | 平台代付单号 |
| out_payout_no | string | 是 | 商户代付单号 |
| amount | int | 是 | 代付金额(最小单位整数,×10000) |
| currency | string | 是 | 币种 |
| status | string | 是 | 归一状态:success/failed(代付终态均回调) |
| fee_amount | int|null | 否 | 手续费(未知时为 null) |
| channel_order_no | null | 否 | 上游订单号不对商户暴露,恒为 null |
| finished_at | string|null | 否 | 完成时间(ISO8601) |
| failed_reason | string|null | 否 | 失败原因(失败时返回) |
| sign | string | 是 | 签名(代付回调用 api_secret_payout 计算) |
注意:代付回调体不包含
notify_status。notify_status只是「查单返回」里平台→商户的推送状态字段,不会出现在回调体中。
回调验签算法与请求签名完全一致(见「签名与通用字段」一节):
sign 外、且值非 null」的所有字段;key=value 用 & 连接;&secret=<对应密钥>,对该 raw string 做 HMAC-SHA256,输出 16 进制小写;sign 比对(建议用常量时间比较,如 crypto.timingSafeEqual,防时序攻击)。密钥选择:代收回调用 api_secret_pay,代付回调用 api_secret_payout。channel_order_no 等值为 null 的字段不参与签名(与请求签名「跳过 null」一致)。
8000ms,无应答即判本次失败。6 次(含首次),退避间隔依次约 1分钟 / 2分钟 / 5分钟 / 10分钟 / 30分钟 / 60分钟(超过取最后一档);达上限仍失败则该回调任务置 failed,商户可用查单接口兜底获取终态。3 次)触发平台侧站内信告警(运营介入排查),不影响商户侧重试。notify_url 不跟随 30x 重定向(安全加固)。notify_url 指向内网 / 环回 / 链路本地地址会被拦截,直接判失败且不重试(地址不会自行变化);下单时此类 notify_url 亦会返回 100001。status 为对外归一形态(非内部真实值):代收恒为 success;代付为 success/failed。channel_order_no 对商户恒为 null(不暴露上游单号)——这是对外约定,非缺失。success 时触发回调;代付在终态 success/failed 均触发。actual_amount / net_amount / paid_at;代付特有 finished_at / failed_reason。以下错误码对所有需鉴权端点通用;各端点「常见错误」仅列出高频或业务相关项,不代表其他鉴权码不会返回:
100001 参数校验失败(HTTP 200,message 为具体字段错误 Joi 原文,如 "amount" must be a number)100101 请求过期(timestamp 超出允许窗口)100102 IP 不在白名单100103 重放(nonce/签名指纹重复)100104 签名错误100105 IP 在黑名单100106 鉴权失败次数过多(限流)