diff --git a/_doc/Task.md b/_doc/Task.md index dc72afd13..e2aaf2714 100644 --- a/_doc/Task.md +++ b/_doc/Task.md @@ -1,13 +1,14 @@ 2.0.4: - [ ] 支付流程也改为先落库后支付情况, 避免极端情况导致掉单 + - [ ] 支付改造 + - [ ] 关闭订单改造 + - [ ] 支付补偿改造 - [x] 首页驾驶舱功能: 各通道收入和支付情况 - [x] 第一排: (数字格式)显示今日收入、支出金额,支付总订单数量、退款总订单数, 时间分支分为: 今日金额/昨日金额/七天内金额 - [x] 第二排: (饼图)显示各通道各支付方式数量和占比, 时间分为: 今日金额/昨日金额/七天内金额 - [x] 第三排: (折线图)显示各通道支付分为支付金额和退款,时间分为: 今日金额/昨日金额/七天内金额 - [ ] 报表功能 - [ ] 各通道收入和支付情况 -- [ ] 微信新增V3版本接口 - - [ ] 付款码支付自动路由到V2接口 - [ ] 增加转账功能 - [ ] 支付宝 - [ ] 微信 @@ -22,9 +23,10 @@ - [ ] 各通道支持单独的限额 - [ ] 整个订单支持整体的限额 2.0.x 版本内容 +- [ ] 微信新增V3版本接口 + - [ ] 付款码支付自动路由到V2接口 - [ ] 统一关闭接口增加使用撤销关闭订单 - [ ] 增加各类日志记录,例如钱包的各项操作 -- [ ] 支付流程涉及异步支付时, 更换支付方式需要控制预防客户重复付款 - [ ] 增加撤销功能,用于处理线下支付订单的情况 - [ ] 数据库表进行规则, 字段设置长度, 增加索引 diff --git a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/payment/PayOrderTest.java b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/payment/PayOrderTest.java index 77de4fe86..45010967f 100644 --- a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/payment/PayOrderTest.java +++ b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/payment/PayOrderTest.java @@ -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 payChannels = Arrays.asList(cash, async); + param.setPayChannels(payChannels); + DaxPayResult execute = DaxPayKit.execute(param); + System.out.println(execute); + System.out.println(execute.getData()); + } } diff --git a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java index 30074236e..60fa19978 100644 --- a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java +++ b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/admin/controller/order/PayOrderController.java @@ -63,7 +63,7 @@ public class PayOrderController { @Operation(summary = "查询支付订单扩展信息") @GetMapping("/getExtraById") public ResResult getExtraById(Long id){ - return Res.ok(payOrderExtraService.findById(id)); + return Res.ok(payOrderExtraService.findById(id).toDto()); } @Operation(summary = "查询支付订单关联支付通道订单") diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/builder/PayBuilder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/builder/PayBuilder.java index 7af313de7..421ec8354 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/builder/PayBuilder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/builder/PayBuilder.java @@ -60,7 +60,6 @@ public class PayBuilder { .setExpiredTime(expiredTime) .setCombinationPay(payParam.getPayChannels().size() > 1) .setAsyncPay(asyncPay.isPresent()) - .setAsyncChannel(asyncPay.orElse(null)) .setRefundableBalance(sumAmount); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/dao/PayChannelOrderManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/dao/PayChannelOrderManager.java index 4cb8446bb..19cac6310 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/dao/PayChannelOrderManager.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/dao/PayChannelOrderManager.java @@ -35,6 +35,15 @@ public class PayChannelOrderManager extends BaseManager findByAsyncChannel(Long paymentId) { + return lambdaQuery() + .eq(PayChannelOrder::getPaymentId,paymentId) + .eq(PayChannelOrder::isAsync,true) + .oneOpt(); + } /** * 根据订单id删除异步支付记录 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/entity/PayChannelOrder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/entity/PayChannelOrder.java index 77e461229..fb1838f5b 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/entity/PayChannelOrder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/entity/PayChannelOrder.java @@ -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; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java index 7b0f80299..a596bb3a3 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayChannelOrderService.java @@ -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 payChannelOrders = payInfo.getPayChannelOrders(); - payChannelOrders.removeIf(o->Objects.equals(o.getChannel(),payChannelOrder.getChannel())); - payChannelOrders.add(payOrderChannelOpt.get()); - return payChannelOrder; } + // 对上下文中的通道支付订单进行替换 + List payChannelOrders = payInfo.getPayChannelOrders(); + payChannelOrders.removeIf(o->Objects.equals(o.getChannel(),payChannelOrder.getChannel())); + payChannelOrders.add(payChannelOrder); + // 更新支付订单中的异步通道信息 + payOrder.setAsyncChannel(payChannelOrder.getChannel()); + payOrderManager.updateById(payOrder); + return payChannelOrder; } /** diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderExtraService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderExtraService.java index f28d06b94..cd5f96f05 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderExtraService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/order/pay/service/PayOrderExtraService.java @@ -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); + } + } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/close/service/PayCloseService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/close/service/PayCloseService.java index b3b8870ea..175b2d9c6 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/close/service/PayCloseService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/close/service/PayCloseService.java @@ -134,7 +134,7 @@ public class PayCloseService { * 成功后处理方法 */ private void successHandler(PayOrder payOrder, List payChannelOrders){ - // 取消订单 + // 关闭订单 payOrder.setStatus(PayStatusEnum.CLOSE.getCode()) .setCloseTime(LocalDateTime.now()); payOrderService.updateById(payOrder); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/factory/PayStrategyFactory.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/factory/PayStrategyFactory.java index 2cef716ad..64f1e63f0 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/factory/PayStrategyFactory.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/factory/PayStrategyFactory.java @@ -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; } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayAssistService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayAssistService.java index c0733e909..5d46363a8 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayAssistService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayAssistService.java @@ -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); - } - /** * 校验支付状态,支付成功则返回,支付失败则抛出对应的异常 */ diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayService.java index 374d8b713..9c2ea3e5a 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/service/PayService.java @@ -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 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 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 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 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 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 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(){ - } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WeChatPayStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WeChatPayStrategy.java index b21939b56..b61de5044 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WeChatPayStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WeChatPayStrategy.java @@ -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);