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 数字信封(卡号、协议号等敏感字段)
  • AuthorizationRequest-IDBaofu-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 与业务状态

  1. 设置参数(进程内全局一次,OpenPayFactory.setOptions)。
  2. 组装 Request(含必填的 riskControlInfo 等字段,见 OpenAPI)。
  3. 处理 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_statusout_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> riskControlInfoprivate 字段,通过 getRiskControlInfo / setRiskControlInfo 访问),序列化为 JSON 字段 risk_control_info。不同商户开通行业要求的键不同,须按 OpenAPI 附录与当笔真实交易填写,禁止写死固定测试值。

接口 Request 字段 说明
预绑卡 bindCardApply riskControlInfo 必填;见协议支付 OpenAPI 风控章节
协议支付 pay riskControlInfo 必填;常见含 goodsCategorychPayIp
H5 收银台 h5Cashier riskControlInfo 必填;至少含 register_timelogin_typedevice_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 收银台 OpenAPIrisk_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-IDConfig.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

paybindCardApply同一路径,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 中需数字信封加密的字段名
响应 OpenPayRawResponsegetHttpStatus()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;写交易仍建议先查单确认
响应验签未通过 响应被篡改或证书/序列号配置错误 勿当成功;核对 publicKeymerchantSerialNobaofuSerialNo
加签失败 / 数字信封* 商户私钥或 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 时,表示已受理、未终态:

  1. 使用 orderQuery(或 refundQuery)轮询,或等待异步 notify
  2. 禁止在未查单的情况下重复 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
}

分账接入说明

分账 不能 跳过支付直接调用。推荐顺序:

  1. 协议支付 pay,请求体设置 profit_sharing=true(勿传 profit_sharing_info
  2. 支付成功且 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:00orig_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 规则;② merchantSerialNobaofuSerialNo 是否与证书一致;③ 响应头 Baofu-Signature 是否存在。


获取帮助

  • 开发者站点:SDK 下载、OpenAPI、证书与入网说明
  • 商务 / 技术支持:通过对接群或工单联系
  • 接口字段与错误码:以开发者站点发布的 OpenAPI 为准