文章内容如下:
- 支付流程简介
- 接口规范
- 前置准备
- 聚合支付实现
- 后置准备
- 总结
支付流程简介
支付流程大致可以分为以下几个步骤:
- 用户选择支付方式(微信或支付宝)。
- 后端根据用户选择的支付方式,构建订单信息,并将订单信息发送给宝付。
- 宝付生成订单响应体。
- 后端解析订单响应体参数并封装返回给前端。
- 用户使用响应体请求进行支付。
- 支付完成后,支付平台会通知->宝付->后端支付结果。
- 后端根据支付结果更新订单状态。
- 增加获取支付结果补偿机制(兜底)
示意图:
接口规范
-
接口说明
-
参数说明
| 参数类型 | 简称 | 参数说明 |
|---|---|---|
| 整数 | I | 整数,十亿以内,简称是大写INT的首字母 |
| 日期 | D | 使用yyyyMMdd(如20210315)的格式 |
| 日期时间 | T | 使用yyyyMMddHHmmss(如20210315155012)的格式 |
| 字符串 | S | 任意合法的字符串,如S(16),表示字符串长度不超过16位 |
| 枚举值 | E | 见具体参数描述 |
| 浮点数 | F | 不超过10亿,小数点后最多7位 |
| 复合类型 | C | 数组内部嵌套键值对 |
-
签名和验签
- 所有请求和返回报文都包含签名参数,接收方务必检查签名的正确性,以保证业务数据合法安全。
- 签名和验签支持国密(SM2)和RSA两种方式,签名结果需要转换成16进制字符串。
- RSA签名使用标准签名算法”SHA256withRSA”,密钥长度2048位。
- RSA签名步骤:明文转json字符串->RSA->HEX(16)->密文(signStr)
-
幂等支持
本文档中部分接口支持幂等,当同一个商户订单号outTradeNo多次调用时,遵循如下:
- 同一个outTradeNo代表同一笔交易,outTradeNo需保证全局唯一
- 如之前已经返回成功,再次调用仍然会返回成功,不会重复处理交易
- outTradeNo只能包含字母、数字、下划线 _
-
接口类型
-
网关 接口
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 商户号 | merId | 是 | S(16) | 100000 | 宝付支付分配的商户号 |
| 终端号 | terId | 是 | S(16) | 100000 | 宝付支付分配的终端号 |
| 接口名称 | method | 是 | S(64) | unified_order | 对应接口的名称 |
| 字符集 | charset | 是 | S(16) | UTF-8 | 字符集编码,固定值UTF-8 |
| 接口版本号 | version | 是 | S(8) | 1 | 对应接口的版本号,固定值1.0 |
| 格式化类型 | format | 是 | S(8) | json | 各个接口业务参数的格式化类型,固定值json |
| 时间戳 | timestamp | 是 | T | 20210315155012 | 时间戳与宝付支付系统时间误差不超过10分钟,格式为yyyyMMddHHmmss,如:2021年3月15日15点50分12秒表示为:20210315155012 |
| 加密签名类型 | signType | 是 | E | SM2 | 商户生成签名和加密字符串使用的算法类型,见附录【签名类型】 |
| 签名证书序列号 | signSn | 是 | S(10) | 1 | 发送方公钥证书序列号,用于接收方验签证书选择 |
| 加密证书序列号 | ncrptnSn | 是 | S(10) | 1 | 接收方公钥证书序列号,用于接收方解密证书选择 |
| 数字信封 | dgtlEnvlp | 否 | S | 使用接收方公钥加密的对称密钥,并做16进制转码,是否需要传值,详见各个业务接口说明 | |
| 签名串 | signStr | 是 | S | 使用发送方私钥签名的非对称密钥,并做16进制转码 | |
| 业务参数 | bizContent | 是 | S | 业务数据报文,JSON格式,具体见各业务接口定义 |
返回参数格式约定如下:
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 返回状态码 | returnCode | 是 | S(16) | SUCCESS | 成功:SUCCESS 失败:FAIL 通信标识,非交易标识 |
| 返回信息 | returnMsg | 是 | S(128) | OK | 当returnCode返回FAIL时返回错误信息,如:验签失败 解密失败 商户号不存在 参数格式验证错误等 检查报文重新发起请求 |
当returnCode返回SUCCESS时,以下字段有值:
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 商户号 | merId | 是 | S(16) | 100000 | 宝付支付分配的商户号 |
| 终端号 | terId | 是 | S(16) | 100000 | 宝付支付分配的终端号 |
| 字符集 | charset | 是 | S(16) | UTF-8 | 字符集编码,固定值UTF-8 |
| 接口版本号 | version | 是 | S(8) | 1 | 对应接口的版本号,固定值1.0 |
| 格式化类型 | format | 是 | S(8) | json | 各个接口业务参数的格式化类型,固定值json |
| 加密签名类型 | signType | 是 | E | SM2 | 与商户原请求签名加密类型一致,如:商户请求采用国密类型,则对应返回也采用国密类型 |
| 签名证书序列号 | signSn | 是 | S(10) | 1 | 发送方公钥证书序列号,用于接收方验签证书选择,固定值1 |
| 加密证书序列号 | ncrptnSn | 是 | S(10) | 1 | 接收方公钥证书序列号,用于接收方解密证书选择,固定值1 |
| 数字信封 | dgtlEnvlp | 否 | S | 使用接收方公钥加密的对称密钥,并做16进制转码,返回是否有值,详见各个业务接口返回参数说明 | |
| 签名串 | signStr | 是 | S | 使用发送方私钥签名的非对称密钥,并做16进制转码 | |
| 返回参数 | dataContent | 是 | S | 业务数据报文,JSON格式,具体见各业务接口返回参数说明 |
-
直连接口
商户请求宝付系统,宝付系统同步返回应答结果。
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 商户号 | merId | 是 | S(16) | 100000 | 宝付支付分配的商户号 |
| 终端号 | terId | 是 | S(16) | 100000 | 宝付支付分配的终端号 |
| 接口名称 | method | 是 | S(64) | unified_order | 对应接口的名称 |
| 字符集 | charset | 是 | S(16) | UTF-8 | 字符集编码,固定值UTF-8 |
| 接口版本号 | version | 是 | S(8) | 1 | 对应接口的版本号,固定值1.0 |
| 格式化类型 | format | 是 | S(8) | json | 各个接口业务参数的格式化类型,固定值json |
| 时间戳 | timestamp | 是 | T | 20210315155012 | 时间戳与宝付支付系统时间误差不超过10分钟,格式为yyyyMMddHHmmss,如:2021年3月15日15点50分12秒表示为:20210315155012 |
| 加密签名类型 | signType | 是 | E | SM2 | 商户生成签名和加密字符串使用的算法类型,见附录【签名类型】 |
| 签名证书序列号 | signSn | 是 | S(10) | 1 | 发送方公钥证书序列号,用于接收方验签证书选择 |
| 加密证书序列号 | ncrptnSn | 是 | S(10) | 1 | 接收方公钥证书序列号,用于接收方解密证书选择 |
| 数字信封 | dgtlEnvlp | 否 | S | 使用接收方公钥加密的对称密钥,并做16进制转码,是否需要传值,详见各个业务接口说明 | |
| 签名串 | signStr | 是 | S | 使用发送方私钥签名的非对称密钥,并做16进制转码 | |
| 业务参数 | bizContent | 是 | S | 业务数据报文,JSON格式,具体见各业务接口定义 |
返回参数格式约定如下:
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 返回状态码 | returnCode | 是 | S(16) | SUCCESS | 成功:SUCCESS 失败:FAIL 通信标识,非交易标识 |
| 返回信息 | returnMsg | 是 | S(128) | OK | 当returnCode返回FAIL时返回错误信息,如:验签失败 解密失败 商户号不存在 参数格式验证错误等 检查报文重新发起请求 |
当returnCode返回SUCCESS时,以下字段有值:
| 商户号 | merId | 是 | S(16) | 100000 | 宝付支付分配的商户号 |
|---|---|---|---|---|---|
| 终端号 | terId | 是 | S(16) | 100000 | 宝付支付分配的终端号 |
| 字符集 | charset | 是 | S(16) | UTF-8 | 字符集编码,固定值UTF-8 |
| 接口版本号 | version | 是 | S(8) | 1 | 对应接口的版本号,固定值1.0 |
| 格式化类型 | format | 是 | S(8) | json | 各个接口业务参数的格式化类型,固定值json |
| 加密签名类型 | signType | 是 | E | SM2 | 与商户原请求签名加密类型一致,如:商户请求采用国密类型,则对应返回也采用国密类型 |
| 签名证书序列号 | signSn | 是 | S(10) | 1 | 发送方公钥证书序列号,用于接收方验签证书选择,固定值1 |
| 加密证书序列号 | ncrptnSn | 是 | S(10) | 1 | 接收方公钥证书序列号,用于接收方解密证书选择,固定值1 |
| 数字信封 | dgtlEnvlp | 否 | S | 使用接收方公钥加密的对称密钥,并做16进制转码,返回是否有值,详见各个业务接口返回参数说明 | |
| 签名串 | signStr | 是 | S | 使用发送方私钥签名的非对称密钥,并做16进制转码 | |
| 返回参数 | dataContent | 是 | S | 业务数据报文,JSON格式,具体见各业务接口返回参数说明 |
-
异步通知
宝付请求商户系统,特定情况下订单的最终处理结果,如:统一下单,退款等。
- 通知参数格式约定如下:
| 字段名 | 变量名 | 必填 | 类型 | 示例值 | 描述 |
|---|---|---|---|---|---|
| 商户号 | merId | 是 | S(16) | 100000 | 宝付支付分配的商户号 |
| 终端号 | terId | 是 | S(16) | 100000 | 宝付支付分配的终端号 |
| 字符集 | charset | 是 | S(16) | UTF-8 | 字符集编码,固定值UTF-8 |
| 接口版本号 | version | 是 | S(8) | 1 | 对应接口的版本号,固定值1.0 |
| 格式化类型 | format | 是 | S(8) | json | 各个接口业务参数的格式化类型,固定值json |
| 通知类型 | notifyType | 是 | E | PAYMENT | 本次通知的订单类型,见附录:【通知类型】 |
| 加密签名类型 | signType | 是 | E | SM2 | 与商户原请求签名加密类型一致,如:商户请求采用国密类型,则对应返回也采用国密类型 |
| 签名证书序列号 | signSn | 是 | S(10) | 1 | 发送方公钥证书序列号,用于接收方验签证书选择,固定值1 |
| 加密证书序列号 | ncrptnSn | 是 | S(10) | 1 | 接收方公钥证书序列号,用于接收方解密证书选择,固定值1 |
| 数字信封 | dgtlEnvlp | 否 | S | 使用接收方公钥加密的对称密钥,并做16进制转码,返回是否有值,详见各个业务接口返回参数说明 | |
| 签名串 | signStr | 是 | S | 使用发送方私钥签名的非对称密钥,并做16进制转码 | |
| 返回参数 | dataContent | 是 | S | 业务数据报文,JSON格式,具体见各业务接口返回参数说明 |
异步通知说明:
-
异步通知以POST方式访问商户设置的通知URL
-
商户系统接收到宝付异步通知,并正确的完成相应的业务处理后,必须同步返回大写OK字符串。
-
由于网络抖动等异常因素以及商户侧未按照约定返回OK,宝付支付系统会多次请求商户侧通知地址,商户系统必须能够正确处理重复的通知。建议商户系统收到通知进行处理时,采用数据锁进行并发控制,检查对应业务数据状态,如未处理,则进行处理,防止多次通知造成资金损失。
-
宝付支付系统会在未正常收到商户侧返回OK时,在24小时内最多通知10次,若在10次通知次数完成后依然未正常收到商户侧按约定返回OK,则不再进行通知,商户应调用订单查询接口确认订单状态。
接下来,我们将分别实现支付宝和微信的支付功能
-
前置准备
项目环境配置
然后,在application.yml或者properties文件中添加支付宝,微信,宝付的配置信息,本文以properties配置为例如下:
- 支付宝配置
- 微信配置
- 宝付配置
具体见nacos ->> xxxxxx.properties
-
支付实现
-
读取配置
- 创建BaoFuConfig类用于读取宝付配置(详情见项目)
说明:可根据实际情况自行扩展属性或新增配置类
首先,我们需要创建一个(ManualPayFactory.java)根据支付类型参数返回具体的支付策略实现类
其次定义了一个名为 PayStrategy 的接口,用于抽象支付策略行为,具体实现由不同的策略类来完成PayStrategy
-
宝付支付
-
BaofuPayStrategy
宝付支付策略,主要功能包括:
- 统一下单交易创建;
- 支付订单查询;
- 申请退款;
- 退款订单查询。
-
PayChannelStrategyFactory
支付渠道策略接口,包含两个核心方法:
- buildPayExtend:构建支付扩展字段和风控信息
- buildPayResponse:构建支付响应数据
-
BaoFuClient
主要功能包括:
- 发起统一下单、查询支付状态、发起退款及查询退款结果;
- 封装请求与响应处理,验证签名,解析返回数据;
- 提供通用的请求执行与结果构建方法。
-
BaoFuApiClient
主要功能包括:
- 初始化配置:加载商户号、终端号、API地址及RSA公私钥。
- 异步加载密钥:从文件存储中读取并解析RSA公/私钥
- 构建请求:将业务数据签名后封装成PostMasterEntity。
- 发送HTTP请求:通过表单方式提交数据到宝付接口。
- 处理响应:解析返回结果为ResultMasterEntity对象。
定义了一个名为 BaofuPayStrategy 的类,并声明其使用接口 PayStrategy,表示这是宝付支付的具体实现
package com.omp.finance.payment.infrastruture.third.impl;
import cn.hutool.json.JSONUtil;
import com.omp.finance.common.config.AliPayConfig;
import com.omp.finance.common.config.BaoFuConfig;
import com.omp.finance.common.config.WeChatPayConfig;
import com.omp.finance.payment.domain.dto.third.request.*;
import com.omp.finance.payment.domain.dto.third.response.PayResult;
import com.omp.finance.payment.domain.dto.third.response.RefundResult;
import com.omp.finance.payment.domain.dto.third.response.RefundResultResponse;
import com.omp.finance.payment.domain.dto.third.response.TradePayResponse;
import com.omp.finance.payment.infrastructure.strategy.PayStrategy;
import com.omp.finance.payment.infrastruture.third.impl.baofu.BaoFuClient;
import com.omp.finance.payment.infrastruture.third.impl.baofu.Entitys.OrderRefundRequest;
import com.omp.finance.payment.infrastruture.third.impl.baofu.Entitys.UnifiedOrderRequest;
import com.omp.finance.payment.infrastruture.third.impl.baofu.PayChannelStrategy;
import com.omp.finance.payment.infrastruture.third.impl.baofu.PayChannelStrategyFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 宝付支付策略实现
*/
@Component("baoFuPayStrategy")
@Slf4j
public class BaofuPayStrategy implements PayStrategy {
@Resource
private BaoFuConfig baoFuConfig;
@Resource
private AliPayConfig aliPayConfig;
@Resource
private PayChannelStrategyFactory strategyFactory;
@Resource
private BaoFuClient baoFuClient;
@Override
public TradePayResponse manualPayTrade(TradePayRequest req) throws Exception {
log.info("发起宝付支付请求 manualPayTrade req:{}", JSONUtil.toJsonStr(req));
// STEP 1: 公共参数检查
req.commonVerify();
if (StringUtils.isBlank(req.getRegisterCityCode())) {
// 使用配置默认地区
req.setRegisterCityCode(baoFuConfig.getAreaCode());
}
// STEP 2: 构建基础 bizContent
UnifiedOrderRequest bizContent = buildBaseBizContent(req);
// STEP 3: 获取对应支付渠道策略
String payCode = req.getPayType();
PayChannelStrategy strategy = strategyFactory.getStrategy(payCode);
// STEP 4: 构建 payExtend(由策略决定)
strategy.buildPayExtend(bizContent, req);
return baoFuClient.unifiedOrder(bizContent, strategy);
}
@Override
public String nativePay(TradePayRequest req) throws Exception {
// 实现宝付 Native 支付逻辑
return null;
}
@Override
public PayResult confirmManualPayResult(PayResultRequest resultReq) throws Exception {
log.info("查询支付结果 confirmManualPayResult req:{}", JSONUtil.toJsonStr(resultReq));
return baoFuClient.queryPayStatus(resultReq.getReqNo());
}
@Override
public void closeManualPayOrder(CloseOrderRequest closeOrderReq) {
// 关闭订单逻辑
}
@Override
public RefundResult manualRefund(RefundRequest refundReq) throws Exception {
log.info("手动退款请求 manualRefund req:{}", JSONUtil.toJsonStr(refundReq));
OrderRefundRequest orderRefundContent = OrderRefundRequest
.createDefault(baoFuConfig)
.withOther(refundReq)
.withNotifyUrl(baoFuConfig.getRefundNotifyUrl());
return baoFuClient.refund(orderRefundContent);
}
@Override
public RefundResultResponse getManualRefundResult(RefundResultRequest resultReq) throws Exception {
log.info("查询手动退款结果 req:{}", JSONUtil.toJsonStr(resultReq));
return baoFuClient.queryRefundStatus(resultReq.getRefundReqNo());
}
@Override
public boolean checkSign(Map<String, String> notifyParamsMap, boolean isApplet) throws Exception {
return baoFuClient.verifySignature(notifyParamsMap);
}
private UnifiedOrderRequest buildBaseBizContent(TradePayRequest req) {
return new UnifiedOrderRequest(req, baoFuConfig, aliPayConfig);
}
}
定义了一个名为 BaoFuClient 的类,并声明表示这是宝付支付的具体实现交互封装
package com.omp.finance.payment.infrastruture.third.impl.baofu;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.omp.finance.account.domain.entity.enums.BaoFuRefundStatusEnum;
import com.omp.finance.common.config.BaoFuConfig;
import com.omp.finance.common.constant.BaoFuConstants;
import com.omp.finance.common.constant.TradePayConstant;
import com.omp.finance.common.exception.FinanceBusinessException;
import com.omp.finance.payment.domain.dto.third.response.PayResult;
import com.omp.finance.payment.domain.dto.third.response.RefundResult;
import com.omp.finance.payment.domain.dto.third.response.RefundResultResponse;
import com.omp.finance.payment.domain.dto.third.response.TradePayResponse;
import com.omp.finance.payment.infrastruture.third.impl.baofu.Entitys.*;
import com.omp.finance.payment.infrastruture.third.impl.baofu.util.JHSignatureUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
/**
* 宝付支付客户端工具类
*/
@Component
@Slf4j
public class BaoFuClient {
@Resource
private BaoFuConfig baoFuConfig;
@Resource
private BaoFuApiClient baoFuRequestBuilder;
/**
* 发起统一下单请求
*/
public TradePayResponse unifiedOrder(UnifiedOrderRequest request, PayChannelStrategy strategy) throws Exception {
ResultMasterEntity rme = executeAndValidate(request, BaoFuConstants.API.UNIFIED_ORDER);
UnifiedOrderResponse response = parseResponse(rme, UnifiedOrderResponse.class);
if (BaoFuConstants.STATUS.FAIL.equals(response.getResultCode())) {
log.error("申请宝付统一下单返回失败code:{},msg;{}", BaoFuConstants.STATUS.FAIL, response.getErrMsg());
throw new FinanceBusinessException(response.getErrCode(), response.getErrMsg());
}
Map<String, Object> chlRetMap = parseChlRetParam(response.getChlRetParam());
return buildTradePayResponse(strategy, chlRetMap);
}
/**
* 查询支付订单状态
*/
public PayResult queryPayStatus(String outTradeNo) throws Exception {
OrderQueryResponse response = sendQueryRequest(outTradeNo, BaoFuConstants.API.ORDER_QUERY, OrderQueryResponse.class);
return buildPayResult(response);
}
/**
* 发起退款请求
*/
public RefundResult refund(OrderRefundRequest request) throws Exception {
ResultMasterEntity rme = executeAndValidate(request, BaoFuConstants.API.ORDER_REFUND);
OrderRefundResponse response = parseResponse(rme, OrderRefundResponse.class);
if (BaoFuConstants.STATUS.SUCCESS.equals(response.getResultCode())) {
return RefundResult.builder()
.refundRespNo(response.getRefundNo())
.refundStatus(TradePayConstant.BUSINESS_REFUND_STATUS_SUCCESS)
.build();
} else {
return RefundResult.builder()
.refundRespNo(response.getRefundNo())
.refundStatus(TradePayConstant.BUSINESS_REFUND_STATUS_FAIL)
.failReason(response.getErrMsg())
.build();
}
}
/**
* 查询退款结果
*/
public RefundResultResponse queryRefundStatus(String refundNo) throws Exception {
OrderRefundQueryResponse response = sendQueryRequest(refundNo, BaoFuConstants.API.REFUND_QUERY, OrderRefundQueryResponse.class);
log.info("查询宝付退款结果返回{}:", response);
String codeByBaoFuCode = BaoFuRefundStatusEnum.getCodeByBaoFuCode(response.getRefundState());
if (codeByBaoFuCode == null) {
log.error("查询宝付退款结果返回失败code:{},msg;{}", response.getRefundState(), response.getErrMsg());
return RefundResultResponse.builder()
.status(null)
.refundRespNo(response.getTradeNo())
.failReason(response.getErrCode())
.build();
}
return RefundResultResponse.builder()
.status(Integer.valueOf(codeByBaoFuCode))
.refundRespNo(response.getTradeNo())
.failReason(response.getErrCode())
.build();
}
/**
* 发送通用请求
*/
private <T> ResultMasterEntity executeAndValidate(T request, String apiMethod) throws Exception {
boolean isLogEnabled = baoFuConfig.getIsLogEnabled();
if (isLogEnabled) {
log.info("{}--宝付请求参数:{}", apiMethod, request);
}
ResultMasterEntity rme = baoFuRequestBuilder.buildAndPost(apiMethod, request);
if (isLogEnabled) {
log.info("{}--宝付响应参数:{}", apiMethod, rme);
}
if (BaoFuConstants.STATUS.FAIL.equals(rme.getReturnCode())) {
log.error("{}--宝付返回失败code:{},msg;{}", apiMethod, rme.getReturnCode(), rme.getReturnMsg());
throw new FinanceBusinessException(rme.getReturnCode(), rme.getReturnMsg());
}
verifySignature(rme.getDataContent(), rme.getSignStr());
return rme;
}
/**
* 通用查询请求
*/
private <T> T sendQueryRequest(String outTradeNo, String apiMethod, Class<T> responseType) throws Exception {
OrderQueryRequest content = new OrderQueryRequest().initDefaultFields(baoFuConfig).withOutTradeNo(outTradeNo);
ResultMasterEntity rme = executeAndValidate(content, apiMethod);
return parseResponse(rme, responseType);
}
/**
* 解析响应数据
*/
private <T> T parseResponse(ResultMasterEntity rme, Class<T> responseType) throws Exception {
return JSON.parseObject(rme.getDataContent(), responseType);
}
/**
* 构建支付返回对象
*/
private TradePayResponse buildTradePayResponse(PayChannelStrategy strategy, Map<String, Object> chlRetMap) {
if (strategy == null) {
throw new FinanceBusinessException("INVALID_PAYMENT_CHANNEL_STRATEGY", "支付渠道策略无效");
}
TradePayResponse response = TradePayResponse.builder().build();
strategy.buildPayResponse(response, chlRetMap);
return response;
}
/**
* 构建支付结果
*/
private PayResult buildPayResult(OrderQueryResponse response) {
if (BaoFuConstants.STATUS.SUCCESS.equals(response.getTxnState())) {
return PayResult.builder()
.isPaySuccess(true)
.payRespNo(response.getReqChlNo())
.payTime(response.getFinishTime())
.build();
}
return PayResult.builder().isPaySuccess(false).build();
}
/**
* 解析 chlRetParam 字段
*/
private Map<String, Object> parseChlRetParam(String chlRetParam) {
try {
return JSON.parseObject(chlRetParam, Map.class);
} catch (Exception e) {
log.error("解析 chlRetParam 失败: {}", chlRetParam, e);
throw new FinanceBusinessException("INVALID_CHL_RET_PARAM", "宝付返回参数格式错误");
}
}
public boolean verifySignature(Map<String, String> notifyParamsMap) throws Exception {
// 参数非空校验
if (notifyParamsMap == null || MapUtil.isEmpty(notifyParamsMap)) {
return false;
}
// 签名字段和数据内容字段必须存在
if (!notifyParamsMap.containsKey(BaoFuConstants.FIELD.SIGN_STR) ||
!notifyParamsMap.containsKey(BaoFuConstants.FIELD.DATA_CONTENT)) {
log.warn("宝付验签失败,请检查参数是否为空");
return false;
}
String signStr = notifyParamsMap.get(BaoFuConstants.FIELD.SIGN_STR);
String dataContent = notifyParamsMap.get(BaoFuConstants.FIELD.DATA_CONTENT);
// 防止空字符串传入签名验证方法
if (StringUtils.isBlank(signStr) || StringUtils.isBlank(dataContent)) {
log.warn("宝付验签失败,请检查参数是否为空");
return false;
}
return verifySignature(dataContent, signStr);
}
/**
* 验签
*/
private boolean verifySignature(String dataContent, String signStr) throws Exception {
if (!JHSignatureUtils.verifySignature(baoFuRequestBuilder.getPublicKey(), dataContent, signStr)) {
log.error("验签失败");
throw new FinanceBusinessException("SIGNATURE_FAILED", "宝付接口验签失败");
}
return true;
}
}
4. ## 后置准备
3.1 回调地址
nacos配置白名单-->omp-poly.properties
- 支付结果通知
- 退款结果通知
3.2 xxl-job
- 支付结果处理
- 退款结果处理
-
总结
本文介绍了如何使用Spring Boot框架将宝付聚合支付集成到Web应用程序中。我们首先介绍了宝付聚合支付的基本概念和流程,然后介绍了如何在Spring Boot中集成宝付聚合支付,包括如何进行支付,退款以及回调处理。最后,我们还加入了xxl-job补偿机制来提高系统支付功能健壮性。