feat 支付流程调整为多阶段, 预防掉单问题, 增加更多测试类

This commit is contained in:
xxm1995
2024-03-23 19:20:07 +08:00
parent 88c7de465c
commit 5e4de478ef
13 changed files with 297 additions and 154 deletions

View File

@@ -1,13 +1,14 @@
2.0.4:
- [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单
- [ ] 支付改造
- [ ] 关闭订单改造
- [ ] 支付补偿改造
- [x] 首页驾驶舱功能: 各通道收入和支付情况
- [x] 第一排: (数字格式)显示今日收入、支出金额,支付总订单数量、退款总订单数, 时间分支分为: 今日金额/昨日金额/七天内金额
- [x] 第二排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额
- [x] 第三排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额
- [ ] 报表功能
- [ ] 各通道收入和支付情况
- [ ] 微信新增V3版本接口
- [ ] 付款码支付自动路由到V2接口
- [ ] 增加转账功能
- [ ] 支付宝
- [ ] 微信
@@ -22,9 +23,10 @@
- [ ] 各通道支持单独的限额
- [ ] 整个订单支持整体的限额
2.0.x 版本内容
- [ ] 微信新增V3版本接口
- [ ] 付款码支付自动路由到V2接口
- [ ] 统一关闭接口增加使用撤销关闭订单
- [ ] 增加各类日志记录,例如钱包的各项操作
- [ ] 支付流程涉及异步支付时, 更换支付方式需要控制预防客户重复付款
- [ ] 增加撤销功能,用于处理线下支付订单的情况
- [ ] 数据库表进行规则, 字段设置长度, 增加索引

View File

@@ -36,7 +36,6 @@ public class PayOrderTest {
DaxPayKit.initConfig(config);
}
/**
* 单通道支付
*/
@@ -46,10 +45,10 @@ public class PayOrderTest {
param.setClientIp("127.0.0.1");
param.setNotNotify(true);
param.setBusinessNo("P0001");
param.setBusinessNo("P0004");
param.setTitle("测试接口支付");
PayChannelParam payChannelParam = new PayChannelParam();
payChannelParam.setChannel(PayChannelEnum.WECHAT.getCode());
payChannelParam.setChannel(PayChannelEnum.UNION_PAY.getCode());
payChannelParam.setWay(PayWayEnum.QRCODE.getCode());
payChannelParam.setAmount(1);
@@ -64,13 +63,13 @@ public class PayOrderTest {
* 多通道支付. 全部为同步支付方式
*/
@Test
public void multiPay() {
public void multiSyncPay() {
PayParam param = new PayParam();
param.setClientIp("127.0.0.1");
param.setNotNotify(true);
param.setBusinessNo("P0002");
param.setTitle("测试组合支付");
param.setBusinessNo("P0005");
param.setTitle("测试组合支付(全同步)");
// 现金支付
PayChannelParam cash = new PayChannelParam();
cash.setChannel(PayChannelEnum.CASH.getCode());
@@ -104,4 +103,35 @@ public class PayOrderTest {
System.out.println(execute);
System.out.println(execute.getData());
}
/**
* 多通道支付. 包含异步支付
*/
@Test
public void multiAsyncPay() {
PayParam param = new PayParam();
param.setClientIp("127.0.0.1");
param.setNotNotify(true);
param.setBusinessNo("P0009");
param.setTitle("测试组合支付(包含异步支付)");
// 现金支付
PayChannelParam cash = new PayChannelParam();
cash.setChannel(PayChannelEnum.CASH.getCode());
cash.setWay(PayWayEnum.NORMAL.getCode());
cash.setAmount(10);
// 异步支付
PayChannelParam async = new PayChannelParam();
async.setChannel(PayChannelEnum.ALI.getCode());
async.setWay(PayWayEnum.WEB.getCode());
async.setAmount(1);
// 组装组合支付的参数
List<PayChannelParam> payChannels = Arrays.asList(cash, async);
param.setPayChannels(payChannels);
DaxPayResult<PayOrderModel> execute = DaxPayKit.execute(param);
System.out.println(execute);
System.out.println(execute.getData());
}
}

View File

@@ -63,7 +63,7 @@ public class PayOrderController {
@Operation(summary = "查询支付订单扩展信息")
@GetMapping("/getExtraById")
public ResResult<PayOrderExtraDto> getExtraById(Long id){
return Res.ok(payOrderExtraService.findById(id));
return Res.ok(payOrderExtraService.findById(id).toDto());
}
@Operation(summary = "查询支付订单关联支付通道订单")

View File

@@ -60,7 +60,6 @@ public class PayBuilder {
.setExpiredTime(expiredTime)
.setCombinationPay(payParam.getPayChannels().size() > 1)
.setAsyncPay(asyncPay.isPresent())
.setAsyncChannel(asyncPay.orElse(null))
.setRefundableBalance(sumAmount);
}

View File

@@ -35,6 +35,15 @@ public class PayChannelOrderManager extends BaseManager<PayChannelOrderMapper, P
.eq(PayChannelOrder::getChannel,channel)
.oneOpt();
}
/**
* 根据订单id和支付通道查询
*/
public Optional<PayChannelOrder> findByAsyncChannel(Long paymentId) {
return lambdaQuery()
.eq(PayChannelOrder::getPaymentId,paymentId)
.eq(PayChannelOrder::isAsync,true)
.oneOpt();
}
/**
* 根据订单id删除异步支付记录

View File

@@ -57,7 +57,7 @@ public class PayChannelOrder extends MpCreateEntity implements EntityBaseFunctio
@DbColumn(comment = "支付状态")
private String status;
@DbColumn(comment = "支付时间")
@DbColumn(comment = "支付完成时间")
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private LocalDateTime payTime;

View File

@@ -2,12 +2,13 @@ package cn.bootx.platform.daxpay.service.core.order.pay.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.util.ResultConvertUtil;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.service.common.context.PayLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundChannelOrder;
@@ -16,7 +17,6 @@ import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@@ -36,6 +36,8 @@ import java.util.Optional;
public class PayChannelOrderService {
private final PayChannelOrderManager channelOrderManager;
private final PayOrderManager payOrderManager;
/**
* 根据支付ID查询列表
*/
@@ -51,10 +53,10 @@ public class PayChannelOrderService {
}
/**
* 切换支付订单关联的异步支付通道, 同时会设置是否支付完成状态, 并返回通道订单
* 使用单独事务
* 切换支付订单关联的异步支付通道, 设置是否支付完成状态, 并返回通道订单
* 同时会更新支付订单上的异步通道信息
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
@Transactional(rollbackFor = Exception.class)
public PayChannelOrder switchAsyncPayChannel(PayOrder payOrder, PayChannelParam payChannelParam){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
// 是否支付完成
@@ -66,8 +68,9 @@ public class PayChannelOrderService {
if (Objects.nonNull(channelParam)){
channelParamStr = JSONUtil.toJsonStr(channelParam);
}
PayChannelOrder payChannelOrder;
if (!payOrderChannelOpt.isPresent()){
PayChannelOrder payChannelOrder = new PayChannelOrder();
payChannelOrder = new PayChannelOrder();
// 替换原有的的支付通道信息
payChannelOrder.setPayWay(payChannelParam.getWay())
.setPaymentId(payOrder.getId())
@@ -76,28 +79,27 @@ public class PayChannelOrderService {
.setPayWay(payChannelParam.getWay())
.setAmount(payChannelParam.getAmount())
.setRefundableBalance(payChannelParam.getAmount())
.setPayTime(LocalDateTime.now())
.setChannelExtra(channelParamStr)
.setStatus(payStatus.getCode());
channelOrderManager.deleteByPaymentIdAndAsync(payOrder.getId());
channelOrderManager.save(payChannelOrder);
payInfo.getPayChannelOrders().add(payChannelOrder);
return payChannelOrder;
} else {
// 更新支付通道信息
PayChannelOrder payChannelOrder = payOrderChannelOpt.get();
payChannelOrder
.setPayWay(payChannelParam.getWay())
.setPayTime(LocalDateTime.now())
payChannelOrder = payOrderChannelOpt.get();
payChannelOrder.setPayWay(payChannelParam.getWay())
.setChannelExtra(channelParamStr)
.setStatus(payStatus.getCode());
channelOrderManager.updateById(payChannelOrder);
// 更新时一次请求有多次访问, 对上下文中的通道支付订单进行替换
List<PayChannelOrder> payChannelOrders = payInfo.getPayChannelOrders();
payChannelOrders.removeIf(o->Objects.equals(o.getChannel(),payChannelOrder.getChannel()));
payChannelOrders.add(payOrderChannelOpt.get());
return payChannelOrder;
}
// 对上下文中的通道支付订单进行替换
List<PayChannelOrder> payChannelOrders = payInfo.getPayChannelOrders();
payChannelOrders.removeIf(o->Objects.equals(o.getChannel(),payChannelOrder.getChannel()));
payChannelOrders.add(payChannelOrder);
// 更新支付订单中的异步通道信息
payOrder.setAsyncChannel(payChannelOrder.getChannel());
payOrderManager.updateById(payOrder);
return payChannelOrder;
}
/**

View File

@@ -3,10 +3,11 @@ package cn.bootx.platform.daxpay.service.core.order.pay.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderExtraManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.service.dto.order.pay.PayOrderExtraDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 支付订单扩展信息
@@ -22,7 +23,16 @@ public class PayOrderExtraService {
/**
* 查询详情
*/
public PayOrderExtraDto findById(Long id){
return payOrderExtraManager.findById(id).map(PayOrderExtra::toDto).orElseThrow(()->new DataNotExistException("支付订单扩展信息不存在"));
public PayOrderExtra findById(Long id){
return payOrderExtraManager.findById(id).orElseThrow(()->new DataNotExistException("支付订单扩展信息不存在"));
}
/**
* 更新, 使用单独事务
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void update(PayOrderExtra payOrderExtra){
payOrderExtraManager.updateById(payOrderExtra);
}
}

View File

@@ -134,7 +134,7 @@ public class PayCloseService {
* 成功后处理方法
*/
private void successHandler(PayOrder payOrder, List<PayChannelOrder> payChannelOrders){
// 取消订单
// 关闭订单
payOrder.setStatus(PayStatusEnum.CLOSE.getCode())
.setCloseTime(LocalDateTime.now());
payOrderService.updateById(payOrder);

View File

@@ -32,7 +32,7 @@ public class PayStrategyFactory {
* @param payChannelParam 支付类型
* @return 支付策略
*/
public AbsPayStrategy createAsyncFront(PayChannelParam payChannelParam) {
public AbsPayStrategy create(PayChannelParam payChannelParam) {
AbsPayStrategy strategy;
PayChannelEnum channelEnum = PayChannelEnum.findByCode(payChannelParam.getChannel());
switch (channelEnum) {
@@ -111,7 +111,7 @@ public class PayStrategyFactory {
}
// 此处有一个根据Type的反转排序
sortList.stream().filter(Objects::nonNull).forEach(payMode -> list.add(createAsyncFront(payMode)));
sortList.stream().filter(Objects::nonNull).forEach(payMode -> list.add(create(payMode)));
return list;
}

View File

@@ -1,12 +1,14 @@
package cn.bootx.platform.daxpay.service.core.payment.pay.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.service.common.context.*;
import cn.bootx.platform.daxpay.service.common.context.ApiInfoLocal;
import cn.bootx.platform.daxpay.service.common.context.NoticeLocal;
import cn.bootx.platform.daxpay.service.common.context.PayLocal;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.order.pay.builder.PayBuilder;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
@@ -23,6 +25,7 @@ import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.Arrays;
@@ -119,22 +122,32 @@ public class PayAssistService {
noticeInfo.setQuitUrl(payParam.getQuitUrl());
}
/**
* 获取异步支付参数
*/
public PayChannelParam getAsyncPayParam(PayParam payParam, PayOrder payOrder) {
// 查询之前的支付方式
String asyncPayChannel = payOrder.getAsyncChannel();
PayChannelOrder payChannelOrder = payChannelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), asyncPayChannel)
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
// 查询异步支付方式
return payParam.getPayChannels()
.stream()
.filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel()))
.findFirst()
.orElseThrow(() -> new PayFailureException("支付参数异常, 不存在异步支付方式"));
}
/**
* 切换异步支付参数
*/
public PayChannelParam switchAsyncPayParam(PayParam payParam, PayOrder payOrder) {
// 查询之前的异步支付方式
PayChannelOrder payChannelOrder = payChannelOrderManager.findByAsyncChannel(payOrder.getId())
.orElseThrow(() -> new PayFailureException("支付订单数据异常, 不存在异步支付方式"));
// 新的异步支付方式
PayChannelParam payChannelParam = payParam.getPayChannels()
.stream()
.filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel()))
.findFirst()
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
.orElseThrow(() -> new PayFailureException("支付参数异常, 不存在异步支付方式"));
// 新传入的金额是否一致
if (!Objects.equals(payChannelOrder.getAmount(), payChannelParam.getAmount())){
throw new PayFailureException("传入的支付金额非法!与订单金额不一致");
@@ -145,6 +158,7 @@ public class PayAssistService {
/**
* 创建支付订单并保存, 返回支付订单
*/
@Transactional(rollbackFor = Exception.class)
public PayOrder createPayOrder(PayParam payParam) {
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
// 构建支付订单并保存
@@ -170,34 +184,6 @@ public class PayAssistService {
payInfo.getPayChannelOrders().addAll(channelOrders);
}
/**
* 更新支付订单扩展参数
* @param payParam 支付参数
* @param paymentId 支付订单id
*/
public PayOrderExtra updatePayOrderExtra(PayParam payParam,Long paymentId){
ApiInfoLocal apiInfo = PaymentContextLocal.get().getApiInfo();
RequestLocal requestInfo = PaymentContextLocal.get().getRequestInfo();
PlatformLocal platformInfo = PaymentContextLocal.get().getPlatformInfo();
PayOrderExtra payOrderExtra = payOrderExtraManager.findById(paymentId)
.orElseThrow(() -> new DataNotExistException("支付订单不存在"));
NoticeLocal noticeInfo = PaymentContextLocal.get().getNoticeInfo();
String notifyUrl = noticeInfo.getNotifyUrl();
String returnUrl = noticeInfo.getReturnUrl();
payOrderExtra.setReqTime(payParam.getReqTime())
.setReqSignType(platformInfo.getSignType())
.setReqSign(payParam.getSign())
.setNotifyUrl(notifyUrl)
.setReturnUrl(returnUrl)
.setNoticeSign(apiInfo.isNoticeSign())
.setAttach(payParam.getAttach())
.setClientIp(payParam.getClientIp())
.setReqId(requestInfo.getReqId());
return payOrderExtraManager.updateById(payOrderExtra);
}
/**
* 校验支付状态,支付成功则返回,支付失败则抛出对应的异常
*/

View File

@@ -10,8 +10,11 @@ import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.bootx.platform.daxpay.service.common.context.PayLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.order.pay.builder.PayBuilder;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderExtraService;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.service.core.payment.notice.service.ClientNoticeService;
import cn.bootx.platform.daxpay.service.core.payment.pay.factory.PayStrategyFactory;
@@ -26,7 +29,6 @@ import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
@@ -53,17 +55,20 @@ public class PayService {
private final PayOrderService payOrderService;
private final PayOrderExtraService payOrderExtraManager;
private final PayAssistService payAssistService;
private final ClientNoticeService clientNoticeService;
private final PayChannelOrderManager payChannelOrderManager;
private final LockTemplate lockTemplate;
/**
* 简单下单, 可以视为不支持组合支付的下单接口
*/
@Transactional(rollbackFor = Exception.class)
public PayResult simplePay(SimplePayParam simplePayParam) {
// 组装支付参数
PayParam payParam = new PayParam();
@@ -82,14 +87,13 @@ public class PayService {
/**
* 支付入口
* 支付下单接口(同步/异步/组合支付)
* 1. 同步支付:包含一个或多个同步支付通道进行支付
* 2. 异步支付:例如支付宝、微信,发起支付后还需要跳转第三方平台进行支付,支付后通常需要进行回调,之后才完成支付记录
* 3. 组合支付:主要是混合了同步支付和异步支付,同时异步支付只能有一个,在支付时先对同步支付进行扣减,然后异步支付回调结束后完成整个支付单
* 注意:
* 组合支付在非第一次支付的时候,只对新传入的异步支付通道进行处理,该通道的价格使用第一次发起的价格,旧的同步支付如果传入后也不做处理,
* 支付单中支付通道列表将会为 旧有的同步支付+新传入的异步支付方式(在具体支付实现中处理)
* 1. 同步支付:包含一个或多个同步支付通道进行支付, 使用一个事务进行包裹,要么成功要么失败
* 2. 异步支付:会首先创建订单信息,然后再发起支付,支付成功后更新订单和通道订单信息,支付失败也会存在订单信息,预防丢单
* 3. 组合支付(包含异步支付):会首先进行同步通道的支付,支付完成后才会调用异步支付,如果同步支付失败会回滚信息,不会进行存库
* 执行异步通道支付的逻辑与上面异步支付的逻辑一致
* 4. 同步支付一次支付完成,不允许重复发起支付
* 5. 重复支付时不允许中途将异步支付换为同步支付也不允许更改出支付通道和支付方式之外的支付参数请求时间、IP、签名等可以更改
*
* 订单数据会先进行入库, 才会进行发起支付, 在调用各通道支付之前发生错误, 数据不会入库
*/
public PayResult pay(PayParam payParam){
@@ -115,7 +119,7 @@ public class PayService {
List<PayChannelParam> payChannels = payParam.getPayChannels();
// 不包含异步支付通道
if (PayUtil.isNotSync(payChannels)){
return this.firstSyncPay(payParam);
return SpringUtil.getBean(this.getClass()).firstSyncPay(payParam);
}
// 单个异步通道支付
else if (payChannels.size() == 1 && !PayUtil.isNotSync(payChannels)) {
@@ -128,21 +132,35 @@ public class PayService {
throw new PayFailureException("支付参数错误");
}
} else {
List<PayChannelParam> payChannels = payParam.getPayChannels();
// 不包含异步支付通道
if (PayUtil.isNotSync(payChannels)){
throw new PayFailureException("支付参数错误, 不可以中途切换为同步支付通道");
}
// 单个异步通道支付
if (payOrder.isAsyncPay() && !payOrder.isCombinationPay()){
return this.repeatAsyncPay(payParam,payOrder);
}
// 包含异步通道的组合支付
else if (payOrder.isAsyncPay()){
return this.repeatCombinationPay(payParam, payOrder);
} else {
throw new PayFailureException("支付参数错误");
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lockTemplate.releaseLock(lock);
}
return null;
}
/**
* 首次同步支付, 可以为一个或多个同步通道进行支付
* 使用事务,保证支付只有成功或失败两种状态
*/
private PayResult firstSyncPay(PayParam payParam){
@Transactional(rollbackFor = Exception.class)
public PayResult firstSyncPay(PayParam payParam){
// 创建支付订单和扩展记录并返回支付订单对象
PayOrder payOrder = payAssistService.createPayOrder(payParam);
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
@@ -179,11 +197,27 @@ public class PayService {
/**
* 首次单个异步通道支付
* 拆分为多阶段1. 保存订单记录信息 2 调起支付 3. 支付成功后操作
*/
private PayResult firstAsyncPay(PayParam payParam){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
// 开启新事务执行订单预保存操作, 并返回对应的支付策略组
AbsPayStrategy asyncPayStrategy = SpringUtil.getBean(this.getClass()).asyncPayPreSave(payParam);
// 开启新事务执行订单预保存操作
PayOrder payOrder = payAssistService.createPayOrder(payParam);
// 获取支付方式,通过工厂生成对应的策略组
List<AbsPayStrategy> strategies = PayStrategyFactory.createAsyncLast(payParam.getPayChannels());
if (CollectionUtil.isEmpty(strategies)) {
throw new PayUnsupportedMethodException();
}
// 获取异步通道
AbsPayStrategy asyncPayStrategy = Optional.ofNullable(strategies.get(0))
.orElseThrow(() -> new PayFailureException("数据和代码异常, 请排查代码"));
// 初始化支付的参数
asyncPayStrategy.initPayParam(payOrder, payParam);
// 执行支付前处理动作
asyncPayStrategy.doBeforePayHandler();
// 支付操作
try {
@@ -192,19 +226,30 @@ public class PayService {
// 记录错误原因
PayOrderExtra payOrderExtra = payInfo.getPayOrderExtra();
payOrderExtra.setErrorMsg(e.getMessage());
payOrderExtraManager.update(payOrderExtra);
throw e;
}
// 支付调起成功后操作, 使用事务来保证数据一致性
return SpringUtil.getBean(this.getClass()).firstAsyncPaySuccess(asyncPayStrategy,payOrder);
}
/**
* 首次单通道异步支付成功后操作, 更新订单和扥爱松
*/
@Transactional(rollbackFor = Exception.class)
public PayResult firstAsyncPaySuccess(AbsPayStrategy asyncPayStrategy, PayOrder payOrder){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
// 支付调用成功操作,
asyncPayStrategy.doSuccessHandler();
PayOrder payOrder = payInfo.getPayOrder();
// 如果支付完成, 进行订单完成处理, 同时发送回调消息
if (payInfo.isPayComplete()) {
// TODO 使用网关返回的时间
payOrder.setGatewayOrderNo(payInfo.getGatewayOrderNo())
.setStatus(SUCCESS.getCode())
.setPayTime(LocalDateTime.now());
// TODO 使用网关返回的时间
payOrderService.updateById(payOrder);
}
payOrderService.updateById(payOrder);
// 如果支付完成 发送通知
if (Objects.equals(payOrder.getStatus(), SUCCESS.getCode())){
clientNoticeService.registerPayNotice(payOrder, payInfo.getPayOrderExtra(), payInfo.getPayChannelOrders());
@@ -217,6 +262,40 @@ public class PayService {
*/
private PayResult firstCombinationPay(PayParam payParam){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
// ------------------------- 进行同步支付 -------------------------------
PayOrder payOrder = SpringUtil.getBean(this.getClass()).firstCombinationSyncPay(payParam);
// ------------------------- 进行异步支付 -------------------------------
// 创建异步通道策略类
//获取异步支付通道参数并构建支付策略
PayChannelParam payChannelParam = payAssistService.getAsyncPayParam(payParam, payOrder);
AbsPayStrategy asyncPayStrategy = PayStrategyFactory.create(payChannelParam);
// 初始化支付的参数
asyncPayStrategy.initPayParam(payOrder, payParam);
// 支付前准备
asyncPayStrategy.doBeforePayHandler();
// 设置异步支付通道订单信息
asyncPayStrategy.generateChannelOrder();
try {
// 异步支付操作
asyncPayStrategy.doPayHandler();
} catch (Exception e) {
// 记录错误原因
PayOrderExtra payOrderExtra = payInfo.getPayOrderExtra();
payOrderExtra.setErrorMsg(e.getMessage());
payOrderExtraManager.update(payOrderExtra);
throw e;
}
// 支付调起成功后操作, 使用事务来保证数据一致性
return SpringUtil.getBean(this.getClass()).firstCombinationPaySuccess(asyncPayStrategy);
}
/**
* 首次组合支付, 先进行同步支付, 如果成功返回支付订单
* 注意: 同时也执行异步支付通道订单的保存, 保证订单操作的原子性
*/
@Transactional(rollbackFor = Exception.class)
public PayOrder firstCombinationSyncPay(PayParam payParam){
// 创建支付订单和扩展记录并返回支付订单对象
PayOrder payOrder = payAssistService.createPayOrder(payParam);
@@ -230,7 +309,7 @@ public class PayService {
strategy.initPayParam(payOrder, payParam);
}
// 取出同步通道 然后进行支付
// 取出同步通道 然后进行同步通道的支付, 使用单独事务
List<AbsPayStrategy> syncStrategies = strategies.stream()
.filter(strategy -> !ASYNC_TYPE.contains(strategy.getChannel()))
.collect(Collectors.toList());
@@ -242,94 +321,120 @@ public class PayService {
// 执行支付前处理动作
syncStrategies.forEach(AbsPayStrategy::doBeforePayHandler);
// 生成支付通道订单
syncStrategies.forEach(AbsPayStrategy::generateChannelOrder);
// 生成支付通道订单, 同时也执行异步支付通道订单的保存, 保证订单操作的原子性
strategies.forEach(AbsPayStrategy::generateChannelOrder);
// 支付操作
syncStrategies.forEach(AbsPayStrategy::doPayHandler);
// 支付成功操作, 进行扣款、创建记录类类似的操作
// 支付调起成功操作, 进行扣款、创建记录类类似的操作
syncStrategies.forEach(AbsPayStrategy::doSuccessHandler);
payOrder.setStatus(SUCCESS.getCode())
.setPayTime(LocalDateTime.now());
// 保存通道支付订单
payAssistService.savePayChannelOrder(strategies);
// ------------------------- 进行异步支付 -------------------------------
// 筛选出异步通道策略类
AbsPayStrategy asyncPayStrategy = strategies.stream()
.filter(strategy -> !ASYNC_TYPE.contains(strategy.getChannel()))
.findFirst()
.orElseThrow(() -> new PayFailureException("数据和代码异常, 请排查代码"));
return payOrder;
}
/**
* 首次组合支付成功后操作
*/
@Transactional(rollbackFor = Exception.class)
public PayResult firstCombinationPaySuccess(AbsPayStrategy asyncPayStrategy){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
PayOrder payOrder = payInfo.getPayOrder();
// 支付调起成功的处理
asyncPayStrategy.doSuccessHandler();
// 如果支付完成, 进行订单完成处理, 同时发送回调消息
if (payInfo.isPayComplete()) {
// TODO 使用网关返回的时间
payOrder.setGatewayOrderNo(payInfo.getGatewayOrderNo())
.setStatus(SUCCESS.getCode())
.setPayTime(LocalDateTime.now());
}
payOrderService.updateById(payOrder);
payOrderService.updateById(payOrder);
// 如果支付完成 发送通知
if (Objects.equals(payOrder.getStatus(), SUCCESS.getCode())){
clientNoticeService.registerPayNotice(payOrder, payInfo.getPayOrderExtra(), payInfo.getPayChannelOrders());
}
return PayBuilder.buildPayResultByPayOrder(payOrder);
}
/**
* 重复支付-单异步通道支付
*/
public PayResult repeatAsyncPay(PayParam payParam, PayOrder payOrder){
// 查询支付扩展订单信息
PayOrderExtra payOrderExtra = payOrderExtraManager.findById(payOrder.getId());
//获取异步支付通道参数并构建支付策略
PayChannelParam payChannelParam = payAssistService.switchAsyncPayParam(payParam, payOrder);
AbsPayStrategy asyncPayStrategy = PayStrategyFactory.create(payChannelParam);
// 初始化支付的参数
asyncPayStrategy.initPayParam(payOrder, payParam);
// 执行支付前处理动作
asyncPayStrategy.doBeforePayHandler();
// 更新支付通道订单的信息
asyncPayStrategy.generateChannelOrder();
try {
// 支付操作
asyncPayStrategy.doPayHandler();
} catch (Exception e) {
// 记录错误原因
payOrderExtra.setErrorMsg(e.getMessage());
payOrderExtraManager.update(payOrderExtra);
throw e;
}
// 支付调起成功后操作, 使用事务来保证数据一致性
return SpringUtil.getBean(this.getClass()).repeatPaySuccess(asyncPayStrategy, payOrder, payOrderExtra);
}
/**
* 重复发起组合支付(包含异步支付)
*/
public PayResult repeatCombinationPay(PayParam payParam, PayOrder payOrder){
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
PayOrderExtra payOrderExtra = payInfo.getPayOrderExtra();
PayChannelParam payChannelParam = payAssistService.switchAsyncPayParam(payParam, payOrder);
// 取出异步通道 然后进行支付
AbsPayStrategy asyncPayStrategy = PayStrategyFactory.create(payChannelParam);
// 初始化参数
asyncPayStrategy.initPayParam(payOrder, payParam);
// 支付前准备
asyncPayStrategy.doBeforePayHandler();
// 设置异步支付通道订单信息
// 更新异步支付通道订单信息
asyncPayStrategy.generateChannelOrder();
try {
// 异步支付操作
asyncPayStrategy.doPayHandler();
} catch (Exception e) {
// 记录错误原因
PayOrderExtra payOrderExtra = payInfo.getPayOrderExtra();
payOrderExtra.setErrorMsg(e.getMessage());
payOrderExtraManager.update(payOrderExtra);
throw e;
}
// 支付调成功操作, 进行扣款、创建记录类类似的操作
// 支付调成功操作, 使用事务来保证数据一致性
return SpringUtil.getBean(this.getClass()).repeatPaySuccess(asyncPayStrategy, payOrder,payOrderExtra);
}
/**
* 重复支付成功后操作
*/
@Transactional(rollbackFor = Exception.class)
public PayResult repeatPaySuccess(AbsPayStrategy asyncPayStrategy, PayOrder payOrder, PayOrderExtra payOrderExtra) {
PayLocal payInfo = PaymentContextLocal.get().getPayInfo();
asyncPayStrategy.doSuccessHandler();
// 如果支付完成, 进行订单完成处理, 同时发送回调消息
if (payInfo.isPayComplete()) {
// TODO 使用网关返回的时间
payOrder.setGatewayOrderNo(payInfo.getGatewayOrderNo())
.setStatus(SUCCESS.getCode())
.setPayTime(LocalDateTime.now());
// TODO 使用网关返回的时间
payOrderService.updateById(payOrder);
}
payOrderService.updateById(payOrder);
// 如果支付完成 发送通知
if (Objects.equals(payOrder.getStatus(), SUCCESS.getCode())){
clientNoticeService.registerPayNotice(payOrder, payInfo.getPayOrderExtra(), payInfo.getPayChannelOrders());
// 查询通道订单
List<PayChannelOrder> payChannelOrders = payChannelOrderManager.findAllByPaymentId(payOrder.getId());
clientNoticeService.registerPayNotice(payOrder, payOrderExtra, payChannelOrders);
}
return PayBuilder.buildPayResultByPayOrder(payOrder);
}
/**
* 异步支付预报保存处理, 无论请求成功还是失败, 各种订单对象都会保存
* 1. 创建支付订单/通道支付订单/扩展信息
* 2, 返回支付列表记录
*/
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public AbsPayStrategy asyncPayPreSave(PayParam payParam){
// 创建支付订单和扩展记录并返回支付订单对象
PayOrder payOrder = payAssistService.createPayOrder(payParam);
// 获取支付方式,通过工厂生成对应的策略组
List<AbsPayStrategy> strategies = PayStrategyFactory.createAsyncLast(payParam.getPayChannels());
if (CollectionUtil.isEmpty(strategies)) {
throw new PayUnsupportedMethodException();
}
AbsPayStrategy absPayStrategy = Optional.ofNullable(strategies.get(0))
.orElseThrow(() -> new PayFailureException("数据和代码异常, 请排查代码"));
// 初始化支付的参数
absPayStrategy.initPayParam(payOrder, payParam);
// 执行支付前处理动作
absPayStrategy.doBeforePayHandler();
// 执行支付通道订单的生成和保存
absPayStrategy.generateChannelOrder();
return absPayStrategy;
}
/**
* 重复支付
*/
public void repeatAsyncPay(){
}
/**
*
*/
public void repeatCombinationPay(){
}
}

View File

@@ -101,9 +101,9 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/
@Override
public void doSuccessHandler() {
PayChannelOrder payChannelOrder = channelOrderService.switchAsyncPayChannel(this.getOrder(), this.getPayChannelParam());
this.getOrder().setAsyncChannel(this.getChannel().getCode());
PayLocal asyncPayInfo = PaymentContextLocal.get().getPayInfo();
// 更新通道支付订单
PayChannelOrder payChannelOrder = channelOrderService.switchAsyncPayChannel(this.getOrder(), this.getPayChannelParam());
// 是否支付完成, 保存流水记录
if (asyncPayInfo.isPayComplete()){
weChatPayRecordService.pay(this.getOrder(), payChannelOrder);