丰海支付商户 API 文档
接入信息
- 网关域名:https://pay.feng-hai.com
- 请求格式:JSON 或表单参数
- 商户凭证:AppId + API Secret
- 请求签名:HMAC-SHA256 或 MD5 参数签名
- 回调验签:统一使用 API Secret
HMAC 请求头签名
请求头:
| 名称 | 说明 |
|---|---|
| X-Merchant-AppId | 商户 AppId |
| X-Merchant-Timestamp | Unix 秒级时间戳 |
| X-Merchant-Nonce | 随机字符串 |
| X-Merchant-Sign | sha256=签名值 |
签名原文:
timestamp + "." + nonce + "." + raw_body
签名算法:
HMAC-SHA256(API Secret, 签名原文)
MD5 参数签名
将除 `sign` 外的所有参数按 ASCII 升序排列,拼接:
k1=v1&k2=v2&key=API_SECRET
然后 MD5 大写。
创建扫码订单
POST /api/v1/scanpay/create
说明:本接口为收款接口,商户无需预存余额即可创建订单。订单支付成功后,平台按“收款金额 - 商户手续费 - 固码手续费”计算商户净入账,并写入商户余额。
参数:
| 参数 | 必填 | 说明 |
|---|---|---|
| merchant_order_no | 是 | 商户订单号,同商户唯一 |
| amount | 是 | 金额,例如 116.00 |
| payer_ip | 否 | 付款用户真实 IP。强烈建议传;不传时平台不会使用商户服务器请求 IP 选码,而是先返回支付链接,等付款用户打开支付页后按访问 IP 分配固码 |
| notify_url | 否 | 异步回调地址 |
| subject | 否 | 商品标题 |
| attach | 否 | 商户自定义透传 |
| amount_policy | 否 | EXACT 或 AUTO_UNIQUE_CENTS |
地区路由说明:如果请求传入 `payer_ip`,平台会立即按该 IP 识别省市并匹配本地区固码。IP 无法识别,或该省市没有启用固码/地区路由时,接口返回 `409`,提示“该地区暂无收款方式”,并带上 IP 识别出的地址名称,不会退回其他地区随机出码。
如果请求未传 `payer_ip`,平台只创建 `PENDING_ROUTE` 订单并返回 `pay_url`,此时 `qrcode_url` 为空、尚未锁定固码。付款用户打开 `pay_url` 后,系统使用支付页访问 IP 识别地区并分配固码;成功后订单变为 `WAITING_SIGNAL`,失败后订单变为 `ROUTE_FAILED`,支付页和查询接口会显示“该地区暂无收款方式”和识别地址。该流程避免把商户服务器 IP 当成最终付款用户 IP。若请求已传 `payer_ip`,后续支付页访问不会覆盖该付款 IP。
固码可用性说明:固码会同时校验启用状态、单笔最小/最大金额、日收款限额、总收款限额、待支付并发锁和同金额防撞规则。因无地区码、超限、锁定或同金额冲突导致不能分配时,平台会写入 `QRCODE_SELECTION_BLOCKED` 后台告警和风控事件,运营需要补码、调整限额或处理待支付锁。
手续费说明:商户费和固码费按分分别向上取整。极小金额订单可能出现手续费合计大于等于收款金额的情况,商户净入账会被压到 `0.00`;例如 0.01 元订单在商户费率 0.0060、固码费率 0.0060 下,商户费和固码费各为 0.01,净入账为 0.00。
商户后台也提供“扫码建单”页面,登录后可直接填写金额、付款 IP 和备注,由平台自动生成商户订单号并返回支付二维码 URL;该入口与正式 API 使用同一套地区和固码可用性规则。
固码出码说明:普通静态固码返回上传图片地址;若后台固码启用“慧徕店动态金额”模式,`qrcode_url` 返回平台动态二维码地址,用户扫码后平台按 User-Agent 分流微信、支付宝、云闪付,并把订单金额作为慧徕店 `fixedAmount` 或云闪付 `amount` 带入,避免用户二次手填金额。
返回:
{
"code": "SUCCESS",
"platform_order_no": "P202605260001",
"merchant_order_no": "M202605260001",
"status": "WAITING_SIGNAL",
"status_label": "待支付",
"status_desc": "订单已创建并锁定固码,等待用户扫码付款。",
"amount": "116.00",
"merchant_fee": "0.70",
"qrcode_fee": "0.70",
"fee": "1.40",
"net_amount": "114.60",
"pay_url": "https://pay.feng-hai.com/pay/P202605260001",
"qrcode_url": "https://pay.feng-hai.com/pay/P202605260001/dynamic-qr.png",
"qr_group_marker": "HLD-SZ-A-G01",
"qr_marker": "HLD-SZ-A-G01-001"
}
错误返回格式
当参数缺失、签名错误、商户未启用或码池不可用时,接口会返回统一 JSON,方便商户开发联调:
{
"code": "VALIDATION_ERROR",
"message": "请求参数错误,请检查字段:商户订单号、金额",
"status": 422,
"errors": [
{"field":"merchant_order_no","label":"商户订单号","message":"Field required"},
{"field":"amount","label":"金额","message":"Field required"}
]
}
常见错误码:
| code | 说明 |
|---|---|
| VALIDATION_ERROR | 请求参数错误 |
| AUTH_FAILED | AppId、签名、时间戳或 nonce 错误 |
| FORBIDDEN | IP 白名单或权限不允许 |
| NOT_FOUND | 订单不存在 |
| CONFLICT | 码池无可用二维码或金额冲突 |
入账与费率
商户余额不是调用前置条件,而是支付成功后的结果。
商户净入账 = 订单实付金额 - 商户费率手续费 - 固码费率手续费
示例:
实付金额:116.00
商户费率:0.0060,商户手续费:0.70
固码费率:0.0060,固码手续费:0.70
合计手续费:1.40
商户净入账:114.60
订单状态说明
创建订单、查询订单和平台回调都会同时返回英文状态码与中文说明:
| status | status_label | 说明 |
|---|---|---|
| PENDING_ROUTE | 待分配收款方式 | 商户未传付款用户 IP,订单已创建,等待付款用户打开支付页后按访问 IP 分配固码 |
| WAITING_SIGNAL | 待支付 | 订单已创建并锁定固码,等待用户扫码付款 |
| AUTO_CONFIRMED | 支付成功 | 监听信号唯一匹配订单,系统自动确认入账 |
| MANUAL_CONFIRMED | 人工确认成功 | 运营人员人工复核后确认入账 |
| REVIEW_REQUIRED | 待人工复核 | 到账信号多候选或异常,需要总后台复核 |
| ROUTE_FAILED | 该地区暂无收款方式 | 支付页访问 IP 无法识别地区,或该地区没有可用固码/额度/锁定条件不满足 |
| EXPIRED | 已过期 | 系统定时任务发现订单超时未支付,自动过期并释放固码;若用户实际已付款且到账信号在平台宽限期内上传,系统可纠正为自动确认 |
| CLOSED | 已关闭 | 商户或总后台关闭订单,并释放固码 |
状态字典接口:
GET /api/v1/status-map
返回订单状态、回调状态和自动化任务的状态流转说明。
自动化任务状态流转
平台计划任务建议每分钟执行一次 `deploy/run_tasks_native.sh`。核心状态更新:
| 任务 | 自动处理 |
|---|---|
| expire-orders | 将超过 `expire_time` 的 `PENDING_ROUTE` / `WAITING_SIGNAL` 订单改为 `EXPIRED`,并释放固码锁 |
| dispatch-callbacks | 将 `PENDING/FAILED` 回调按 `next_retry_at` 发送;成功改为 `SENT`,失败改为 `FAILED` 并设置下次重试时间。后台业务回调列表也可对单笔 `PENDING/FAILED` 记录点击“手动回调”,立即复用同一套签名逻辑发送 |
| check-devices | 检查监听设备心跳、商户 App 状态和通知权限,产生告警,不直接修改订单状态 |
| dispatch-alerts | 发送 Telegram 告警,不直接修改订单状态 |
到账信号匹配使用 App 上报的 `notify_time`,不是上传到服务器的时间。为处理监听端 DNS/网络短暂异常导致队列稍后上传的情况,平台允许在 `SIGNAL_EXPIRED_MATCH_GRACE_SECONDS` 宽限期内把唯一匹配的 `EXPIRED` 订单纠正为 `AUTO_CONFIRMED`;超过宽限期仍进入未匹配/复核流程。
查询订单
POST /api/v1/scanpay/query
参数:
| 参数 | 说明 |
|---|---|
| merchant_order_no | 商户订单号 |
| platform_order_no | 平台订单号 |
关闭订单
POST /api/v1/scanpay/close
仅待分配收款方式或待支付订单可关闭,关闭后释放锁定固码。
查询余额
POST /api/v1/account/balance
平台回调
订单确认后,平台会 POST 到商户 notify_url。回调正文包含订单状态、中文状态、收款金额、商户手续费、固码手续费、合计手续费和商户净入账。
示例:
{
"code": "SUCCESS",
"platform_order_no": "P202605260001",
"order_no": "P202605260001",
"merchant_order_no": "M202605260001",
"merchant_no": "M202605260001",
"status": "AUTO_CONFIRMED",
"status_label": "支付成功",
"status_desc": "系统收到到账信号并唯一匹配订单,已自动确认入账。",
"amount": "116.00",
"merchant_fee": "0.70",
"qrcode_fee": "0.70",
"fee": "1.40",
"net_amount": "114.60",
"callback_event": "ORDER_CONFIRMED",
"event_at": "2026-05-26T18:30:00+08:00",
"payer_ip": "101.32.1.8",
"payer_region": {
"province": "广东省",
"city": "深圳市",
"source": "merchant_payload"
},
"attach": "商户透传内容"
}
回调头:
| 名称 | 说明 |
|---|---|
| X-FH-AppId | 商户 AppId |
| X-FH-Timestamp | 时间戳 |
| X-FH-Signature | sha256=签名 |
回调签名使用 API Secret。
`callback_event` 会随状态变化区分:`AUTO_CONFIRMED` / `MANUAL_CONFIRMED` 为 `ORDER_CONFIRMED`,`CLOSED` 为 `ORDER_CLOSED`,`EXPIRED` 为 `ORDER_EXPIRED`,`REVIEW_REQUIRED` 为 `ORDER_REVIEW_REQUIRED`,`ROUTE_FAILED` 为 `ORDER_ROUTE_FAILED`。非成功状态回调的 `confirmed_at` 为空,统一使用 `event_at` 表示本次事件时间。
PHP MD5 示例
function fh_sign($params, $secret) {
unset($params['sign']);
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $k => $v) {
if ($v === '' || $v === null) continue;
$pairs[] = $k . '=' . $v;
}
$pairs[] = 'key=' . $secret;
return strtoupper(md5(implode('&', $pairs)));
}
Python HMAC 示例
import time, json, hmac, hashlib, requests, uuid
body = json.dumps({"merchant_order_no":"M10001","amount":"116.00"}, separators=(",",":"), ensure_ascii=False).encode()
ts = str(int(time.time()))
nonce = uuid.uuid4().hex
sign = "sha256=" + hmac.new(API_SECRET.encode(), ts.encode()+b"."+nonce.encode()+b"."+body, hashlib.sha256).hexdigest()
headers = {"X-Merchant-AppId": APP_ID, "X-Merchant-Timestamp": ts, "X-Merchant-Nonce": nonce, "X-Merchant-Sign": sign, "Content-Type":"application/json"}
print(requests.post("https://pay.feng-hai.com/api/v1/scanpay/create", data=body, headers=headers).json())
---
临时演示商户测试接口
该接口仅用于部署联调,不用于正式生产接入。使用前需要在总后台开启:
系统设置 -> 演示测试接口 -> 开启
演示下单页面
https://pay.feng-hai.com/demo/pay-test
演示创建订单
POST /demo/api/scanpay/create
Content-Type: application/json
请求示例:
{
"merchant_order_no": "DEMO202605260001",
"amount": "1.00",
"payer_ip": "101.32.1.8",
"subject": "演示扫码支付",
"attach": "demo-test",
"amount_policy": "EXACT"
}
返回字段与正式 `/api/v1/scanpay/create` 一致,并额外返回:
{
"demo": true,
"notice": "演示接口未验签,仅用于临时联调;正式上线请在系统设置关闭。"
}
演示查询订单
POST /demo/api/scanpay/query
Content-Type: application/json
{
"merchant_order_no": "DEMO202605260001"
}
关闭方式
系统设置 -> 演示测试接口 -> 关闭
订单状态与自动任务说明
商户可通过以下接口查看状态说明:
GET /api/v1/status-guide
订单创建、查询、关闭接口均返回 `status`、`status_name`、`status_desc`、`callback_status`、`callback_status_name` 等字段。
系统自动任务会处理:订单过期、固码释放、商户回调重试、设备离线检查。