宝付聚合支付

85 阅读15分钟

文章内容如下:

  • 支付流程简介
  • 接口规范
  • 前置准备
  • 聚合支付实现
  • 后置准备
  • 总结

支付流程简介

支付流程大致可以分为以下几个步骤:

  1. 用户选择支付方式(微信或支付宝)。
  2. 后端根据用户选择的支付方式,构建订单信息,并将订单信息发送给宝付。
  3. 宝付生成订单响应体。
  4. 后端解析订单响应体参数并封装返回给前端。
  5. 用户使用响应体请求进行支付。
  6. 支付完成后,支付平台会通知->宝付->后端支付结果。
  7. 后端根据支付结果更新订单状态。
  8. 增加获取支付结果补偿机制(兜底)

示意图:

示意图.PNG

接口规范

  1. 接口说明

  1. 参数说明

参数类型简称参数说明
整数I整数,十亿以内,简称是大写INT的首字母
日期D使用yyyyMMdd(如20210315)的格式
日期时间T使用yyyyMMddHHmmss(如20210315155012)的格式
字符串S任意合法的字符串,如S(16),表示字符串长度不超过16位
枚举值E见具体参数描述
浮点数F不超过10亿,小数点后最多7位
复合类型C数组内部嵌套键值对
  1. 签名和验签

  • 所有请求和返回报文都包含签名参数,接收方务必检查签名的正确性,以保证业务数据合法安全。
  • 签名和验签支持国密(SM2)和RSA两种方式,签名结果需要转换成16进制字符串。
  • RSA签名使用标准签名算法”SHA256withRSA”,密钥长度2048位。
  • RSA签名步骤:明文转json字符串->RSA->HEX(16)->密文(signStr)
  1. 幂等支持

本文档中部分接口支持幂等,当同一个商户订单号outTradeNo多次调用时,遵循如下:

  • 同一个outTradeNo代表同一笔交易,outTradeNo需保证全局唯一
  • 如之前已经返回成功,再次调用仍然会返回成功,不会重复处理交易
  • outTradeNo只能包含字母、数字、下划线 _
  1. 接口类型

  1. 网关 接口

字段名变量名必填类型示例值描述
商户号merIdS(16)100000宝付支付分配的商户号
终端号terIdS(16)100000宝付支付分配的终端号
接口名称methodS(64)unified_order对应接口的名称
字符集charsetS(16)UTF-8字符集编码,固定值UTF-8
接口版本号versionS(8)1对应接口的版本号,固定值1.0
格式化类型formatS(8)json各个接口业务参数的格式化类型,固定值json
时间戳timestampT20210315155012时间戳与宝付支付系统时间误差不超过10分钟,格式为yyyyMMddHHmmss,如:2021年3月15日15点50分12秒表示为:20210315155012
加密签名类型signTypeESM2商户生成签名和加密字符串使用的算法类型,见附录【签名类型】
签名证书序列号signSnS(10)1发送方公钥证书序列号,用于接收方验签证书选择
加密证书序列号ncrptnSnS(10)1接收方公钥证书序列号,用于接收方解密证书选择
数字信封dgtlEnvlpS使用接收方公钥加密的对称密钥,并做16进制转码,是否需要传值,详见各个业务接口说明
签名串signStrS使用发送方私钥签名的非对称密钥,并做16进制转码
业务参数bizContentS业务数据报文,JSON格式,具体见各业务接口定义

返回参数格式约定如下:

字段名变量名必填类型示例值描述
返回状态码returnCodeS(16)SUCCESS成功:SUCCESS 失败:FAIL 通信标识,非交易标识
返回信息returnMsgS(128)OK当returnCode返回FAIL时返回错误信息,如:验签失败 解密失败 商户号不存在 参数格式验证错误等 检查报文重新发起请求

当returnCode返回SUCCESS时,以下字段有值

