10 KiB
慧明宝供应链平台 · Java 对接示例
对接方(下单平台)调用慧明宝接口的完整示例,与平台 PHP 端口径一一对应。
配套文档:《慧明宝供应链平台接口文档 V1.0.17》。本示例等价于平台自带的联调脚本 qyt_test.php。
一、对接链路
flowchart LR
subgraph A["你方(下单平台)"]
A1["业务系统"]
end
subgraph B["慧明宝平台"]
B1["开放接口<br/>partners.php"]
B2["发放队列"]
B3["回调队列"]
end
subgraph C["上游油品平台(企油通)"]
C1["运单油费下发"]
C2["司机加油"]
end
A1 -->|"① 分油 share<br/>RSA+AES 加密"| B1
B1 -->|"② 同步返回 oilCardNo<br/>(仅代表受理)"| A1
B1 --> B2
B2 -->|"③ 下发油费"| C1
C1 -->|"④ 查发放结果"| B2
B2 -->|"⑤ 通知 share_oil"| A1
C2 -->|"⑥ 加油消费推送"| B3
B3 -->|"⑦ 通知 add_oil(每笔一次)"| A1
A1 -->|"⑧ 退款 refund(可选)"| B1
B1 -->|"⑨ 通知 refund"| A1
不看图的话,一句话版本:分油是异步的。同步返回只代表平台受理,真正到账要等 share_oil 通知,或自己查订单状态。
sequenceDiagram
participant Y as 你方系统
participant P as 慧明宝平台
participant Q as 企油通
Y->>P: share 分油(金额/司机/身份证/车牌)
P-->>Y: code=00 + oilCardNo(受理)
P->>Q: 运单油费下发
Q-->>P: 受理
P->>Q: 查发放结果(轮询)
Q-->>P: 发放成功
P-->>Y: 通知 optType=share_oil
Note over Y,P: 此时 oil_status_zsy=2,油费才真正到账
Q-->>P: 司机加油(一单可多次)
P-->>Y: 通知 optType=add_oil(每笔推一次)
opt 司机没加完,需要退款
Y->>P: refund 退款
P->>Q: 调低司机凭证额度
P-->>Y: 通知 optType=refund
end
二、快速开始
mvn -q compile exec:java -Dexec.mainClass=com.hmb.openapi.demo.ShareDemo
运行前改 ShareDemo.java 顶部 5 个配置(平台分配):
| 配置 | 说明 |
|---|---|
BASE_URL |
接口域名,不含结尾斜杠 |
APP_KEY |
平台分配的 appKey |
ORG_NO |
公司编号,作为业务参数 orgNo |
PARTNER_PRIVATE_KEY |
你方私钥,PKCS#8 一行 base64,用于加签 |
PLATFORM_PUBLIC_KEY |
平台公钥,X.509 一行 base64,用于加密与验签回调 |
私钥若是 PKCS#1(-----BEGIN RSA PRIVATE KEY-----),Java 读不了,先转换:
openssl pkcs8 -topk8 -inform PEM -in old.pem -outform PEM -nocrypt -out pkcs8.pem
目录结构
src/main/java/com/hmb/openapi/
├── HmbConfig.java 配置与地址拼接
├── HmbCrypto.java 签名、加解密、PHP 兼容的 urlencode
├── JsonPhp.java 请求体序列化(必须与 PHP 口径一致)
├── HmbApiClient.java 五个业务接口
├── HmbResponse.java 响应封装
├── HmbNotifyVerifier.java 接收油卡变动通知(解密 + 验签)
└── demo/
├── ShareDemo.java 下单 → 查状态 → 取加油地址
└── NotifyControllerDemo.java 接收通知的 Spring 示例
三、请求地址
域名 + /partners.php + 接口路径。平台没配 URL 重写,中间的入口文件不能省。
| 接口 | method(请求头) | 路径 |
|---|---|---|
| 用户分油 | syb.oil.oilCard.share |
/oilCard/share |
| 订单状态查询 | syb.oil.oilCard.orderStatus |
/oilCard/orderStatus |
| 订单退款 | syb.oil.oilCard.refund |
/oilCard/refund |
| 付款码申请 | syb.pay.qrCode.request |
/oilCard/getPayQrCode |
| 分油记录查询 | syb.oil.oilCard.shareList |
/oilCard/shareList |
| 合作商余额查询 | syb.oil.account.balance |
/oilCard/balance |
| 司机可用额度 | syb.oil.driver.quota |
/oilCard/driverQuota |
例:http://47.104.73.7/partners.php/oilCard/share
四、报文结构
请求
请求头(自定义 HTTP 头):
| 头 | 说明 |
|---|---|
version |
固定 1.0.0 |
appKey |
平台分配 |
method |
接口标识,填错返回 0005 |
requestId |
必须全局唯一,重复返回 0012 |
sign |
见下方加签步骤 |
请求体(业务参数不出现在明文里):
{"key": "RSA加密后的AES密钥(base64)", "data": "AES加密后的业务报文(base64)"}
加签与加密(顺序不能变)
- 业务参数序列化成
bodyJson(PHP 口径,见「六、常见坑」第 2 条) - 拼待签串:
http_build_query([version, appKey, method, requestId, body]) SHA256withRSA用你方私钥签名,base64 → 放进请求头sign- 随机 32 字节 AES 密钥,
AES-256-ECB/PKCS5Padding加密bodyJson,base64 →data - 用平台公钥
RSA/ECB/PKCS1Padding加密该 AES 密钥,base64 →key
待签串长这样(注意 body 整体被 urlencode 了):
version=1.0.0&appKey=abc&method=syb.oil.oilCard.share&requestId=abcXXXX&body=%7B%22orgNo%22%3A%22U230%22%2C%22name%22%3A%22%E5%BC%A0%E4%B8%89%22%7D
响应
平台响应是明文 JSON,不加密:
{"code":"00","message":"请求成功","errCode":null,"errMessage":null,"data":{"oilCardNo":"OIL1756...","amount":"100.00"}}
code=00 为成功,否则看 errCode / errMessage。
五、接口字段
5.1 用户分油 share
| 参数 | 必填 | 说明 |
|---|---|---|
orgNo |
是 | 公司编号 |
amount |
是 | 分油金额(元),最多两位小数且大于 0 |
mobile |
是 | 司机手机号 |
name |
是 | 司机姓名,用于上游实名二要素核验 |
idCard |
是 | 司机身份证号,18 位 |
plateNo |
是 | 车牌号,平台侧留存展示,不上送上游 |
saleOrderNo |
是 | 你方内部销售单号,同一单号重复提交返回原订单(幂等) |
oilCardType |
是 | 固定 zsy_ticket |
返回 data:oilCardNo(油卡编号,后续接口都用它)、amount。
5.2 订单状态查询 orderStatus
入参 orgNo + oilCardNo。关键返回字段:
| 字段 | 说明 |
|---|---|
oil_status_zsy |
0 未提交 / 1 发放中 / 2 发放成功 / 3 发放失败。只有 2 才是真正到账 |
used_amount |
已加油金额(一单可多次加油,累计值) |
used_num |
加油次数 |
remain_amount |
剩余金额 = 分油金额 − 已加油 − 已退款,未退款时即可退金额 |
refund_status |
0 未退款 / 1 退款中 / 2 已退款 / 3 退款失败 / 4 退款结果确认中 |
use_status |
0 未加油 / 1 有加油记录(可能只加了一部分) |
5.3 订单退款 refund
入参 orgNo + oilCardNo + reason(选填)。
- 退款金额固定为剩余未加油金额,不能指定,已加油部分不退
- 每单只能成功退一次
- 返回
refundStatus=2成功;=4表示结果确认中,此时切勿重复发起,稍后查订单状态或等refund通知
六、常见坑
1. http_build_query 的编码规则和 Java URLEncoder 不一样
Java 的 URLEncoder 不编码 *,PHP 会编成 %2A;报文里只要出现一个这样的字符,验签就失败。本示例用 HmbCrypto.phpUrlEncode() 复刻了 PHP 规则:只有 A-Z a-z 0-9 - _ . 原样保留,空格编成 +,其余按 UTF-8 编成 %XX 大写十六进制。
2. 请求体 JSON 里的 / 必须转义成 \/
平台收到后会把 body 反序列化再重新序列化去验签,用的是 PHP json_encode($body, JSON_UNESCAPED_UNICODE),它默认转义斜杠,而 Java 的 JSON 库不会。备注里带个日期 2026/09/29 就会对不上。所以请求体请用本示例的 JsonPhp.encode(),不要换成 Jackson/Gson。中文要原样输出,不能转 \uXXXX。
3. requestId 必须全局唯一
平台按它拦重复请求,重复返回 0012。
4. 分油是异步的
同步返回只代表受理。真正到账看 oil_status_zsy=2,或等 share_oil 通知。
5. 一张订单可以分多次加油
每笔加油单独推一次 add_oil 通知,amount 是本次占用金额,orderAmount 才是订单总金额。请按 shareNo(企油通消费订单号,每笔不同)做幂等去重。
6. 验签回调时,body 用解密出来的明文原串
不要反序列化后再序列化 —— 不同语言的 JSON 输出不一定一致。本示例已这样处理。
七、接收油卡变动通知
把你的接收地址填到平台后台的合作商「回调地址」。报文与请求方向同构,解析步骤:
- 用你方私钥解出 AES 密钥 → 解出业务报文明文
- 用平台公钥验签,待签串是原样拼接(不 urlencode):
version=..&appKey=..&method=..&requestId=..&body={解密出的明文}
业务字段:
| 字段 | 说明 |
|---|---|
oilCardNo |
油卡编号,对应下单返回值 |
optType |
share_oil 分油 / add_oil 加油 / refund 退款 |
amount |
分油=分油金额;加油=本次加油占用金额;退款=退款金额 |
orderAmount |
订单实付金额(即分油金额),三种场景都推,便于与 amount 对照 |
shareNo |
分油=分油记录编号;加油=企油通消费订单号(每笔不同,用于去重) |
type |
add 增加 / sub 减少 |
处理成功必须返回 {"code":"00","message":"请求成功"},否则平台按 60 秒 / 5 分钟 / 30 分钟退避重试 3 次。
八、错误码
| errCode | 含义 |
|---|---|
| 0001 | 缺少必填请求参数(如未传 idCard / plateNo) |
| 0005 | 请求头 method 不正确 |
| 0006 | 签名验证不通过 |
| 0012 | 请求流水号重复 |
| 0013 | 请求参数不正确(格式校验未过) |
| 0014 | orgNo 与 appKey 不匹配 |
| 2023 | 销售单号重复且原单发放失败,请换新单号 |
| 2028 | 账户余额不足 |
| 2032 | 该订单退款正在处理中 |
| 2036 | 未找到对应的分油记录 |
| 2048 | 退单状态异常(不满足退款前置条件,如已全部加油) |
| 2049 | 调用上游退款失败(凭证余额不足或上游拒绝) |
完整列表见接口文档附录 4.3。