From e6c7747351244e56a1f231df879843df73868115 Mon Sep 17 00:00:00 2001 From: nws <3239709711@qq.com> Date: Thu, 21 Dec 2023 23:08:04 +0800 Subject: [PATCH] =?UTF-8?q?ref=20=E8=BF=81=E7=A7=BB=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../platform/daxpay/param/pay/PayParam.java | 2 +- .../daxpay/param/pay/PayWayParam.java | 4 +- .../channel/AlipayConfigController.java | 56 ++++ .../openapi/controller/UniPayController.java | 4 +- .../alipay/service/AliPayOrderService.java | 2 +- .../channel/alipay/service/AliPayService.java | 12 +- .../alipay/service/AlipayConfigService.java | 42 +-- .../wechat/service/WeChatPayService.java | 4 +- .../wechat/service/WeChatPaymentService.java | 2 +- .../order/pay/builder/PaymentBuilder.java | 118 ++++---- .../pay/dao/PayExpiredTimeRepository.java | 52 ++++ .../order/pay/dao/PayOrderChannelManager.java | 29 ++ .../order/pay/dao/PayOrderChannelMapper.java | 14 + .../order/pay/dao/PayOrderExtraManager.java | 18 ++ .../order/pay/dao/PayOrderExtraMapper.java | 14 + .../core/order/pay/dao/PayOrderManager.java | 5 + .../core/order/pay/entity/PayOrder.java | 2 +- .../order/pay/entity/PayOrderChannel.java | 4 +- .../pay/entity/PayOrderRefundableInfo.java | 2 +- .../order/pay/service/PayOrderService.java | 104 ++++++++ .../pay/factory/PayStrategyFactory.java | 33 ++- .../core/payment/pay/service/PayService.java | 251 +++++++++++++++++- .../payment/pay/strategy/AliPayStrategy.java | 17 +- .../platform/daxpay/func/AbsPayStrategy.java | 4 +- .../channel/alipay/AlipayConfigParam.java | 12 +- .../platform/daxpay/util/PayWayUtil.java | 8 +- 26 files changed, 660 insertions(+), 155 deletions(-) create mode 100644 daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/openapi/controller/channel/AlipayConfigController.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayExpiredTimeRepository.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelManager.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelMapper.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraManager.java create mode 100644 daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraMapper.java diff --git a/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayParam.java b/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayParam.java index 3c201356..94929376 100644 --- a/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayParam.java +++ b/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayParam.java @@ -18,7 +18,7 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) @Data @Schema(title = "支付参数") -public class PayParam extends PayCommonParam{ +public class PayParam extends PayCommonParam { @Schema(description = "业务号") @NotBlank(message = "业务号不可为空") diff --git a/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayWayParam.java b/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayWayParam.java index 706b80b2..8013b3cd 100644 --- a/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayWayParam.java +++ b/daxpay-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayWayParam.java @@ -27,14 +27,14 @@ public class PayWayParam { */ @Schema(description = "支付渠道编码") @NotBlank(message = "支付渠道编码不可为空") - private String payChannel; + private String channel; /** * @see PayWayEnum#getCode() */ @Schema(description = "支付方式编码") @NotBlank(message = "支付方式编码不可为空") - private String payWay; + private String way; @Schema(description = "支付金额") @NotNull(message = "支付金额不可为空") diff --git a/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/openapi/controller/channel/AlipayConfigController.java b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/openapi/controller/channel/AlipayConfigController.java new file mode 100644 index 00000000..b0b2b330 --- /dev/null +++ b/daxpay-single/daxpay-single-admin/src/main/java/cn/bootx/platform/daxpay/openapi/controller/channel/AlipayConfigController.java @@ -0,0 +1,56 @@ +package cn.bootx.platform.daxpay.openapi.controller.channel; + +import cn.bootx.platform.common.core.rest.Res; +import cn.bootx.platform.common.core.rest.ResResult; +import cn.bootx.platform.common.core.rest.dto.LabelValue; +import cn.bootx.platform.daxpay.core.channel.alipay.service.AlipayConfigService; +import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto; +import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigParam; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import lombok.SneakyThrows; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * @author xxm + * @since 2021/2/26 + */ +@Tag(name = "支付宝配置") +@RestController +@RequestMapping("/alipay") +@AllArgsConstructor +public class AlipayConfigController { + + private final AlipayConfigService alipayConfigService; + + @Operation(summary = "更新") + @PostMapping("/update") + public ResResult update(@RequestBody AlipayConfigParam param) { + alipayConfigService.update(param); + return Res.ok(); + } + + @Operation(summary = "根据Id查询") + @GetMapping("/findById") + public ResResult findById() { + return Res.ok(alipayConfigService.getConfig().toDto()); + } + + @Operation(summary = "支付宝支持支付方式") + @GetMapping("/findPayWayList") + public ResResult> findPayWayList() { + return Res.ok(alipayConfigService.findPayWayList()); + } + + @SneakyThrows + @Operation(summary = "读取证书文件内容") + @PostMapping("/readPem") + public ResResult readPem(MultipartFile file){ + return Res.ok(new String(file.getBytes(), StandardCharsets.UTF_8)); + } +} diff --git a/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/openapi/controller/UniPayController.java b/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/openapi/controller/UniPayController.java index 4e2c2020..c192ff5b 100644 --- a/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/openapi/controller/UniPayController.java +++ b/daxpay-single/daxpay-single-gateway/src/main/java/cn/bootx/platform/daxpay/openapi/controller/UniPayController.java @@ -1,6 +1,7 @@ package cn.bootx.platform.daxpay.openapi.controller; import cn.bootx.platform.common.core.annotation.IgnoreAuth; +import cn.bootx.platform.daxpay.core.payment.pay.service.PayService; import cn.bootx.platform.daxpay.param.pay.*; import cn.bootx.platform.daxpay.result.DaxResult; import cn.bootx.platform.daxpay.result.pay.PayResult; @@ -25,10 +26,11 @@ import org.springframework.web.bind.annotation.RestController; @RequestMapping("/unipay") @RequiredArgsConstructor public class UniPayController { - + private final PayService payService; @Operation(summary = "统一下单") @PostMapping("/pay") public DaxResult pay(@RequestBody PayParam payParam){ + payService.pay(payParam); return DaxRes.ok(); } @Operation(summary = "简单下单") diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayOrderService.java index 8d37cf7b..eb804ae8 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayOrderService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayOrderService.java @@ -55,7 +55,7 @@ public class AliPayOrderService { refundableInfos.removeIf(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getChannel())); // 更新支付宝支付类型信息 payChannelInfo.add(new PayOrderChannel().setChannel(PayChannelEnum.ALI.getCode()) - .setPayWay(payWayParam.getPayWay()) + .setPayWay(payWayParam.getWay()) .setAmount(payWayParam.getAmount()) .setChannelExtra(payWayParam.getChannelExtra())); // TODO 更新支付关联信息 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayService.java index 355fe110..20e0ec0c 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AliPayService.java @@ -53,7 +53,7 @@ public class AliPayService { throw new PayFailureException("支付宝未配置可用的支付方式"); } // 发起的支付类型是否在支持的范围内 - PayWayEnum payWayEnum = Optional.ofNullable(AliPayWay.findByCode(payWayParam.getPayWay())) + PayWayEnum payWayEnum = Optional.ofNullable(AliPayWay.findByCode(payWayParam.getWay())) .orElseThrow(() -> new PayFailureException("非法的支付宝支付类型")); if (!alipayConfig.getPayWays().contains(payWayEnum.getCode())) { throw new PayFailureException("该支付宝支付方式不可用"); @@ -69,23 +69,23 @@ public class AliPayService { // 线程存储 AsyncPayInfo asyncPayInfo = Optional.ofNullable(AsyncPayInfoLocal.get()).orElse(new AsyncPayInfo()); // wap支付 - if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.WAP.getCode())) { + if (Objects.equals(payWayParam.getWay(), PayWayEnum.WAP.getCode())) { payBody = this.wapPay(amount, payOrder, alipayConfig, aliPayParam); } // 程序支付 - else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.APP.getCode())) { + else if (Objects.equals(payWayParam.getWay(), PayWayEnum.APP.getCode())) { payBody = this.appPay(amount, payOrder, alipayConfig); } // pc支付 - else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.WEB.getCode())) { + else if (Objects.equals(payWayParam.getWay(), PayWayEnum.WEB.getCode())) { payBody = this.webPay(amount, payOrder, alipayConfig, aliPayParam); } // 二维码支付 - else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.QRCODE.getCode())) { + else if (Objects.equals(payWayParam.getWay(), PayWayEnum.QRCODE.getCode())) { payBody = this.qrCodePay(amount, payOrder, alipayConfig); } // 付款码支付 - else if (Objects.equals(payWayParam.getPayWay(), PayWayEnum.BARCODE.getCode())) { + else if (Objects.equals(payWayParam.getWay(), PayWayEnum.BARCODE.getCode())) { String tradeNo = this.barCode(amount, payOrder, aliPayParam, alipayConfig); asyncPayInfo.setExpiredTime(false).setTradeNo(tradeNo); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipayConfigService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipayConfigService.java index 4b838890..1d3e3871 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipayConfigService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/alipay/service/AlipayConfigService.java @@ -2,16 +2,12 @@ package cn.bootx.platform.daxpay.core.channel.alipay.service; import cn.bootx.platform.common.core.exception.BizException; import cn.bootx.platform.common.core.exception.DataNotExistException; -import cn.bootx.platform.common.core.rest.PageResult; import cn.bootx.platform.common.core.rest.dto.LabelValue; -import cn.bootx.platform.common.core.rest.param.PageParam; import cn.bootx.platform.daxpay.code.AliPayCode; import cn.bootx.platform.daxpay.code.AliPayWay; import cn.bootx.platform.daxpay.core.channel.alipay.dao.AlipayConfigManager; import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig; -import cn.bootx.platform.daxpay.dto.channel.alipay.AlipayConfigDto; import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigParam; -import cn.bootx.platform.daxpay.param.channel.alipay.AlipayConfigQuery; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.util.CharsetUtil; @@ -37,42 +33,20 @@ import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class AlipayConfigService { - + /** 默认支付包配置的主键ID */ + private final static Long ID = 0L; private final AlipayConfigManager alipayConfigManager; - /** - * 添加支付宝配置 - */ - @Transactional(rollbackFor = Exception.class) - public void add(AlipayConfigParam param) { - AlipayConfig alipayConfig = AlipayConfig.init(param); - alipayConfigManager.save(alipayConfig); - } - /** * 修改 */ @Transactional(rollbackFor = Exception.class) public void update(AlipayConfigParam param) { - AlipayConfig alipayConfig = alipayConfigManager.findById(param.getId()).orElseThrow(DataNotExistException::new); + AlipayConfig alipayConfig = alipayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在")); BeanUtil.copyProperties(param, alipayConfig, CopyOptions.create().ignoreNullValue()); alipayConfigManager.updateById(alipayConfig); } - /** - * 获取 - */ - public AlipayConfigDto findById(Long id) { - return alipayConfigManager.findById(id).map(AlipayConfig::toDto).orElseThrow(DataNotExistException::new); - } - - /** - * 分页 - */ - public PageResult page(PageParam pageParam, AlipayConfigQuery param) { - return null; - } - /** * 支付宝支持支付方式 */ @@ -84,18 +58,18 @@ public class AlipayConfigService { } /** - * 初始化IJPay服务,通过商户应用AppCode获取支付配置 + * 获取支付配置 */ - public void initApiConfigByMchAppCode(String mchAppCode){ - AlipayConfig alipayConfig = null; - this.initApiConfig(alipayConfig); + public AlipayConfig getConfig(){ + return alipayConfigManager.findById(ID).orElseThrow(() -> new DataNotExistException("支付宝配置不存在")); } + /** * 初始化IJPay服务 */ @SneakyThrows - public void initApiConfig(AlipayConfig alipayConfig) { + public void initConfig(AlipayConfig alipayConfig) { AliPayApiConfig aliPayApiConfig; // 公钥 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java index b5322f1e..4070dbc1 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPayService.java @@ -68,7 +68,7 @@ public class WeChatPayService { throw new PayFailureException("未配置微信支付方式"); } - PayWayEnum payWayEnum = Optional.ofNullable(WeChatPayWay.findByCode(payWayParam.getPayWay())) + PayWayEnum payWayEnum = Optional.ofNullable(WeChatPayWay.findByCode(payWayParam.getWay())) .orElseThrow(() -> new PayFailureException("非法的微信支付类型")); if (!payWays.contains(payWayEnum.getCode())) { throw new PayFailureException("该微信支付方式不可用"); @@ -84,7 +84,7 @@ public class WeChatPayService { String totalFee = String.valueOf(amount); AsyncPayInfo asyncPayInfo = Optional.ofNullable(AsyncPayInfoLocal.get()).orElse(new AsyncPayInfo()); String payBody = null; - PayWayEnum payWayEnum = PayWayEnum.findByCode(payWayParam.getPayWay()); + PayWayEnum payWayEnum = PayWayEnum.findByCode(payWayParam.getWay()); // wap支付 if (payWayEnum == PayWayEnum.WAP) { diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java index 43e96dab..db721d65 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/channel/wechat/service/WeChatPaymentService.java @@ -51,7 +51,7 @@ public class WeChatPaymentService { refundableInfos.removeIf(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getChannel())); // 添加微信支付类型信息 payTypeInfos.add(new PayOrderChannel().setChannel(PayChannelEnum.WECHAT.getCode()) - .setPayWay(payWayParam.getPayWay()) + .setPayWay(payWayParam.getWay()) .setAmount(payWayParam.getAmount()) .setChannelExtra(payWayParam.getChannelExtra())); // TODO 更新支付方式列表 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/builder/PaymentBuilder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/builder/PaymentBuilder.java index a2685224..e461cf2d 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/builder/PaymentBuilder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/builder/PaymentBuilder.java @@ -1,21 +1,22 @@ package cn.bootx.platform.daxpay.core.order.pay.builder; -import cn.bootx.platform.common.spring.util.WebServletUtil; +import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel; import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderRefundableInfo; +import cn.bootx.platform.daxpay.core.payment.pay.local.AsyncPayInfo; +import cn.bootx.platform.daxpay.core.payment.pay.local.AsyncPayInfoLocal; import cn.bootx.platform.daxpay.param.pay.PayParam; import cn.bootx.platform.daxpay.param.pay.PayWayParam; import cn.bootx.platform.daxpay.result.pay.PayResult; import cn.hutool.core.collection.CollectionUtil; -import cn.hutool.extra.servlet.ServletUtil; import lombok.experimental.UtilityClass; -import javax.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -31,45 +32,45 @@ public class PaymentBuilder { * 构建payment记录 */ public PayOrder buildPayOrder(PayParam payParam) { - PayOrder payOrder = new PayOrder(); - - HttpServletRequest request = WebServletUtil.getRequest(); - String ip = ServletUtil.getClientIP(request); - // 基础信息 - payOrder.setBusinessNo(payParam.getBusinessNo()) - .setTitle(payParam.getTitle()); - - // 支付方式 + // 可退款信息 List refundableInfos = buildRefundableInfo(payParam.getPayWays()); - List payOrderChannels = buildPayChannel(payParam.getPayWays()); // 计算总价 - int sumAmount = payOrderChannels.stream() - .map(PayOrderChannel::getAmount) + int sumAmount = payParam.getPayWays().stream() + .map(PayWayParam::getAmount) .filter(Objects::nonNull) .reduce(Integer::sum) .orElse(0); - // 支付渠道信息 - payOrder.setRefundableInfos(refundableInfos) + // 是否有异步支付方式 + Optional asyncPayMode = payParam.getPayWays().stream() + .map(PayWayParam::getChannel) + .filter(PayChannelEnum.ASYNC_TYPE_CODE::contains) + .findFirst(); + // 构建支付订单对象 + return new PayOrder() + .setBusinessNo(payParam.getBusinessNo()) + .setTitle(payParam.getTitle()) + .setRefundableInfos(refundableInfos) .setStatus(PayStatusEnum.PROGRESS.getCode()) .setAmount(sumAmount) - .setCombinationPayMode(payParam.getPayWays().size()>1) + .setCombinationPayMode(payParam.getPayWays().size() > 1) + .setAsyncPayMode(asyncPayMode.isPresent()) + .setAsyncPayChannel(asyncPayMode.orElse(null)) .setRefundableBalance(sumAmount); - return payOrder; } /** * 构建订单关联通道信息 */ - private List buildPayChannel(List payWayParamList) { - if (CollectionUtil.isEmpty(payWayParamList)) { + public List buildPayChannel(List payWayParams) { + if (CollectionUtil.isEmpty(payWayParams)) { return Collections.emptyList(); } - return payWayParamList.stream() + return payWayParams.stream() .map(o-> new PayOrderChannel() - .setChannel(o.getPayChannel()) - .setPayWay(o.getPayWay()) - .setAmount(o.getAmount()) - .setChannelExtra(o.getChannelExtra())) + .setChannel(o.getChannel()) + .setPayWay(o.getWay()) + .setAmount(o.getAmount()) + .setChannelExtra(o.getChannelExtra())) .collect(Collectors.toList()); } @@ -82,41 +83,50 @@ public class PaymentBuilder { } return payWayParamList.stream() .map(o-> new PayOrderRefundableInfo() - .setChannel(o.getPayChannel()) + .setChannel(o.getChannel()) .setAmount(o.getAmount())) .collect(Collectors.toList()); } /** - * 根据Payment构建PaymentResult - * @param payment payment - * @return paymentVO + * 根据支付订单构建支付结果 + * @param payOrder 支付订单 + * @return PayResult 支付结果 */ - public PayResult buildResultByPayment(PayOrder payment) { -// PayResult paymentResult; -// try { -// paymentResult = new PayResult(); -// // 异步支付信息 -// paymentResult.setAsyncPayChannel(payment.getAsyncPayChannel()) -// .setAsyncPayMode(payment.isAsyncPayMode()) -// .setStatus(payment.getStatus()); -// -// -// // 设置异步支付参数 -// List moneyPayTypeInfos = channelInfos.stream() -// .filter(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getPayChannel())) -// .collect(Collectors.toList()); -// if (!CollUtil.isEmpty(moneyPayTypeInfos)) { -// paymentResult.setAsyncPayInfo(AsyncPayInfoLocal.get()); -// } -// } -// finally { -// // 清空线程变量 -// AsyncPayInfoLocal.clear(); -// } -// return paymentResult; - return null; + public PayResult buildPayResultByPayOrder(PayOrder payOrder) { + PayResult paymentResult; + try { + paymentResult = new PayResult() + .setPaymentId(payOrder.getId()) + .setAsyncPayMode(payOrder.isAsyncPayMode()) + .setAsyncPayChannel(payOrder.getAsyncPayChannel()) + .setStatus(payOrder.getStatus()); + + // 设置异步支付参数 + AsyncPayInfo asyncPayInfo = AsyncPayInfoLocal.get(); + if (Objects.nonNull(asyncPayInfo)) { + paymentResult.setPayBody(AsyncPayInfoLocal.get().getPayBody()); + } + } + finally { + // 清空线程变量 + AsyncPayInfoLocal.clear(); + } + return paymentResult; } + /** + * 根据新的新传入的支付参数和支付订单构建出当前需要支付参数 + * 主要是针对其中的异步支付参数进行处理 + * + * @param newPayParam 新传入的参数 + * @param payOrder 支付订单 + * @return PayParam 支付参数 + */ + public PayParam buildPayParam(PayParam newPayParam, PayOrder payOrder) { + + + return null; + } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayExpiredTimeRepository.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayExpiredTimeRepository.java new file mode 100644 index 00000000..15a98249 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayExpiredTimeRepository.java @@ -0,0 +1,52 @@ +package cn.bootx.platform.daxpay.core.order.pay.dao; + +import cn.bootx.platform.common.core.util.LocalDateTimeUtil; +import cn.bootx.platform.common.redis.RedisClient; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.Set; + +/** + * 支付单过期处理 + * + * @author xxm + * @since 2022/7/12 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class PayExpiredTimeRepository { + + private static final String KEY = "payment:pay:expiredtime"; + + private final RedisClient redisClient; + + /** + * 根据 token 存储对应的 ExpiredTokenKey + */ + public void store(Long paymentId, LocalDateTime expiredTime) { + long time = LocalDateTimeUtil.timestamp(expiredTime); + redisClient.zadd(KEY, String.valueOf(paymentId), time); + } + + /** + * 获取所有已过期的ExpiredTokenKey + */ + public Set retrieveExpiredKeys(LocalDateTime expiredTime) { + long time = LocalDateTimeUtil.timestamp(expiredTime); + return redisClient.zrangeByScore(KEY, 0L, time); + } + + /** + * 删除指定的ExpiredTokenKey + */ + public void removeKeys(String... keys) { + if (keys != null && keys.length > 0) { + redisClient.zremByMembers(KEY, keys); + } + } + +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelManager.java new file mode 100644 index 00000000..c3c04cfe --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelManager.java @@ -0,0 +1,29 @@ +package cn.bootx.platform.daxpay.core.order.pay.dao; + +import cn.bootx.platform.common.mybatisplus.impl.BaseManager; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +/** + * 支付订单关联支付时通道信息 + * @author xxm + * @since 2023/12/20 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class PayOrderChannelManager extends BaseManager { + /** + * 根据订单id和支付渠道查询 + */ + public Optional findByOderIdAndChannel(Long paymentId, String channel) { + return lambdaQuery() + .eq(PayOrderChannel::getPaymentId,paymentId) + .eq(PayOrderChannel::getChannel,channel) + .oneOpt(); + } +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelMapper.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelMapper.java new file mode 100644 index 00000000..8787e01c --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderChannelMapper.java @@ -0,0 +1,14 @@ +package cn.bootx.platform.daxpay.core.order.pay.dao; + +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 支付订单关联支付时通道信息 + * @author xxm + * @since 2023/12/20 + */ +@Mapper +public interface PayOrderChannelMapper extends BaseMapper { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraManager.java new file mode 100644 index 00000000..683941c8 --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraManager.java @@ -0,0 +1,18 @@ +package cn.bootx.platform.daxpay.core.order.pay.dao; + +import cn.bootx.platform.common.mybatisplus.impl.BaseManager; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderExtra; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Repository; + +/** + * 支付订单扩展信息 + * @author xxm + * @since 2023/12/20 + */ +@Slf4j +@Repository +@RequiredArgsConstructor +public class PayOrderExtraManager extends BaseManager { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraMapper.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraMapper.java new file mode 100644 index 00000000..c8665fce --- /dev/null +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderExtraMapper.java @@ -0,0 +1,14 @@ +package cn.bootx.platform.daxpay.core.order.pay.dao; + +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderExtra; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +/** + * 支付订单扩展信息 + * @author xxm + * @since 2023/12/20 + */ +@Mapper +public interface PayOrderExtraMapper extends BaseMapper { +} diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderManager.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderManager.java index 537c96b1..7fa36b39 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderManager.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/dao/PayOrderManager.java @@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; +import java.util.Optional; + /** * 支付订单 * @author xxm @@ -15,4 +17,7 @@ import org.springframework.stereotype.Repository; @Repository @RequiredArgsConstructor public class PayOrderManager extends BaseManager { + public Optional findByBusinessNo(String businessNo) { + return findByField(PayOrder::getBusinessNo,businessNo); + } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrder.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrder.java index 816a2255..c166a1c5 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrder.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrder.java @@ -32,7 +32,7 @@ public class PayOrder extends MpBaseEntity { /** 关联的业务id */ @DbMySqlIndex(comment = "业务业务号索引",type = MySqlIndexType.UNIQUE) - @DbColumn(comment = "关联的业务id") + @DbColumn(comment = "关联的业务号") private String businessNo; /** 标题 */ diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderChannel.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderChannel.java index d0788235..6b38d41a 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderChannel.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderChannel.java @@ -1,6 +1,6 @@ package cn.bootx.platform.daxpay.core.order.pay.entity; -import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity; +import cn.bootx.platform.common.mybatisplus.base.MpDelEntity; import cn.bootx.platform.daxpay.param.channel.AliPayParam; import cn.bootx.platform.daxpay.param.channel.VoucherPayParam; import cn.bootx.platform.daxpay.param.channel.WalletPayParam; @@ -22,7 +22,7 @@ import lombok.experimental.Accessors; @Accessors(chain = true) @DbTable(comment = "支付订单关联支付时通道信息") @TableName("pay_order_channel") -public class PayOrderChannel extends MpBaseEntity { +public class PayOrderChannel extends MpDelEntity { @DbColumn(comment = "支付id") private Long paymentId; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderRefundableInfo.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderRefundableInfo.java index 9e1f09ce..c9ca8bff 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderRefundableInfo.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/entity/PayOrderRefundableInfo.java @@ -5,7 +5,7 @@ import lombok.Data; import lombok.experimental.Accessors; /** - * 支付订单可退款信息 + * 支付订单可退款信息(不持久化,直接保存为json) * @author xxm * @since 2023/12/18 */ diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/service/PayOrderService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/service/PayOrderService.java index b6c15d7c..4fa9c715 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/service/PayOrderService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/order/pay/service/PayOrderService.java @@ -1,9 +1,30 @@ package cn.bootx.platform.daxpay.core.order.pay.service; +import cn.bootx.platform.common.core.util.LocalDateTimeUtil; +import cn.bootx.platform.common.spring.exception.RetryableException; +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.code.PayStatusEnum; +import cn.bootx.platform.daxpay.core.order.pay.dao.PayExpiredTimeRepository; +import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderChannelManager; +import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderExtraManager; +import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderManager; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderRefundableInfo; +import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.retry.annotation.Retryable; +import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + /** * 支付订单服务 * @author xxm @@ -13,4 +34,87 @@ import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class PayOrderService { + + private final PayOrderManager orderManager; + + private final PayOrderChannelManager orderChannelManager; + + private final PayOrderExtraManager orderExtraManager; + + private final PayExpiredTimeRepository expiredTimeRepository; + + /** + * 保存 + */ + public PayOrder saveOder(PayOrder payment) { + return orderManager.save(payment); + } + + /** + * 保存支付通道信息列表 + */ + public void saveOrderChannels(List payOrderChannels){ + orderChannelManager.saveAll(payOrderChannels); + } + + /** + * 更新支付记录 + */ + public PayOrder updateById(PayOrder payment) { + // 超时注册 + this.registerExpiredTime(payment); + return orderManager.updateById(payment); + } + + + /** + * 根据id查询 + */ + public Optional findById(Serializable id) { + return orderManager.findById(id); + } + + /** + * 根据BusinessId查询 + */ + public Optional findByBusinessId(String businessNo) { + return orderManager.findByBusinessNo(businessNo); + } + + /** + * 退款成功处理, 更新可退款信息 不要进行持久化 + */ + public void updateRefundSuccess(PayOrder payment, int amount, PayChannelEnum payChannelEnum) { + // 删除旧有的退款记录, 替换退款完的新的 + List refundableInfos = payment.getRefundableInfos(); + PayOrderRefundableInfo refundableInfo = refundableInfos.stream() + .filter(o -> Objects.equals(o.getChannel(), payChannelEnum.getCode())) + .findFirst() + .orElseThrow(() -> new PayFailureException("退款数据不存在")); + refundableInfos.remove(refundableInfo); + refundableInfo.setAmount(refundableInfo.getAmount() - amount); + refundableInfos.add(refundableInfo); + payment.setRefundableInfos(refundableInfos); + } + + /** + * 支付单超时关闭事件注册, 失败重试3次, 间隔一秒 + */ + @Async("bigExecutor") + @Retryable(value = RetryableException.class) + public void registerExpiredTime(PayOrder payOrder) { + LocalDateTime expiredTime = payOrder.getExpiredTime(); + // 支付中且有超时时间才会注册超时关闭时间 + if (Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode()) && Objects.nonNull(expiredTime)) { + try { + // 将过期时间添加到redis中, 往后延时一分钟 + expiredTime = LocalDateTimeUtil.offset(expiredTime, 1, ChronoUnit.MINUTES); + expiredTimeRepository.store(payOrder.getId(), expiredTime); + } + catch (Exception e) { + log.error("注册支付单超时关闭失败"); + throw new RetryableException(); + } + } + } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/factory/PayStrategyFactory.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/factory/PayStrategyFactory.java index 11e259b2..2f71e4f5 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/factory/PayStrategyFactory.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/factory/PayStrategyFactory.java @@ -7,6 +7,7 @@ import cn.bootx.platform.daxpay.func.AbsPayStrategy; import cn.bootx.platform.daxpay.param.pay.PayWayParam; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.extra.spring.SpringUtil; +import lombok.experimental.UtilityClass; import java.util.ArrayList; import java.util.Collections; @@ -23,6 +24,7 @@ import static cn.bootx.platform.daxpay.code.PayChannelEnum.ASYNC_TYPE_CODE; * @author xxm * @since 2020/12/11 */ +@UtilityClass public class PayStrategyFactory { /** @@ -30,10 +32,9 @@ public class PayStrategyFactory { * @param payWayParam 支付类型 * @return 支付策略 */ - public static AbsPayStrategy create(PayWayParam payWayParam) { - - AbsPayStrategy strategy = null; - PayChannelEnum channelEnum = PayChannelEnum.findByCode(payWayParam.getPayChannel()); + public AbsPayStrategy create(PayWayParam payWayParam) { + AbsPayStrategy strategy; + PayChannelEnum channelEnum = PayChannelEnum.findByCode(payWayParam.getChannel()); switch (channelEnum) { case ALI: strategy = SpringUtil.getBean(AliPayStrategy.class); @@ -56,7 +57,6 @@ public class PayStrategyFactory { default: throw new PayUnsupportedMethodException(); } - // noinspection ConstantConditions strategy.setPayWayParam(payWayParam); return strategy; } @@ -64,15 +64,15 @@ public class PayStrategyFactory { /** * 根据传入的支付类型批量创建策略, 异步支付在后面 */ - public static List createDesc(List payWayParamList) { + public static List createAsyncLast(List payWayParamList) { return create(payWayParamList, true); } /** - * 根据传入的支付类型批量创建策略, 默认异步支付在前面 + * 根据传入的支付类型批量创建策略, 异步支付在前面 */ - public static List create(List payWayParamList) { - return create(payWayParamList, false); + public List create(List payWayParamList) { + return create(payWayParamList, true); } /** @@ -80,7 +80,7 @@ public class PayStrategyFactory { * @param payWayParamList 支付类型 * @return 支付策略 */ - private static List create(List payWayParamList, boolean description) { + private List create(List payWayParamList, boolean asyncFirst) { if (CollectionUtil.isEmpty(payWayParamList)) { return Collections.emptyList(); } @@ -89,25 +89,24 @@ public class PayStrategyFactory { // 同步支付 List syncPayWayParamList = payWayParamList.stream() .filter(Objects::nonNull) - .filter(payModeParam -> !ASYNC_TYPE_CODE.contains(payModeParam.getPayChannel())) + .filter(payModeParam -> !ASYNC_TYPE_CODE.contains(payModeParam.getChannel())) .collect(Collectors.toList()); // 异步支付 List asyncPayWayParamList = payWayParamList.stream() .filter(Objects::nonNull) - .filter(payModeParam -> ASYNC_TYPE_CODE.contains(payModeParam.getPayChannel())) + .filter(payModeParam -> ASYNC_TYPE_CODE.contains(payModeParam.getChannel())) .collect(Collectors.toList()); List sortList = new ArrayList<>(payWayParamList.size()); // 异步在后面 - if (description) { - sortList.addAll(syncPayWayParamList); - sortList.addAll(asyncPayWayParamList); - } - else { + if (asyncFirst) { sortList.addAll(asyncPayWayParamList); sortList.addAll(syncPayWayParamList); + } else { + sortList.addAll(syncPayWayParamList); + sortList.addAll(asyncPayWayParamList); } // 此处有一个根据Type的反转排序, diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/service/PayService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/service/PayService.java index 5c9c7ea5..ea92ef65 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/service/PayService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/service/PayService.java @@ -1,11 +1,40 @@ package cn.bootx.platform.daxpay.core.payment.pay.service; +import cn.bootx.platform.common.core.util.LocalDateTimeUtil; +import cn.bootx.platform.common.core.util.ValidationUtil; +import cn.bootx.platform.daxpay.code.PayChannelEnum; +import cn.bootx.platform.daxpay.code.PayStatusEnum; +import cn.bootx.platform.daxpay.core.order.pay.builder.PaymentBuilder; +import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderChannelManager; +import cn.bootx.platform.daxpay.core.order.pay.dao.PayOrderExtraManager; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrder; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderChannel; +import cn.bootx.platform.daxpay.core.order.pay.entity.PayOrderExtra; +import cn.bootx.platform.daxpay.core.order.pay.service.PayOrderService; +import cn.bootx.platform.daxpay.core.payment.pay.factory.PayStrategyFactory; +import cn.bootx.platform.daxpay.exception.pay.PayFailureException; +import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException; +import cn.bootx.platform.daxpay.func.AbsPayStrategy; +import cn.bootx.platform.daxpay.func.PayStrategyConsumer; +import cn.bootx.platform.daxpay.param.pay.PayParam; +import cn.bootx.platform.daxpay.param.pay.PayWayParam; +import cn.bootx.platform.daxpay.result.pay.PayResult; +import cn.bootx.platform.daxpay.util.PayWayUtil; +import cn.hutool.core.collection.CollectionUtil; 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.*; +import java.util.function.Consumer; +import java.util.stream.Collectors; + /** - * 支付流程服务 + * 支付流程 + * * @author xxm * @since 2020/12/9 */ @@ -13,4 +42,224 @@ import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor public class PayService { + + private final PayOrderService payOrderService; + + private final PayOrderExtraManager payOrderExtraManager; + + private final PayOrderChannelManager payOrderChannelManager; + + + /** + * 支付方法(同步/异步/组合支付) 同步支付:都只会在第一次执行中就完成支付,例如钱包、积分都是调用完就进行了扣减,完成了支付记录 + * 异步支付:例如支付宝、微信,发起支付后还需要跳转第三方平台进行支付,支付后通常需要进行回调,之后才完成支付记录 + * 组合支付:主要是混合了同步支付和异步支付,同时异步支付只能有一个,在支付时先对同步支付进行扣减,然后异步支付回调结束后完成整个支付单 + * 组合支付在非第一次支付的时候,只对新传入的异步支付PayMode进行处理,PayMode的价格使用第一次发起的价格,旧的同步支付如果传入后也不做处理, + * Payment中PayModeList将会为 旧有的同步支付+新传入的异步支付方式(在具体支付实现中处理) + */ + @Transactional(rollbackFor = Exception.class) + public PayResult pay(PayParam payParam) { + // 检验参数 + ValidationUtil.validateParam(payParam); + // 异步支付方式检查 + PayWayUtil.validationAsyncPayMode(payParam); + // 获取并校验支付状态 + PayOrder payOrder = this.getAndCheckByBusinessId(payParam.getBusinessNo()); + + // 异步支付且非第一次支付 + if (Objects.nonNull(payOrder) && payOrder.isAsyncPayMode()) { + return this.paySyncNotFirst(payParam, payOrder); + } + else { + // 第一次发起支付或同步支付 + return this.payFirst(payParam, payOrder); + } + } + + /** + * 发起的第一次支付请求(同步/异步) + */ + private PayResult payFirst(PayParam payParam, PayOrder payOrder) { + // 0. 已经发起过支付情况直接返回支付结果 + if (Objects.nonNull(payOrder)) { + return PaymentBuilder.buildPayResultByPayOrder(payOrder); + } + + // 1. 价格检测 + PayWayUtil.validationAmount(payParam.getPayWays()); + + // 2. 创建支付相关的记录并返回支付订单对象 + payOrder = this.createPayOrder(payParam); + + // 3. 调用支付方法进行发起支付 + this.payFirstMethod(payParam, payOrder); + + // 4. 获取支付记录信息 +// payOrder = payOrderService.findById(payOrder.getId()).orElseThrow(PayNotExistedException::new); + + // 5. 返回支付结果 + return PaymentBuilder.buildPayResultByPayOrder(payOrder); + } + + /** + * 执行支付方法 (第一次支付) + * @param payParam 支付参数 + * @param payOrder 支付订单 + */ + private void payFirstMethod(PayParam payParam, PayOrder payOrder) { + + // 1.获取支付方式,通过工厂生成对应的策略组 + List paymentStrategyList = PayStrategyFactory.create(payParam.getPayWays()); + if (CollectionUtil.isEmpty(paymentStrategyList)) { + throw new PayUnsupportedMethodException(); + } + + // 2.初始化支付的参数 + for (AbsPayStrategy paymentStrategy : paymentStrategyList) { + paymentStrategy.initPayParam(payOrder, payParam); + } + + // 3.支付前准备, 执行支付前处理 + this.doHandler(payOrder, paymentStrategyList, AbsPayStrategy::doBeforePayHandler, null); + + // 4.支付操作 + this.doHandler(payOrder, paymentStrategyList, AbsPayStrategy::doPayHandler, (strategies, payOrderObj) -> { + // 发起支付成功进行的执行方法 + strategies.forEach(AbsPayStrategy::doSuccessHandler); + // 所有支付方式都是同步时, 对支付订单进行处理 + if (PayWayUtil.isNotSync(payParam.getPayWays())) { + // 修改支付订单状态为成功 + payOrderObj.setStatus(PayStatusEnum.SUCCESS.getCode()); + payOrderObj.setPayTime(LocalDateTime.now()); + } + payOrderService.updateById(payOrderObj); + }); + } + + /** + * 异步支付执行(非第一次请求), 只执行异步支付策略, 报错不影响继续发起支付 + */ + private PayResult paySyncNotFirst(PayParam payParam, PayOrder payOrder) { + + // 0. 处理支付完成情况(完成/退款) + List trades = Arrays.asList(PayStatusEnum.SUCCESS.getCode(), PayStatusEnum.CANCEL.getCode(), + PayStatusEnum.PARTIAL_REFUND.getCode(), PayStatusEnum.REFUNDED.getCode()); + if (trades.contains(payOrder.getStatus())) { + return PaymentBuilder.buildPayResultByPayOrder(payOrder); + } + + // 1.获取 异步支付 通道道,通过工厂生成对应的策略组(只包含异步支付的策略, 同步支付不再进行执行) + PayWayParam payWayParam = this.getAsyncPayParam(payParam, payOrder); + List asyncStrategyList = PayStrategyFactory.create(Collections.singletonList(payWayParam)); + + // 2.初始化支付的参数 + for (AbsPayStrategy paymentStrategy : asyncStrategyList) { + paymentStrategy.initPayParam(payOrder, payParam); + } + // 3.支付前准备 + this.doHandler(payOrder, asyncStrategyList, AbsPayStrategy::doBeforePayHandler, null); + + // 4. 发起支付 + this.doHandler(payOrder, asyncStrategyList, AbsPayStrategy::doPayHandler, (strategyList, paymentObj) -> { + // 发起支付成功进行的执行方法 + strategyList.forEach(AbsPayStrategy::doSuccessHandler); + payOrderService.updateById(paymentObj); + }); + + // 5. 获取支付记录信息 +// payOrder = payOrderService.findById(payOrder.getId()).orElseThrow(PayNotExistedException::new); + + // 6. 组装返回参数 + return PaymentBuilder.buildPayResultByPayOrder(payOrder); + } + + /** + * 执行策略中不同的handler + * @param payment 主支付对象 + * @param strategyList 策略列表 + * @param payMethod 执行支付的函数或者支付前的函数 + * @param successMethod 执行成功的函数 + */ + private void doHandler(PayOrder payment, List strategyList, Consumer payMethod, + PayStrategyConsumer, PayOrder> successMethod) { + // 执行策略操作,如支付前/支付时 + // 等同strategyList.forEach(payMethod.accept(PaymentStrategy)) + strategyList.forEach(payMethod); + + // 执行操作成功的处理 + Optional.ofNullable(successMethod).ifPresent(fun -> fun.accept(strategyList, payment)); + } + + /** + * 获取异步支付参数 + */ + private PayWayParam getAsyncPayParam(PayParam payParam, PayOrder payOrder) { + // 查询之前的支付方式 + String asyncPayChannel = payOrder.getAsyncPayChannel(); + PayOrderChannel payOrderChannel = payOrderChannelManager.findByOderIdAndChannel(payOrder.getId(), asyncPayChannel) + .orElseThrow(() -> new PayFailureException("支付方式数据异常")); + + // 新的异步支付方式 + PayWayParam payWayParam = payParam.getPayWays() + .stream() + .filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel())) + .findFirst() + .orElseThrow(() -> new PayFailureException("支付方式数据异常")); + // 新传入的金额是否一致 + if (!Objects.equals(payOrderChannel.getAmount(), payWayParam.getAmount())){ + throw new PayFailureException("传入的支付金额非法!与订单金额不一致"); + } + return payWayParam; + } + + /** + * 创建支付订单/附加表/支付通道表并保存,返回支付订单 + */ + private PayOrder createPayOrder(PayParam payParam) { + // 构建支付订单并保存 + PayOrder payOrder = PaymentBuilder.buildPayOrder(payParam); + payOrderService.saveOder(payOrder); + // 构建支付订单扩展表并保存 + PayOrderExtra payOrderExtra = new PayOrderExtra() + .setClientIp(payParam.getClientIp()) + .setDescription(payParam.getDescription()); + payOrderExtra.setId(payOrder.getId()); + payOrderExtraManager.save(payOrderExtra); + // 构建支付通道表并保存 + List payOrderChannels = PaymentBuilder.buildPayChannel(payParam.getPayWays()) + .stream() + .peek(o -> o.setPaymentId(payOrder.getId())) + .collect(Collectors.toList()); + payOrderChannelManager.saveAll(payOrderChannels); + return payOrder; + } + + /** + * 校验支付状态,支付成功则返回,支付失败则抛出对应的异常 + */ + private PayOrder getAndCheckByBusinessId(String businessId) { + // 根据订单查询支付记录 + PayOrder payment = payOrderService.findByBusinessId(businessId).orElse(null); + if (Objects.nonNull(payment)) { + // 支付失败类型状态 + List tradesStatus = Arrays.asList(PayStatusEnum.FAIL.getCode(), PayStatusEnum.CANCEL.getCode(), + PayStatusEnum.CLOSE.getCode()); + if (tradesStatus.contains(payment.getStatus())) { + throw new PayFailureException("支付失败或已经被撤销"); + } + // 退款类型状态 + tradesStatus = Arrays.asList(PayStatusEnum.REFUNDED.getCode(), PayStatusEnum.PARTIAL_REFUND.getCode()); + if (tradesStatus.contains(payment.getStatus())) { + throw new PayFailureException("支付失败或已经被撤销"); + } + // 支付超时状态 + if (Objects.nonNull(payment.getExpiredTime()) + && LocalDateTimeUtil.ge(LocalDateTime.now(), payment.getExpiredTime())) { + throw new PayFailureException("支付已超时"); + } + return payment; + } + return null; + } + } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/strategy/AliPayStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/strategy/AliPayStrategy.java index adb2955e..a06b0878 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/strategy/AliPayStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/core/payment/pay/strategy/AliPayStrategy.java @@ -3,7 +3,6 @@ package cn.bootx.platform.daxpay.core.payment.pay.strategy; import cn.bootx.platform.daxpay.code.AliPayCode; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.common.exception.ExceptionInfo; -import cn.bootx.platform.daxpay.core.channel.alipay.dao.AlipayConfigManager; import cn.bootx.platform.daxpay.core.channel.alipay.entity.AlipayConfig; import cn.bootx.platform.daxpay.core.channel.alipay.service.*; import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException; @@ -33,12 +32,8 @@ import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROT @RequiredArgsConstructor public class AliPayStrategy extends AbsPayStrategy { - private final AlipayConfigManager alipayConfigManager; - private final AliPayOrderService aliPaymentService; - private final AlipaySyncService alipaySyncService; - private final AliPayService aliPayService; private final AlipayConfigService alipayConfigService; @@ -80,12 +75,6 @@ public class AliPayStrategy extends AbsPayStrategy { // 检查并获取支付宝支付配置 this.initAlipayConfig(); aliPayService.validation(this.getPayWayParam(), alipayConfig); - - // 如果没有显式传入同步回调地址, 使用默认配置 -// if (StrUtil.isBlank(aliPayParam.getReturnUrl())) { -// aliPayParam.setReturnUrl(alipayConfig.getReturnUrl()); -// } - this.initAlipayConfig(); } /** @@ -156,9 +145,9 @@ public class AliPayStrategy extends AbsPayStrategy { * 初始化支付宝配置信息 */ private void initAlipayConfig() { - // 检查并获取支付宝支付配置 - this.alipayConfig = null; - alipayConfigService.initApiConfig(this.alipayConfig); + // 获取并初始化支付宝支付配置 + this.alipayConfig = alipayConfigService.getConfig(); + alipayConfigService.initConfig(this.alipayConfig); } } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/func/AbsPayStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/func/AbsPayStrategy.java index 740587d6..e1766c5a 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/func/AbsPayStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/func/AbsPayStrategy.java @@ -45,7 +45,7 @@ public abstract class AbsPayStrategy { } /** - * 支付前对处理 包含必要的校验以及对Payment对象的创建和保存操作 + * 支付前处理 包含必要的校验以及对当前通道支付订单的创建和保存操作 */ public void doBeforePayHandler() { } @@ -56,7 +56,7 @@ public abstract class AbsPayStrategy { public abstract void doPayHandler(); /** - * 支付成功的处理方式 + * 支付调起成功的处理方式 */ public void doSuccessHandler() { } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/param/channel/alipay/AlipayConfigParam.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/param/channel/alipay/AlipayConfigParam.java index 5867b142..d2a87e4d 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/param/channel/alipay/AlipayConfigParam.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/param/channel/alipay/AlipayConfigParam.java @@ -4,7 +4,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.experimental.Accessors; -import java.io.Serializable; import java.util.List; /** @@ -14,20 +13,11 @@ import java.util.List; @Data @Accessors(chain = true) @Schema(title = "支付宝配置参数") -public class AlipayConfigParam implements Serializable { - - @Schema(description = "主键") - private Long id; +public class AlipayConfigParam { @Schema(description = "名称") private String name; - @Schema(description = "商户编码") - private String mchCode; - - @Schema(description = "商户应用编码") - private String mchAppCode; - @Schema(description = "支付宝商户appId") private String appId; diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/util/PayWayUtil.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/util/PayWayUtil.java index 17705b3d..39242a0b 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/util/PayWayUtil.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/util/PayWayUtil.java @@ -57,7 +57,7 @@ public class PayWayUtil { */ public boolean isNotSync(List payWayParams) { return payWayParams.stream() - .map(PayWayParam::getPayChannel) + .map(PayWayParam::getChannel) .noneMatch(PayChannelEnum.ASYNC_TYPE_CODE::contains); } @@ -67,7 +67,7 @@ public class PayWayUtil { public PayWayParam getAsyncPayModeParam(PayParam payParam) { return payParam.getPayWays() .stream() - .filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getPayChannel())) + .filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel())) .findFirst() .orElseThrow(() -> new PayFailureException("支付方式数据异常")); } @@ -129,7 +129,7 @@ public class PayWayUtil { public void validationAmount(List payModeList) { for (PayWayParam payWayParam : payModeList) { // 支付金额小于等于零 - if (payWayParam.getAmount() < 1) { + if (payWayParam.getAmount() < 0) { throw new PayAmountAbnormalException(); } } @@ -143,7 +143,7 @@ public class PayWayUtil { List payModeList = payParam.getPayWays(); long asyncPayModeCount = payModeList.stream() - .map(PayWayParam::getPayChannel) + .map(PayWayParam::getChannel) .map(PayChannelEnum::findByCode) .filter(PayChannelEnum.ASYNC_TYPE::contains) .count();