Open Pay Java SDK
面向商户的 Open Pay(统一网关) Java 接入库。请使用 一体包 依赖(classifier=all):
<dependency>
<groupId>com.mandao.openpay</groupId>
<artifactId>open-pay-sdk-java</artifactId>
<version>1.0.0</version>
<classifier>all</classifier>
</dependency>
版本说明:正式版本号以开发者站点公布为准。升级时只需在业务工程中调整上述 version,并同步更新企业仓库或
lib/中的一体包文件。
复制给 LLM:把 https://docs.baofu.com/docs/k9m_8n2p_q4r7t3x/k9m_8n2p_q4r7t3x-1hjt0iof5mtj5 发给编码助手即可;大模型应自动接入,仅在证书缺失、私服上传失败等硬阻断时再问商户。
能力概览
SDK 在商户侧封装以下能力,业务代码只需组装请求对象、Config 并处理响应:
| 能力域 | 说明 |
|---|---|
| 协议支付 | 绑卡(预绑 / 确认 / 查询)、支付、退款、订单与退款查询 |
| H5 签约 | 绑卡 H5 收银台跳转 |
| 统一分账 | 剩余可分账查询、订单分账、余额分账、分账查询 |
| 订阅支付 | 签约、查询订阅、变更订阅、取消订阅、产品查询、账单查询、退款发起、退款查询 |
| 嵌入式账户 | 绑卡+开户、单独绑卡、绑卡结果查询、开户状态查询、绑卡协议列表、解绑、按签约号扣款、扣款查询、退款发起、退款查询、一键多绑 H5、绑卡列表 H5 |
| 通用能力 | RSA2 / SM2 加签验签、敏感字段数字信封、HTTP 客户端、异步回调验签 |
SDK 自动处理:
- RSA2 / SM2 请求加签、响应与回调验签
- AES / SM4 数字信封(卡号、协议号等敏感字段)
Authorization、Request-ID、Baofu-Mch-ID等 HTTP Header- 统一网关 HTTP 状态码 + 扁平业务 JSON 解析
当前 SDK 版本未包含的能力(如部分分账完结、聚合支付等)以开发者站点产品公告与 OpenAPI 为准。
环境要求
- JDK 8+
- Maven 3.6+(或 Gradle,自行转换依赖坐标)
- 可访问 Maven 仓库(企业私服或中央仓库,见下文「获取与安装」)
获取与安装
从开发者站点下载(推荐)
| SDK版本 | 更新日期 | 下载链接 |
|---|---|---|
| JAVA版 | 2026-5-18 | 点击下载 |
在开发者站点下载与当前版本匹配的发布物:
| 文件 | 说明 |
|---|---|
open-pay-sdk-java-{version}-all.jar |
SDK 一体包 |
open-pay-sdk-java-{version}-all.pom |
配套依赖描述(声明 OkHttp、Jackson 等公开依赖) |
请将 jar 与 pom 成对保存、上传;勿只替换 jar 而遗漏同版本 pom,否则 Maven 可能解析到错误的传递依赖。
安装到企业 Maven 仓库
mvn deploy:deploy-file \
-DgroupId=com.mandao.openpay \
-DartifactId=open-pay-sdk-java \
-Dversion=1.0.0 \
-Dpackaging=jar \
-Dclassifier=all \
-Dfile=open-pay-sdk-java-1.0.0-all.jar \
-DpomFile=open-pay-sdk-java-1.0.0-all.pom \
-Durl=https://your-company-maven-repo/repository/releases/ \
-DrepositoryId=your-repo-id
业务 pom.xml 仅声明文首一条依赖(classifier=all)。升级 SDK = 修改 version + 替换仓库中的一体包。
非 Maven 工程
将 open-pay-sdk-java-{version}-all.jar 放入 lib/;另按 -all.pom 所列坐标,从 Maven Central 或企业仓库补齐 OkHttp、Jackson、commons-lang3、BouncyCastle 等公开依赖。
快速开始
调用分为三步:初始化配置 → 组装请求并调用 API → 判断 HTTP 与业务状态。
- 设置参数(进程内全局一次,
OpenPayFactory.setOptions)。 - 组装 Request(含必填的
riskControlInfo等字段,见 OpenAPI)。 - 处理
OpenPayResponse(先 HTTP,再trade_status等业务状态)。
import com.mandao.openpay.protocol.cutpayment.request.CutpaymentPayRequest;
import com.mandao.openpay.protocol.cutpayment.response.CutpaymentPayResponse;
import com.mandao.openpay.sdk.OpenPayFactory;
import com.mandao.openpay.sdk.config.Config;
import com.mandao.openpay.sdk.crypto.CertificateLoader;
import com.mandao.openpay.sdk.crypto.SignType;
import com.mandao.openpay.sdk.exception.OpenPaySdkException;
import com.mandao.openpay.sdk.response.OpenPayResponse;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
OpenPayFactory.setOptions(buildConfig(), "1.0.0");
CutpaymentPayRequest payReq = new CutpaymentPayRequest();
payReq.outTradeNo = System.currentTimeMillis() + "";
payReq.paymentAmount = 100L;
payReq.paymentProtocolNo = "绑卡确认后获得的支付协议号";
Map<String, Object> risk = new HashMap<String, Object>();
risk.put("goodsCategory", "06");
risk.put("userLoginId", "merchant_user_001");
risk.put("chPayIp", "203.0.113.10");
payReq.setRiskControlInfo(risk);
try {
OpenPayResponse<CutpaymentPayResponse> payResp = OpenPayFactory.Protocol.Cutpayment.pay(payReq);
if (payResp.isSuccess()) {
CutpaymentPayResponse body = payResp.getData();
if ("SUCCESS".equals(body.tradeStatus)) {
System.out.println("支付成功," + body.outTradeNo);
} else if ("PROCESSING".equals(body.tradeStatus)) {
System.out.println("处理中,请 orderQuery 轮询或等 notify");
} else {
System.out.println("交易未成功," + body.tradeStatus + "," + body.tradeStatusDesc);
}
} else {
System.out.println("网关拒绝," + payResp.getErrorCode() + "," + payResp.getErrorMsg());
}
} catch (OpenPaySdkException e) {
System.out.println("SDK 运行异常," + e.getMessage());
}
}
private static Config buildConfig() {
Config config = new Config();
config.merchantNo = "您的商户号";
config.terminalNo = "您的终端号"; // 特约商户必填;新会员入网模式可留空
config.privateKey = CertificateLoader.readBytes("/secure/merchant.pfx");
config.privateKeyPwd = "证书密码";
config.publicKey = CertificateLoader.readBytes("/secure/platform_verify.cer");
config.merchantSerialNo = "商户证书序列号";
config.baofuSerialNo = "平台加密证书序列号";
config.signType = SignType.RSA2; // 或 SignType.SM2
config.serverUrl = "https://api.baofu.com"; // 仅域名,见下文
return config;
}
}
其它接口见下文 API 一览 与 风控参数;字段定义以开发者站点发布的 OpenAPI 为准。
统一网关对商户返回 HTTP 状态码 + 扁平业务 JSON(如
trade_status、out_trade_no)。SDK 返回OpenPayResponse<T>:HTTP 2xx 时isSuccess()为 true 且getData()为业务 Body;非 2xx 时读getErrorCode()/getErrorMsg()(来自 Body 的code/message)。OpenPaySdkException仅用于网络、验签、配置等异常。
风控参数(risk_control_info)
SDK 不为风控定义固定 Java 对象,统一使用 Map<String, Object> riskControlInfo(private 字段,通过 getRiskControlInfo / setRiskControlInfo 访问),序列化为 JSON 字段 risk_control_info。不同商户开通行业要求的键不同,须按 OpenAPI 附录与当笔真实交易填写,禁止写死固定测试值。
| 接口 | Request 字段 | 说明 |
|---|---|---|
预绑卡 bindCardApply |
riskControlInfo |
必填;见协议支付 OpenAPI 风控章节 |
协议支付 pay |
riskControlInfo |
必填;常见含 goodsCategory、chPayIp 等 |
H5 收银台 h5Cashier |
riskControlInfo |
必填;至少含 register_time、login_type、device_id |
import java.util.HashMap;
import java.util.Map;
// 协议支付 / 预绑卡(示例键因行业而异)
Map<String, Object> riskControlInfo = new HashMap<String, Object>();
riskControlInfo.put("goodsCategory", "06"); // 附录《商品类目》,按真实商品
riskControlInfo.put("userLoginId", userLoginId); // 商户侧用户登录名
riskControlInfo.put("chPayIp", clientIp); // 持卡人支付 IP
payReq.setRiskControlInfo(riskControlInfo);
// H5 收银台(网关至少校验三字段)
Map<String, Object> h5Risk = new HashMap<String, Object>();
h5Risk.put("register_time", "20260101120000");
h5Risk.put("login_type", "MOBILE");
h5Risk.put("device_id", deviceId);
h5Req.setRiskControlInfo(h5Risk);
字段全集见开发者站点文档:
- 协议支付 / 预绑 / 支付:协议支付 OpenAPI → 风控参数章节
- H5 收银台:绑卡 H5 收银台 OpenAPI →
risk_control_info说明
初始化配置(字段说明)
Config config = new Config();
config.merchantNo = "您的商户号";
config.terminalNo = "您的终端号";
config.privateKey = CertificateLoader.readBytes("/secure/merchant.pfx");
config.privateKeyPwd = "证书密码";
config.publicKey = CertificateLoader.readBytes("/secure/platform_verify.cer");
config.merchantSerialNo = "商户证书序列号";
config.baofuSerialNo = "平台加密证书序列号";
config.signType = SignType.RSA2;
config.serverUrl = "https://api.baofu.com";
OpenPayFactory.setOptions(config, "1.0.0");
网关地址 serverUrl
只填 Base URL(末尾 / 可选),不要包含 /v1 等网关路径片段:
| 环境 | 示例 |
|---|---|
| 生产 | https://api.baofu.com |
| 联调 / 沙箱 | https://vgw.baofoo.com/lending-gateway |
SDK 实际请求地址 = {serverUrl}{网关路径},例如 {serverUrl}/v1/protocol/pay。
SM2 补充配置
国密商户除加签私钥、验签公钥外,须配置 数字信封加密公钥:
config.signType = SignType.SM2;
config.encryptPublicKey = CertificateLoader.readBytes("/secure/platform_encrypt.cer");
// 可选:SM2 私钥独立路径
config.sm2SignKeyPath = "/secure/merchant_sign.sm2";
config.sm2EncKeyPath = "/secure/merchant_enc.sm2";
SM2 加签 / 验签 userId:优先使用 终端号(Header 中的 app_id,对应 Config.terminalNo);新会员入网无终端号时使用 商户号(Config.merchantNo)。
新会员入网模式
- Header 仅传
Baofu-Mch-ID(统一会员编号或商户号,以入网资料为准) - 不传
Baofu-App-ID(Config.terminalNo留空) - 接口路径与特约商户相同(
/v1/protocol/*、/v1/split/*)
API 一览(32 方法)
路径均为 POST,Content-Type application/json。完整 URL = {serverUrl} + 下表「网关路径」。
协议支付
| SDK 方法 | 网关路径 |
|---|---|
OpenPayFactory.Protocol.Cutpayment.bindCardApply |
/v1/protocol/bind-card |
OpenPayFactory.Protocol.Cutpayment.bindCardConfirm |
/v1/protocol/bind-card/confirm |
OpenPayFactory.Protocol.Cutpayment.bindQuery |
/v1/protocol/bind-query |
OpenPayFactory.Protocol.Cutpayment.pay |
/v1/protocol/pay |
OpenPayFactory.Protocol.Cutpayment.refund |
/v1/protocol/refund |
OpenPayFactory.Protocol.Cutpayment.orderQuery |
/v1/protocol/order-query |
OpenPayFactory.Protocol.Cutpayment.refundQuery |
/v1/protocol/refund-query |
H5 签约
| SDK 方法 | 网关路径 |
|---|---|
OpenPayFactory.Protocol.Sign.h5Cashier |
/v1/protocol/sign/h5-cashier |
统一分账
| SDK 方法 | 网关路径 |
|---|---|
OpenPayFactory.Split.shareableQuery |
/v1/split/shareableQuery |
OpenPayFactory.Split.balanceSplit |
/v1/split/balanceSplit |
OpenPayFactory.Split.orderSplit |
/v1/split/orderSplit |
OpenPayFactory.Split.query |
/v1/split/query |
订阅支付
| SDK 方法 | 网关路径 |
|---|---|
OpenPayFactory.Subscription.sign |
/v1/subscription/sign |
OpenPayFactory.Subscription.query |
/v1/subscription/query |
OpenPayFactory.Subscription.change |
/v1/subscription/change |
OpenPayFactory.Subscription.cancel |
/v1/subscription/cancel |
OpenPayFactory.Subscription.productQuery |
/v1/subscription/product/query |
OpenPayFactory.Subscription.deductionQuery |
/v1/subscription/deduction/query |
OpenPayFactory.Subscription.refundCreate |
/v1/subscription/refund/create |
OpenPayFactory.Subscription.refundQuery |
/v1/subscription/refund/query |
嵌入式账户
| SDK 方法 | 网关路径 |
|---|---|
OpenPayFactory.Wallet.accountOpen |
/v1/wallet/account/open |
OpenPayFactory.Wallet.accountBind |
/v1/wallet/account/bind |
OpenPayFactory.Wallet.accountResultQuery |
/v1/wallet/account/result |
OpenPayFactory.Wallet.accountOpenedQuery |
/v1/wallet/account/opened |
OpenPayFactory.Wallet.accountAgreementsQuery |
/v1/wallet/account/agreements |
OpenPayFactory.Wallet.accountUnbind |
/v1/wallet/account/unbind |
OpenPayFactory.Wallet.deductPay |
/v1/wallet/deduct/pay |
OpenPayFactory.Wallet.deductQuery |
/v1/wallet/deduct/query |
OpenPayFactory.Wallet.refundApply |
/v1/wallet/refund/apply |
OpenPayFactory.Wallet.refundQuery |
/v1/wallet/refund/query |
OpenPayFactory.Wallet.h5Multi |
/v1/wallet/h5/multi |
OpenPayFactory.Wallet.h5Agreements |
/v1/wallet/h5/agreements |
双轨进线与通用路径调用
SDK 已封装上表所列接口;统一网关会持续发布新接口与新字段。在 OpenAPI 已支持、SDK 对象尚未包含对应字段 时,商户可复用同一套加签 / 数字信封 / Header,通过 JSON 字符串或自定义路径进线,无需等待 SDK 升级。
方式 A:已封装接口的 JSON 轨(xxxJson)
与 pay、bindCardApply 等同一路径,Body 改为 snake_case JSON 字符串 直传:
String body = "{"
+ "\"out_trade_no\":\"20260609120001001\","
+ "\"payment_amount\":100,"
+ "\"payment_protocol_no\":\"绑卡所得协议号\","
+ "\"risk_control_info\":{\"goodsCategory\":\"06\",\"chPayIp\":\"203.0.113.10\"},"
+ "\"some_new_field\":\"网关已支持、SDK 对象尚未建模的扩展字段\""
+ "}";
OpenPayResponse<CutpaymentPayResponse> resp = OpenPayFactory.Protocol.Cutpayment.payJson(body);
各封装方法均提供对象轨与 JSON 轨,例如:
| 对象轨 | JSON 轨 |
|---|---|
Protocol.Cutpayment.pay |
payJson |
Protocol.Cutpayment.bindCardApply |
bindCardApplyJson |
Protocol.Sign.h5Cashier |
h5CashierJson |
Split.orderSplit |
orderSplitJson |
(其余方法同理,规则为 xxxJson(String jsonBody)。)
| 项 | 说明 |
|---|---|
| JSON 形态 | snake_case,与 OpenAPI 一致 |
| 未知字段 | 原样保留并参与加签发送,SDK 不剥离 |
| 敏感字段 | 与对象轨相同,SDK 按接口约定自动走数字信封(如预绑 bank_card_no、支付 payment_protocol_no) |
| 响应 | 仍为 OpenPayResponse<T>,判读方式与对象轨相同 |
方式 B:任意网关路径(OpenPayClient.post)
SDK 尚未封装的 v4 接口(网关 OpenAPI 已发布的其它 /v1/... 路径),可按下列方式调用:
import com.mandao.openpay.sdk.client.OpenPayPayload;
import com.mandao.openpay.sdk.response.OpenPayRawResponse;
import java.util.Arrays;
// JSON 直传(自定义路径 + 扩展字段)
OpenPayRawResponse raw = OpenPayFactory.client().post(
"/v1/protocol/your-api-path",
OpenPayPayload.fromJson("""
{"out_trade_no":"20260609120001001","foo_bar":"扩展字段"}
""").withSensitiveFields(Arrays.asList("bank_card_no")));
if (raw.isHttpSuccess()) {
String json = raw.getRawBody(); // 原始业务 JSON 字符串
// MyDto dto = raw.parseBody(MyDto.class); // 或商户自建 DTO 反序列化
} else {
// HTTP 401/400/402/500:读 raw.getRawBody() 中的 code / message
}
强类型 Body 等价写法(路径仍可自定义):
OpenPayRawResponse raw = OpenPayFactory.client().post(
"/v1/protocol/pay",
OpenPayPayload.fromObject(payReq));
| 项 | 说明 |
|---|---|
path |
网关对外路径,以 /v1/ 开头;不要拼进 Config.serverUrl |
withSensitiveFields |
方式 B 自定义路径时,由商户声明 Body 中需数字信封加密的字段名 |
| 响应 | OpenPayRawResponse:getHttpStatus()、getRawBody()、parseBody(Class) |
| 加签 | 禁止手写 Authorization / Baofu-Signature;SDK 对最终 Body 字节加签 |
选用建议:已封装接口优先使用封装方法或
xxxJson(敏感字段与响应解密已内置);仅当路径或字段超出当前 SDK 时用方式 B。契约以开发者站点 OpenAPI 为准。
响应结果判断
统一网关对商户的响应形态(SDK 已代做加签、验签、敏感字段解密):
HTTP/1.1 200 OK
Baofu-Signature: ...
Body:
{
"trade_status": "SUCCESS",
"trade_status_desc": "交易成功",
"out_trade_no": "PY1783074423739",
"payment_total_amount": 100,
"successful_payment_time": "2026-07-03T18:26:56+08:00"
}
没有额外的 { "success": true, "data": {...} } 包装层;请直接读 HTTP 状态码与 Body 业务字段。
商户判断顺序
| 步骤 | 判断什么 | 说明 |
|---|---|---|
| 1 | OpenPayResponse.isSuccess() |
对应 HTTP 200 / 202 → 读 getData();401 / 400 / 402 / 500 等 → 读 getErrorCode() / getErrorMsg() |
| 2 | 业务状态 | HTTP 2xx 时,Body 内 trade_status / refund_status / txn_state 等 |
常见 HTTP 状态码(统一网关)
| HTTP | 含义 | 商户处理 |
|---|---|---|
| 200 | 成功,Body 含业务数据 | 读 trade_status 等字段 |
| 202 | 已受理、处理中 | 轮询查单或等异步 notify |
| 401 | 鉴权/验签失败 | 读 Body code(如 SIGN_ERROR)/ message |
| 400 | 请求参数/格式错误 | 修正参数后重试 |
| 402 | 业务拒绝(如余额不足) | 按 Body 中 code / message 处理 |
| 500 | 系统异常 | 稍后重试或联系支持 |
标准写法
HTTP 401 验签失败示例:
HTTP/1.1 401 Unauthorized
Body: { "code": "SIGN_ERROR", "message": "验签不通过" }
OpenPayResponse<CutpaymentPayResponse> payResp = OpenPayFactory.Protocol.Cutpayment.pay(payReq);
if (payResp.isSuccess()) {
CutpaymentPayResponse body = payResp.getData();
if ("SUCCESS".equals(body.tradeStatus)) {
System.out.println("调用成功");
} else if ("PROCESSING".equals(body.tradeStatus)) {
// 202 场景或同步返回处理中:orderQuery 轮询
} else {
System.out.println("交易失败," + body.tradeStatusDesc);
}
} else {
System.out.println("调用失败," + payResp.getErrorCode() + payResp.getErrorMsg());
}
OpenPaySdkException仅捕获网络超时、响应验签失败、证书配置错误等;网关业务拒绝不要依赖 catch 异常。
异常与重试策略
两类结果,不要混用
| 类型 | 表现 | 商户处理 |
|---|---|---|
| 网关 HTTP 结果 | OpenPayResponse.isSuccess() 为 false(401/400/402/500 等) |
读 getErrorCode() / getErrorMsg(),不要 catch |
| SDK 运行异常 | OpenPaySdkException(网络、验签、证书、JSON 等) |
catch 后按场景处置(见下表) |
OpenPaySdkException 常见场景
| message / cause | 含义 | 建议 |
|---|---|---|
HTTP 请求异常,cause 为 SocketTimeoutException |
连接或读超时(默认 connect 3s / read 30s,见 Config) |
写交易见下节「超时先查再重发」;查单类可有限次重试 |
HTTP 请求异常,其他 IOException |
网络不可达、连接重置等 | 查网络与 serverUrl;写交易仍建议先查单确认 |
响应验签未通过 |
响应被篡改或证书/序列号配置错误 | 勿当成功;核对 publicKey、merchantSerialNo、baofuSerialNo |
加签失败 / 数字信封* |
商户私钥或 SM2 加密证书配置错误 | 修正 Config,勿重发写交易 |
请先调用 OpenPayFactory.setOptions |
未初始化 | 启动时调用 setOptions |
判断是否为超时:
} catch (OpenPaySdkException e) {
if (e.getCause() instanceof java.net.SocketTimeoutException) {
// 读/连接超时:写交易须先查单(见下)
}
}
写交易超时:先查再重发(强制)
支付、退款等写接口在超时或结果不明时,禁止直接换单号或盲目重发。须用原商户单号查单确认后再决定:
| 写接口 | 先查接口 | 幂等键 | 查单后动作 |
|---|---|---|---|
pay |
orderQuery |
out_trade_no(与支付请求相同) |
无单 → 可重发 pay;PROCESSING → 轮询;SUCCESS/FAILED → 按终态处理 |
refund |
refundQuery |
out_refund_no(与退款请求相同) |
同上逻辑 |
bindCardApply 等绑卡 |
bindQuery 或业务查单 |
按 OpenAPI 约定 | 以查单结果为准 |
OpenPayResponse<CutpaymentPayResponse> payWrap;
try {
payWrap = OpenPayFactory.Protocol.Cutpayment.pay(payReq);
} catch (OpenPaySdkException e) {
if (e.getCause() instanceof java.net.SocketTimeoutException) {
// 超时:用同一 out_trade_no 查单,勿立即重发 pay
OpenPayResponse<CutpaymentOrderQueryResponse> q = OpenPayFactory.Protocol.Cutpayment.orderQuery(queryReq);
// 根据 q.getData().tradeStatus 决定是否重发
throw e; // 或走查单分支
}
throw e;
}
业务处理中(HTTP 2xx + PROCESSING)
同步返回 trade_status=PROCESSING 或 HTTP 202 时,表示已受理、未终态:
- 使用
orderQuery(或refundQuery)轮询,或等待异步 notify; - 禁止在未查单的情况下重复
pay/refund。
SDK 重试边界
| 项 | 行为 |
|---|---|
| pay / refund / 分账写接口 | SDK 默认不重试;重试策略由商户实现 |
| orderQuery / refundQuery / bindQuery | 读操作,商户可自行有限次重试(建议间隔退避) |
| 网关幂等 | 同一 out_trade_no / out_refund_no 重试,以网关返回为准 |
超时可在 Config 调整:connectTimeout(默认 3000ms)、readTimeout(默认 30000ms,退款慢时可调至 60000ms)。调大超时不能替代「先查再重发」。
多商户(同一网关环境)
OpenPayFactory.setOptions 为进程内全局单例,同时只绑定一套商户配置(商户号、终端号、证书)。
| 部署方式 | 说明 |
|---|---|
| 推荐 | 一 JVM / 一服务实例对应一商户;多商户多实例,可共用同一 serverUrl |
| 不推荐 | 同一 JVM 内并发多商户(会互相覆盖 Config) |
| 高级 | 自行 new OpenPayClient(config, new OpenPayCrypto(config)) 多实例,不走 OpenPayFactory 静态入口 |
各接口业务状态字段
| 能力 | 字段 | 终态成功 | 处理中 | 失败 |
|---|---|---|---|---|
| 绑卡 / 支付 / 退款 / 查单 / H5 | tradeStatus |
SUCCESS |
PROCESSING |
FAILED |
| 退款查询 | refundStatus |
SUCCESS |
PROCESSING |
— |
| 分账受理 / 查询 | txnState |
以 OpenAPI 为准 | — | 见 failMsg |
底层原始响应
方式 B 及高级定制场景使用 OpenPayFactory.client().post(...),详见上文 双轨进线与通用路径调用 · 方式 B。
异步回调
NotifyVerifyResult result = OpenPayFactory.Protocol.Cutpayment.verifyPayNotify(headers, bodyJson);
if (result.isVerified()) {
// 解析 result.getDecryptedBodyJson(),读 trade_status
}
分账接入说明
分账 不能 跳过支付直接调用。推荐顺序:
- 协议支付
pay,请求体设置profit_sharing=true(勿传profit_sharing_info) - 支付成功且
trade_status=SUCCESS后,使用支付响应中的字段发起分账:
| 分账字段 | 取值 |
|---|---|
orig_trans_id |
支付时的 out_trade_no(商户支付订单号) |
orig_trade_date |
支付响应 successful_payment_time 格式化为 yyyyMMddHHmmss(14 位,含时分秒) |
product_type |
协议支付填 1(见分账 OpenAPI 产品类型说明) |
示例:支付成功时间 2026-07-03T17:39:47+08:00 → orig_trade_date = "20260703173947"。
import java.util.HashMap;
// 1. 支付(分账标签订单;生产须带 riskControlInfo)
CutpaymentPayRequest payReq = new CutpaymentPayRequest();
payReq.outTradeNo = "PY20260703173947001";
payReq.paymentAmount = 100L;
payReq.paymentProtocolNo = "..."; // 绑卡所得协议号
payReq.profitSharing = true;
Map<String, Object> risk = new HashMap<String, Object>();
risk.put("goodsCategory", "06");
risk.put("chPayIp", "203.0.113.10");
payReq.setRiskControlInfo(risk);
OpenPayResponse<CutpaymentPayResponse> payWrap = OpenPayFactory.Protocol.Cutpayment.pay(payReq);
if (!payWrap.isSuccess()) {
throw new IllegalStateException(payWrap.getErrorCode() + payWrap.getErrorMsg());
}
CutpaymentPayResponse payResp = payWrap.getData();
// 2. 剩余可分账查询
SplitShareableQueryRequest sq = new SplitShareableQueryRequest();
sq.memberId = config.merchantNo;
sq.terminalId = config.terminalNo;
sq.origTransId = payResp.outTradeNo;
sq.origTradeDate = formatPayTime(payResp.successfulPaymentTime); // yyyyMMddHHmmss
sq.productType = 1; // 协议支付
OpenPayFactory.Split.shareableQuery(sq);
// 3. 订单分账、分账查询 …
sharing_info 示例(订单分账 orderSplit;JSON 数组字符串):
| 字段 | 说明 |
|---|---|
payee_acct_no |
分账接收方;分给自己时填 商户号(与 member_id 相同) |
acct_type |
1 = 特约商户 |
split_amt |
整数,单位分(如 10 表示 0.10 元);须与 trade_amt 及明细合计一致 |
trade_amt |
订单分账总金额,单位分(字符串) |
[
{
"payee_acct_no": "102002413",
"acct_type": 1,
"detail_desc": "分账给自己",
"split_amt": 10
}
]
注意:订单查询返回的
successful_payment_time可能与支付响应不一致,分账请以支付响应为准。日期仅传yyyyMMdd(8 位)会导致「报文交易要素格式错误」。
异步回调验签
NotifyVerifyResult result = OpenPayFactory.notifyCallback().verify(
NotifyVerifyRequest.builder()
.headers(headers)
.bodyJson(bodyJson)
.build());
if (result.isVerified()) {
String plainBody = result.getDecryptedBodyJson();
}
// 或按业务类型的快捷入口
OpenPayFactory.Protocol.Cutpayment.verifyPayNotify(headers, bodyJson);
OpenPayFactory.Protocol.Cutpayment.verifyRefundNotify(headers, bodyJson);
常见问题
HTTP 404
| 错误配置 | 实际请求 | 结果 |
|---|---|---|
serverUrl = https://api.baofu.com/v1 |
/v1 + /v1/protocol/pay → 双 /v1 |
404 |
| 使用旧版非 v4 路径 | 与 SDK 签名路径不一致 | 404 |
正确:serverUrl 仅填域名;路径由 SDK 追加 /v1/protocol/... 或 /v1/split/...(以 OpenAPI 为准)。
依赖解析异常或 classpath 冲突
请确认:① 业务工程使用 classifier=all 一体包;② version 与仓库 artifact 一致;③ 勿再单独引入其它 Open Pay 相关 jar,避免重复类。
SM2 验签失败
检查:① 终端号 / 商户号与加签 userId 规则;② merchantSerialNo、baofuSerialNo 是否与证书一致;③ 响应头 Baofu-Signature 是否存在。
获取帮助
- 开发者站点:SDK 下载、OpenAPI、证书与入网说明
- 商务 / 技术支持:通过对接群或工单联系
- 接口字段与错误码:以开发者站点发布的 OpenAPI 为准