字段名变量名必填类型示例值描述
商户号merIdS(16)100000宝付支付分配的商户号
终端号terIdS(16)100000宝付支付分配的终端号
字符集charsetS(16)UTF-8字符集编码,固定值UTF-8
接口版本号versionS(8)1对应接口的版本号,固定值1.0
格式化类型formatS(8)json各个接口业务参数的格式化类型,固定值json
加密签名类型signTypeESM2与商户原请求签名加密类型一致,如:商户请求采用国密类型,则对应返回也采用国密类型
签名证书序列号signSnS(10)1发送方公钥证书序列号,用于接收方验签证书选择,固定值1
加密证书序列号ncrptnSnS(10)1接收方公钥证书序列号,用于接收方解密证书选择,固定值1
数字信封dgtlEnvlpS使用接收方公钥加密的对称密钥,并做16进制转码,返回是否有值,详见各个业务接口返回参数说明
签名串signStrS使用发送方私钥签名的非对称密钥,并做16进制转码
返回参数dataContentS业务数据报文,JSON格式,具体见各业务接口返回参数说明
  1. 直连接口

商户请求宝付系统,宝付系统同步返回应答结果。

字段名变量名必填类型示例值描述
商户号merIdS(16)100000宝付支付分配的商户号
终端号terIdS(16)100000宝付支付分配的终端号
接口名称methodS(64)unified_order对应接口的名称
字符集charsetS(16)UTF-8字符集编码,固定值UTF-8
接口版本号versionS(8)1对应接口的版本号,固定值1.0
格式化类型formatS(8)json各个接口业务参数的格式化类型,固定值json
时间戳timestampT20210315155012时间戳与宝付支付系统时间误差不超过10分钟,格式为yyyyMMddHHmmss,如:2021年3月15日15点50分12秒表示为:20210315155012
加密签名类型signTypeESM2商户生成签名和加密字符串使用的算法类型,见附录【签名类型】
签名证书序列号signSnS(10)1发送方公钥证书序列号,用于接收方验签证书选择
加密证书序列号ncrptnSnS(10)1接收方公钥证书序列号,用于接收方解密证书选择
数字信封dgtlEnvlpS使用接收方公钥加密的对称密钥,并做16进制转码,是否需要传值,详见各个业务接口说明
签名串signStrS使用发送方私钥签名的非对称密钥,并做16进制转码
业务参数bizContentS业务数据报文,JSON格式,具体见各业务接口定义

返回参数格式约定如下:

字段名变量名必填类型示例值描述
返回状态码returnCodeS(16)SUCCESS成功:SUCCESS 失败:FAIL 通信标识,非交易标识
返回信息returnMsgS(128)OK当returnCode返回FAIL时返回错误信息,如:验签失败 解密失败 商户号不存在 参数格式验证错误等 检查报文重新发起请求

当returnCode返回SUCCESS时,以下字段有值:

商户号merIdS(16)100000宝付支付分配的商户号
终端号terIdS(16)100000宝付支付分配的终端号
字符集charsetS(16)UTF-8字符集编码,固定值UTF-8
接口版本号versionS(8)1对应接口的版本号,固定值1.0
格式化类型formatS(8)json各个接口业务参数的格式化类型,固定值json
加密签名类型signTypeESM2与商户原请求签名加密类型一致,如:商户请求采用国密类型,则对应返回也采用国密类型
签名证书序列号signSnS(10)1发送方公钥证书序列号,用于接收方验签证书选择,固定值1
加密证书序列号ncrptnSnS(10)1接收方公钥证书序列号,用于接收方解密证书选择,固定值1
数字信封dgtlEnvlpS使用接收方公钥加密的对称密钥,并做16进制转码,返回是否有值,详见各个业务接口返回参数说明
签名串signStrS使用发送方私钥签名的非对称密钥,并做16进制转码
返回参数dataContentS业务数据报文,JSON格式,具体见各业务接口返回参数说明
  1. 异步通知

