商户接入文档API Secret 统一验签

丰海支付商户 API

用于商户创建扫码订单、查询订单、关闭订单、查询余额,并接收丰海支付异步订单通知。本文档按生产接入流程整理,商户可直接复制示例改造。

丰海支付商户 API 文档

接入信息

  • 网关域名:https://pay.feng-hai.com
  • 请求格式:JSON 或表单参数
  • 商户凭证:AppId + API Secret
  • 请求签名:HMAC-SHA256 或 MD5 参数签名
  • 回调验签:统一使用 API Secret

HMAC 请求头签名

请求头:

名称说明
X-Merchant-AppId商户 AppId
X-Merchant-TimestampUnix 秒级时间戳
X-Merchant-Nonce随机字符串
X-Merchant-Signsha256=签名值

签名原文:

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_policyEXACT 或 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_FAILEDAppId、签名、时间戳或 nonce 错误
FORBIDDENIP 白名单或权限不允许
NOT_FOUND订单不存在
CONFLICT码池无可用二维码或金额冲突

入账与费率

商户余额不是调用前置条件,而是支付成功后的结果。

商户净入账 = 订单实付金额 - 商户费率手续费 - 固码费率手续费

示例:

实付金额:116.00
商户费率:0.0060,商户手续费:0.70
固码费率:0.0060,固码手续费:0.70
合计手续费:1.40
商户净入账:114.60

订单状态说明

创建订单、查询订单和平台回调都会同时返回英文状态码与中文说明:

statusstatus_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-Signaturesha256=签名

回调签名使用 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` 等字段。

系统自动任务会处理:订单过期、固码释放、商户回调重试、设备离线检查。