mirror of
https://gitee.com/dromara/dax-pay.git
synced 2025-09-02 10:36:57 +00:00
feat 支付流程调整为多阶段, 预防掉单问题, 增加更多测试类
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
2.0.4:
|
||||
- [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单
|
||||
- [ ] 支付改造
|
||||
- [ ] 关闭订单改造
|
||||
- [ ] 支付补偿改造
|
||||
- [x] 首页驾驶舱功能: 各通道收入和支付情况
|
||||
- [x] 第一排: (数字格式)显示今日收入、支出金额,支付总订单数量、退款总订单数, 时间分支分为: 今日金额/昨日金额/七天内金额
|
||||
- [x] 第二排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额
|
||||
- [x] 第三排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额
|
||||
- [ ] 报表功能
|
||||
- [ ] 各通道收入和支付情况
|
||||
- [ ] 微信新增V3版本接口
|
||||
- [ ] 付款码支付自动路由到V2接口
|
||||
- [ ] 增加转账功能
|
||||
- [ ] 支付宝
|
||||
- [ ] 微信
|
||||
@@ -22,9 +23,10 @@
|
||||
- [ ] 各通道支持单独的限额
|
||||
- [ ] 整个订单支持整体的限额
|
||||
2.0.x 版本内容
|
||||
- [ ] 微信新增V3版本接口
|
||||
- [ ] 付款码支付自动路由到V2接口
|
||||
- [ ] 统一关闭接口增加使用撤销关闭订单
|
||||
- [ ] 增加各类日志记录,例如钱包的各项操作
|
||||
- [ ] 支付流程涉及异步支付时, 更换支付方式需要控制预防客户重复付款
|
||||
- [ ] 增加撤销功能,用于处理线下支付订单的情况
|
||||
- [ ] 数据库表进行规则, 字段设置长度, 增加索引
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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 = "查询支付订单关联支付通道订单")
|
||||
|
@@ -60,7 +60,6 @@ public class PayBuilder {
|
||||
.setExpiredTime(expiredTime)
|
||||
.setCombinationPay(payParam.getPayChannels().size() > 1)
|
||||
.setAsyncPay(asyncPay.isPresent())
|
||||
.setAsyncChannel(asyncPay.orElse(null))
|
||||
.setRefundableBalance(sumAmount);
|
||||
}
|
||||
|
||||
|
@@ -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删除异步支付记录
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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,29 +79,28 @@ 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());
|
||||
payChannelOrders.add(payChannelOrder);
|
||||
// 更新支付订单中的异步通道信息
|
||||
payOrder.setAsyncChannel(payChannelOrder.getChannel());
|
||||
payOrderManager.updateById(payOrder);
|
||||
return payChannelOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新异步支付通道退款余额和状态
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验支付状态,支付成功则返回,支付失败则抛出对应的异常
|
||||
*/
|
||||
|
@@ -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(){
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
Reference in New Issue
Block a user