宝付请求商户系统,特定情况下订单的最终处理结果,如:统一下单,退款等。

  • 通知参数格式约定如下:
字段名变量名必填类型示例值描述
商户号merIdS(16)100000宝付支付分配的商户号
终端号terIdS(16)100000宝付支付分配的终端号
字符集charsetS(16)UTF-8字符集编码,固定值UTF-8
接口版本号versionS(8)1对应接口的版本号,固定值1.0
格式化类型formatS(8)json各个接口业务参数的格式化类型,固定值json
通知类型notifyTypeEPAYMENT本次通知的订单类型,见附录:【通知类型】
加密签名类型signTypeESM2与商户原请求签名加密类型一致,如:商户请求采用国密类型,则对应返回也采用国密类型
签名证书序列号signSnS(10)1发送方公钥证书序列号,用于接收方验签证书选择,固定值1
加密证书序列号ncrptnSnS(10)1接收方公钥证书序列号,用于接收方解密证书选择,固定值1
数字信封dgtlEnvlpS使用接收方公钥加密的对称密钥,并做16进制转码,返回是否有值,详见各个业务接口返回参数说明
签名串signStrS使用发送方私钥签名的非对称密钥,并做16进制转码
返回参数dataContentS业务数据报文,JSON格式,具体见各业务接口返回参数说明

异步通知说明:

  • 异步通知以POST方式访问商户设置的通知URL

  • 商户系统接收到宝付异步通知,并正确的完成相应的业务处理后,必须同步返回大写OK字符串。

  • 由于网络抖动等异常因素以及商户侧未按照约定返回OK,宝付支付系统会多次请求商户侧通知地址,商户系统必须能够正确处理重复的通知。建议商户系统收到通知进行处理时,采用数据锁进行并发控制,检查对应业务数据状态,如未处理,则进行处理,防止多次通知造成资金损失。

  • 宝付支付系统会在未正常收到商户侧返回OK时,在24小时内最多通知10次,若在10次通知次数完成后依然未正常收到商户侧按约定返回OK,则不再进行通知,商户应调用订单查询接口确认订单状态。

接下来,我们将分别实现支付宝和微信的支付功能

  1. 前置准备

项目环境配置

然后,在application.yml或者properties文件中添加支付宝,微信,宝付的配置信息,本文以properties配置为例如下:

  • 支付宝配置
  • 微信配置
  • 宝付配置

具体见nacos ->> xxxxxx.properties

  1. 支付实现

  1. 读取配置

  • 创建BaoFuConfig类用于读取宝付配置(详情见项目)

说明:可根据实际情况自行扩展属性或新增配置类

首先,我们需要创建一个(ManualPayFactory.java)根据支付类型参数返回具体的支付策略实现类

其次定义了一个名为 PayStrategy 的接口,用于抽象支付策略行为,具体实现由不同的策略类来完成PayStrategy

  1. 宝付支付

  1. BaofuPayStrategy

宝付支付策略,主要功能包括:

  • 统一下单交易创建;
  • 支付订单查询;
  • 申请退款;
  • 退款订单查询。
  1. PayChannelStrategyFactory

支付渠道策略接口,包含两个核心方法:

  • buildPayExtend:构建支付扩展字段和风控信息
  • buildPayResponse:构建支付响应数据
  1. BaoFuClient

主要功能包括:

  • 发起统一下单、查询支付状态、发起退款及查询退款结果;
  • 封装请求与响应处理,验证签名,解析返回数据;
  • 提供通用的请求执行与结果构建方法。
  1. 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

  • 支付结果处理
  • 退款结果处理
  1. 总结

本文介绍了如何使用Spring Boot框架将宝付聚合支付集成到Web应用程序中。我们首先介绍了宝付聚合支付的基本概念和流程,然后介绍了如何在Spring Boot中集成宝付聚合支付,包括如何进行支付,退款以及回调处理。最后,我们还加入了xxl-job补偿机制来提高系统支付功能健壮性。