收款 API 接入文档
本文档定义格力支付收款接口的认证规则、请求参数、响应字段及支付结果处理方式。
01接口概述
接入方通过服务端创建收款订单,获取二维码图片,并查询客户付款结果。接口凭证和订单按账号隔离。
| 当前服务地址 | https://glzf.top |
|---|---|
| 接口前缀 | /api/v1 |
| 传输协议 | 公网使用 HTTPS;本机调试可使用 HTTP。 |
| 请求格式 | POST 使用 application/json;GET 参数放在查询字符串中。 |
| 金额单位 | 请求字段 amount 为元,响应字段 amount_fen 为整数分。 |
| 时间单位 | Unix 时间戳,单位为秒。 |
接口列表
| 方法 | 路径 | 功能 |
|---|---|---|
| POST | /api/v1/orders | 创建订单 |
| GET | /api/v1/orders/{order_id} | 查询订单 |
| GET | /api/v1/orders/by-reference/{out_trade_no} | 按业务订单号查询 |
| GET | /api/v1/orders/{order_id}/qrcode | 获取 PNG 图片 |
| GET | /api/v1/qrcode | 创建或复用订单并返回图片 |
当前统一收款商户为豪运到。接口返回的支付成功表示客户付款已确认,不表示银行卡结算完成。当前支持 USDT 提现申请,未提供沙箱、渠道分账、退款或接入方 webhook。
02接入准备
- 向管理员申请邀请码,完成邀请注册并登录,在 API 凭证页面申请密钥。
- 保存凭证 JSON。文件包含
app_id、public_key、private_key和algorithm。 - 在接入方服务器加载私钥,为每次请求生成签名。
- 创建订单后获取 PNG,由接入方页面展示;每 5 秒查询支付结果。
- 确认订单号、金额及
paid=true后,以幂等方式完成业务处理。
私钥仅在申请时返回一次,不可找回。重新申请会使旧私钥立即失效,App ID 和已有订单保持不变。私钥应保存在服务端,不得发送至浏览器或包含在移动端安装包中。
网站登录会话仅用于控制台。调用下列 API 时,即使已登录,也必须提供签名请求头。
03认证与签名
3.1 签名算法
使用 RSA 2048 位密钥、SHA-256 摘要和 PKCS#1 v1.5 填充。私钥格式为 PKCS#8 PEM,公钥格式为 SubjectPublicKeyInfo PEM。签名结果使用标准 Base64 编码,保留末尾填充字符 =。
3.2 公共请求头
| 名称 | 必填 | 格式 / 说明 |
|---|---|---|
X-App-Id | 是 | app_ 加 24 位十六进制字符,从凭证页面取得。 |
X-Timestamp | 是 | 10 位 Unix 秒;与服务器时间差不得超过 300 秒。 |
X-Nonce | 是 | 16–64 位随机字符串,仅允许 A-Z a-z 0-9 _ -;每次请求重新生成。 |
X-Signature | 是 | 签名结果的 Base64 字符串,长度为 344 个字符。 |
Content-Type | POST 必填 | application/json |
3.3 签名原文
按以下顺序拼接六行,以单个 LF(\n)分隔,最后一行末尾不追加换行。
app_id
HTTP_METHOD
request_target
timestamp
nonce
sha256_hex(body_bytes)
| 组成部分 | 处理规则 |
|---|---|
HTTP_METHOD | 使用大写,如 POST、GET。 |
request_target | 接口路径及原始查询字符串,不含协议、域名、端口。查询参数保留实际发送顺序及 URL 编码。 |
body_bytes | 实际发送的 UTF-8 请求体字节。JSON 序列化后计算摘要,签名后原样发送。 |
sha256_hex | SHA-256 结果转为 64 位小写十六进制字符串。 |
GET 请求体为空字节,其摘要固定为:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
例如,GET 创建二维码接口的 request_target 为:
/api/v1/qrcode?amount=1.00&out_trade_no=ORDER_001
3.4 签名代码
import base64, hashlib, secrets, time
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
timestamp = str(int(time.time()))
nonce = secrets.token_hex(16)
body = b'{"amount":"1.00","out_trade_no":"ORDER_001"}'
target = "/api/v1/orders"
message = "\n".join([
app_id, "POST", target, timestamp, nonce,
hashlib.sha256(body).hexdigest()
]).encode("utf-8")
key = serialization.load_pem_private_key(private_pem.encode(), password=None)
signature = base64.b64encode(
key.sign(message, padding.PKCS1v15(), hashes.SHA256())
).decode("ascii")
查询订单、下载图片和重试请求均需重新签名。重试时保持业务订单号不变,并重新生成时间戳与 nonce。签名校验通过后的请求即使出现业务错误,也不能复用 nonce。
04创建收款订单
/api/v1/orders请求参数 · JSON Body
| 字段 | 类型 | 必填 | 约束 |
|---|---|---|---|
amount | string / number | 是 | 单位元,大于 0;最多 13 位整数、2 位小数。推荐字符串,如 "1.00"。 |
out_trade_no | string | 是 | 1–64 位字母、数字、下划线或横线;当前账号内永久唯一。 |
pay_type | string | 否 | 支付方式偏好,默认 auto,枚举见下表。 |
支付方式与自动识别
| pay_type | 支付方式 | 说明 |
|---|---|---|
auto | 自动识别(默认) | 根据顾客使用的扫码应用自动适配。 |
wechat | 微信支付 | 记录微信支付偏好。 |
alipay | 支付宝 | 记录支付宝支付偏好。 |
三种选择均生成聚合收款码,实际可用支付方式以收银台为准;选择微信或支付宝不会限制其他受支持的扫码应用。返回的 pay_type 是接入方偏好,不代表最终实际付款渠道。
省略参数时默认自动识别;空字符串、null、其他值或重复的 GET 参数返回 HTTP 400。枚举区分大小写,传入参数需参与签名。同一业务单号重试沿用首次保存的偏好,省略或修改此参数不更改原订单。
POST /api/v1/orders
Content-Type: application/json
X-App-Id: {app_id}
X-Timestamp: {timestamp}
X-Nonce: {nonce}
X-Signature: {signature}
{"amount":"1.00","out_trade_no":"ORDER_001","pay_type":"auto"}
响应结果
| HTTP | 含义 |
|---|---|
| 201 | 首次创建成功,返回订单对象。 |
| 200 | 返回已存在的同金额订单,或返回已明确拒绝的 failed 订单。 |
| 202 | 订单正在创建或创建结果尚未确认,状态为 creating / unknown。 |
| 409 | 相同业务订单号对应的金额不一致,或 nonce 已使用。 |
{
"order_id": "0123456789abcdef0123456789abcdef",
"out_trade_no": "ORDER_001",
"pay_type": "auto",
"amount": "1.00",
"amount_fen": 100,
"status": "pending",
"paid": false,
"message": "等待顾客扫码付款",
"created_at": 1790553600,
"expires_at": 1790553900,
"confirmed_at": null,
"receipt_id": null,
"qrcode_url": "/api/v1/orders/0123456789abcdef0123456789abcdef/qrcode",
"status_url": "/api/v1/orders/0123456789abcdef0123456789abcdef",
"poll_after_ms": 5000
}
同账号、同业务订单号、同金额的重复请求返回同一订单,不延长二维码有效期。不同账号可以使用相同的业务订单号。仅 HTTP 2xx 不能说明收款码已生成,应检查订单状态和二维码地址。
实际支付限额由商户与支付渠道决定。创建请求出现超时后,按原业务订单号查询,避免更换单号重复收款。
05按平台订单号查询
/api/v1/orders/{order_id}| 路径参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
order_id | string | 是 | 创建接口返回的 32 位小写十六进制订单号。 |
请求体为空。成功返回 HTTP 200 和订单对象,订单不存在或不属于当前账号时返回 HTTP 404。同一订单最多每 5 秒刷新一次支付结果,其余请求返回已保存的状态。
result = client.request(
"GET", "/api/v1/orders/0123456789abcdef0123456789abcdef"
)
if (result["paid"] and result["amount_fen"] == 100
and result["out_trade_no"] == "ORDER_001"):
# 在业务数据库事务内幂等处理,避免重复发货。
pass
已确认支付的订单返回保存的成功结果。查询异常返回 502,不代表付款失败。
06按业务订单号查询
/api/v1/orders/by-reference/{out_trade_no}| 路径参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
out_trade_no | string | 是 | 接入方创建订单时传入的业务订单号。 |
适用于创建请求超时、创建响应丢失或仅保留业务单号的场景。返回结构、刷新频率与按平台订单号查询一致。
GET /api/v1/orders/by-reference/ORDER_001
若返回 404,可使用相同业务订单号和相同金额重新调用创建接口;每次调用仍需生成新 nonce。
07获取二维码图片
/api/v1/orders/{order_id}/qrcode| 路径参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
order_id | string | 是 | 平台订单号,要求与当前账号匹配。 |
响应
| 响应项 | 说明 |
|---|---|
| HTTP 200 | 响应体为 PNG 图片的二进制内容。 |
Content-Type | image/png |
X-Order-Id | 当前平台订单号。 |
X-Order-Status-Url | 查询订单的相对路径。 |
X-Pay-Type | 首次创建保存的支付方式偏好。 |
| HTTP 409 | 订单尚无可用图片;响应体为 JSON 错误对象。 |
图片下载必须携带签名请求头。接入方应由后端下载 PNG,再通过自己的页面展示,不应将接口路径直接作为公开网页的 img 地址。
只在 status=pending 且 qrcode_url 非空时展示付款码。重复下载不重新下单;二维码内的支付收银台显示真实支付渠道与收款商户。
08GET 创建二维码
/api/v1/qrcode该接口将创建订单和获取 PNG 合并为一次请求,适用于要求 GET 传参的服务端集成。
| 查询参数 | 必填 | 说明 |
|---|---|---|
amount | 是 | 收款金额(元),规则与创建订单一致。 |
out_trade_no | 是 | 业务订单号,规则与创建订单一致。 |
pay_type | 否 | auto 自动(默认)、wechat 微信、alipay 支付宝。聚合码自动适配,规则同支付方式与自动识别。 |
GET /api/v1/qrcode?amount=1.00&out_trade_no=ORDER_001&pay_type=wechat
有可用图片时返回 HTTP 200 和 PNG,响应头同第 7 节。没有图片时返回 JSON 错误,应按业务单号查询当前状态。接口会复用同单号同金额的订单,包括已到期订单,接入方展示前须核对订单状态。
HEAD 不创建订单。旧路径 /api/orders、/qrcode 保留兼容,但同样要求签名与业务订单号;新接入使用 v1 路径。
09订单响应字段
| 字段 | 类型 | 可空 | 说明 |
|---|---|---|---|
order_id | string | 否 | 平台订单号,32 位小写十六进制。 |
out_trade_no | string | 否 | 接入方业务订单号。 |
pay_type | string | 否 | 首次创建保存的支付偏好:auto / wechat / alipay;旧订单为 auto,不代表实际付款渠道。 |
amount | string | 否 | 订单金额,单位元,固定两位小数。 |
amount_fen | integer | 否 | 订单金额,单位分;用于金额核对。 |
status | string | 否 | 订单状态,枚举值见下一节。 |
paid | boolean | 否 | 付款是否已确认,只有 paid 状态为 true。 |
message | string | 否 | 展示文案,不应用于业务分支判断。 |
created_at | integer | 否 | 订单创建时间,Unix 秒。 |
expires_at | integer | 否 | 二维码到期时间,创建时间加 300 秒。 |
confirmed_at | integer | 是 | 本平台确认付款成功的时间,Unix 秒。 |
receipt_id | string | 是 | 支付成功后生成的平台回执号。 |
qrcode_url | string | 是 | 图片接口相对路径;未取得图片时为 null。 |
status_url | string | 否 | 订单查询接口相对路径。 |
poll_after_ms | integer | 否 | 建议查询间隔,当前为 5000 毫秒。 |
所有接口路径均相对于 API 服务域名。可空字段在无值时返回 JSON null。
10订单状态与支付确认
| status | 含义 | 处理方式 |
|---|---|---|
creating | 正在创建 | 保留原业务单号,稍后查询。 |
unknown | 创建结果未确认 | 继续查询原订单,不另建同笔业务订单。 |
pending | 等待付款 | 展示可用二维码,每 5 秒查单。 |
paid | 客户付款已确认 | 核对业务单号及金额,幂等完成业务。 |
expired | 二维码已到期且尚未确认付款 | 停止展示,可继续核对晚到结果。 |
closed | 订单已关闭 | 停止展示,需要核对时查询原订单。 |
failed | 创建或支付未完成 | 不发货,需要核对时查询原订单。 |
接入方应通过服务端主动查单确认结果。当前没有向接入方推送的 webhook。控制台在收款窗口打开时自动轮询,关闭后停止,重新打开可继续查询。
支付成功须同时核对 paid=true、业务单号和订单金额。confirmed_at 与 receipt_id 在成功后为非空。建议为业务订单建立数据库唯一约束,在事务中处理成功状态,避免重复发货。
已确认支付的状态不会回退。到期、关闭或失败状态的订单仍可查询,以核对延迟到达的成功结果。前端跳转、付款截图及页面提示不能替代服务端查单。
11错误码与重试
11.1 错误响应
{"error":"API 签名无效","code":401}code 为 HTTP 状态码,error 为可读原因。图片接口出错时也返回 JSON。
| HTTP | 原因 | 处理方式 |
|---|---|---|
| 400 | 参数、金额或 JSON 格式错误 | 修正参数后重新签名。 |
| 401 | 签名错误、时间过期或密钥未启用 | 检查 App ID、有效私钥、时间戳和原始报文。 |
| 403 | 控制台会话验证或密码错误 | 刷新控制台或重新登录;API 签名接口无需 CSRF。 |
| 404 | 订单不存在或不属于当前账号 | 核对订单号和调用账号。 |
| 409 | nonce 重放、金额冲突或暂无二维码 | 按错误说明重新签名、核对金额或查询原单。 |
| 413 | 请求体超过 64 KiB | 减小请求体。 |
| 415 | POST 内容类型错误 | 使用 application/json。 |
| 429 | 请求超过频率限制 | 按 Retry-After 等待并退避。 |
| 502 | 暂时无法核验支付结果 | 稍后查询原订单,不视为支付失败。 |
11.2 请求频率
| 维度 | 限制 |
|---|---|
| API 请求 / 账号 | 120 次 / 分钟 |
| 创建尝试 / 账号 | 30 次 / 分钟,重复创建请求也计数 |
| 签名校验 / 来源 IP | 300 次 / 分钟 |
| 支付结果刷新 / 订单 | 最短间隔 5 秒 |
11.3 异常处理
- 创建超时或响应丢失:先按业务订单号查询。
- 查到订单:继续使用该订单,按当前状态处理。
- 返回 404:使用原业务订单号、原金额重试创建。
- 查询失败或返回 502:等待后继续查单;429 时延长间隔。
每次重试使用新 nonce。网络失败不是付款失败,不能据此重复收款或提前发货。
12示例代码
以下示例调用当前真实收款接口。将凭证文件保存在服务端,使用同一业务订单号重试同一笔业务。
12.1 Python 3.10+
下载 client.py,依赖 cryptography。
python -m pip install cryptography
python client.py credentials.json 1.00 ORDER_001 https://glzf.topimport json
from pathlib import Path
from client import PaymentClient
credentials = json.loads(Path("credentials.json").read_text(encoding="utf-8"))
client = PaymentClient("https://glzf.top", credentials)
order = client.request("POST", "/api/v1/orders", {
"amount": "1.00", "out_trade_no": "ORDER_001", "pay_type": "wechat"
})
if order["status"] == "pending" and order["qrcode_url"]:
image = client.request("GET", order["qrcode_url"])
Path("payment.png").write_bytes(image)
result = client.request("GET", order["status_url"])
12.2 Node.js 20+
下载 client.mjs,无需安装 npm 依赖。
node client.mjs credentials.json 1.00 ORDER_002 https://glzf.topimport {readFileSync, writeFileSync} from 'node:fs';
import {request} from './client.mjs';
const credentials = JSON.parse(readFileSync('credentials.json', 'utf8'));
const base = 'https://glzf.top';
const order = await request(base, credentials, 'POST', '/api/v1/orders', {
amount: '1.00', out_trade_no: 'ORDER_002', pay_type: 'alipay'
});
if (order.status === 'pending' && order.qrcode_url) {
const image = await request(base, credentials, 'GET', order.qrcode_url);
writeFileSync('payment.png', image);
}
const result = await request(base, credentials, 'GET', order.status_url);
命令行示例会保存二维码 PNG,并每 5 秒查单,最多执行 180 轮。集成示例仅展示一次查询,持续确认由接入方服务端任务完成。
13接入验收
| 检查项 | 预期结果 |
|---|---|
| 鉴权 | 无签名、错误私钥、篡改参数、过期时间戳被拒绝。 |
| 防重放 | 重复 nonce 返回 409。 |
| 幂等 | 同单号同金额返回原订单;改金额返回 409。 |
| 账号隔离 | 其他账号无法取得订单或二维码。 |
| 实际付款 | 小额付款后,订单金额、业务单号与 paid=true 均匹配。 |
| 重复处理 | 多次查询成功,业务仅执行一次。 |
| 异常恢复 | 超时后能按原业务单号继续核对;到期停止展示二维码。 |
| 密钥更新 | 旧私钥被拒绝,新私钥可查询已有订单。 |
14余额与 USDT 提现
以下接口均沿用第 3 节的 RSA 请求签名,按 App ID 隔离。API 密钥具有申请提现能力,只应存放在可信服务端。
14.1 费率与记账
默认收款费率 8%,例如收款 100 元,平台服务费 8 元,用户余额增加 92 元。管理员可调整专属费率,费率在创建订单时固定;调价不更改历史订单和已创建的待付款订单。服务费逐单四舍五入到人民币分,仅 paid 订单记入余额。
| 订单新增字段 | 类型 | 说明 |
|---|---|---|
fee_rate | string | 本订单费率百分数,"8.00" 为 8%。 |
platform_fee_fen | integer | 收款服务费,人民币分。 |
net_amount_fen | integer | 扣费后金额,人民币分;非 paid 时尚未计入余额。 |
14.2 接口列表
| 方法 | 路径 | 功能 |
|---|---|---|
| GET | /api/v1/balance | 查询当前余额。 |
| GET | /api/v1/exchange-rate | 查询 USDT / CNY 参考报价。 |
| POST | /api/v1/withdrawal-quotes | 计算并生成有效期 120 秒的提现报价。 |
| POST | /api/v1/withdrawals | 确认报价并冻结对应余额。 |
| GET | /api/v1/withdrawals | 当前账号最近 50 笔申请,倒序。 |
| GET | /api/v1/withdrawals/{withdrawal_id} | 查询单笔申请。 |
14.3 余额与报价
GET /balance 返回 HTTP 200。以下字段除费率外均为整数人民币分;fee_rate 为新订单采用的百分数。
{"gross_fen":10000,"fee_fen":800,"earned_fen":9200,
"available_fen":9200,"frozen_fen":0,"withdrawn_fen":0,"fee_rate":"8.00"}gross_fen 为累计已确认收款,fee_fen 为累计服务费,earned_fen 为扣费后收入;可用余额 available_fen = earned_fen − frozen_fen − withdrawn_fen。
GET /exchange-rate 返回 rate(每 USDT 的人民币元)、unit(CNY/USDT)、source、updated_at(源报价 Unix 秒)、minimum_usdt(10.000000)和 fee_usdt(1.000000)。报价来自 CoinGecko,缓存 60 秒,拒绝超过 300 秒的源报价;不是场外交易买卖报价。
POST /withdrawal-quotes 请求字段:
| 字段 | 类型 | 说明 |
|---|---|---|
amount | string / integer | 必填,提现人民币元;正数,最多两位小数、13 位整数,不超过可用余额。推荐字符串。 |
address | string | 必填,有效的 34 位 TRC20 地址,T 开头且通过 Base58Check。 |
{
"quote_id":"0123456789abcdef0123456789abcdef",
"amount_fen":9200,"network":"TRC20","address":"实际有效的TRC20地址",
"rate":"7.000000","rate_source":"CoinGecko","rate_updated_at":1790637500,
"gross_usdt":"13.142857","fee_usdt":"1.000000","net_usdt":"12.142857",
"expires_at":1790637620
}
gross_usdt 为人民币元除以汇率,向下保留六位小数,至少 10 USDT;扣除 fee_usdt 后,net_usdt 为需要转给用户的数量,最低 9 USDT。quote_id 绑定账号与全部报价字段;expires_at 为创建后 120 秒。计算报价尚未冻结余额。
14.4 确认申请与查询
POST /withdrawals 仅接收以下必填字段:
{"quote_id":"0123456789abcdef0123456789abcdef"}首次确认成功返回 HTTP 201,相同报价重试返回 HTTP 200 与原申请。每次重试需重新签名、生成新 nonce。系统再次校验报价归属、有效期及余额,并原子冻结人民币金额;已提交申请锁定汇率和收款地址。即使原申请被退回,相同报价也只返回原申请,不再次提现。
返回提现对象:包含报价对象除 expires_at 外的全部字段,以及下列字段。查询单笔接口同样返回此对象;列表接口返回 {"withdrawals":[...]}。非本人申请返回 404。
| 字段 | 类型 | 说明 |
|---|---|---|
withdrawal_id | string | 32 位十六进制提现单号。 |
status | string | 见下表。 |
created_at / updated_at | integer | 创建及最后处理时间,Unix 秒。 |
txid | string / null | 管理员登记的 TRON 交易哈希,已打款时非空。 |
note | string | 审核备注或退回原因,可能为空。 |
| status | 含义 | 余额处理 |
|---|---|---|
| pending | 审核中 | 冻结申请人民币金额。 |
| processing | 审核通过,待打款 | 继续冻结。 |
| paid | 已登记打款 | 转入已提现,不返还余额。 |
| rejected | 申请退回 | 释放冻结余额。 |
提现由管理员人工转账后登记,系统不自动发送 USDT 或核验链上 TXID。接口记录与链上到账需分别核对。
14.5 错误、重试与示例
400:金额低于门槛、超余额或地址无效;409:报价过期、不属于当前账号或确认时余额不足;503:暂时无有效汇率,未生成报价或冻结余额。创建报价每账号 20 次/分钟,确认申请每账号 10 次/10 分钟,另受公共限流约束。
balance = client.request("GET", "/api/v1/balance")
quote = client.request("POST", "/api/v1/withdrawal-quotes", {
"amount": "92.00", "address": trc20_address
})
# 展示金额、网络、地址、手续费与 net_usdt,取得用户确认后提交。
withdrawal = client.request("POST", "/api/v1/withdrawals", {
"quote_id": quote["quote_id"]
})
result = client.request("GET", "/api/v1/withdrawals/" + withdrawal["withdrawal_id"])报价过期时重新计算并展示,勿自动确认变化后的报价。提交超时先按原 quote_id 重试或查询申请列表;不要立即新建报价重复申请。