From a1efe54da4f4af144603b6e2ca2b09192fcf2acb Mon Sep 17 00:00:00 2001 From: bootx Date: Fri, 9 Feb 2024 00:32:16 +0800 Subject: [PATCH] =?UTF-8?q?feat=20=E7=AD=BE=E5=90=8D=E7=AE=97=E6=B3=95?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=B5=8C=E5=A5=97=E5=8F=82=E6=95=B0=E4=BC=98?= =?UTF-8?q?=E5=8C=96,=20=E6=96=B0=E5=A2=9E=E6=94=AF=E4=BB=98=E6=BC=94?= =?UTF-8?q?=E7=A4=BA=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- _doc/ChangeLog.md | 35 ++--- _doc/Task.md | 9 +- daxpay-single-demo/pom.xml | 43 ++++++ .../daxpay/demo/DaxPaySingleDemoApp.java | 12 ++ .../demo/config/DaxPayDemoProperties.java | 28 ++++ .../demo/config/DaxPayKitConfiguration.java | 37 +++++ .../controller/DaxPayCashierController.java | 39 ++++++ .../demo/param/CashierSimplePayParam.java | 47 +++++++ .../daxpay/demo/result/PayOrderResult.java | 31 +++++ .../demo/service/DaxPayCashierService.java | 128 ++++++++++++++++++ ...ot.autoconfigure.AutoConfiguration.imports | 1 + daxpay-single-sdk/pom.xml | 11 ++ .../daxpay/sdk/model/pay/PayOrderModel.java | 2 +- .../platform/daxpay/sdk/net/DaxPayKit.java | 5 + .../daxpay/sdk/param/ChannelParam.java | 2 +- .../daxpay/sdk/param/SortMapParam.java | 9 ++ ...yPayParam.java => QueryPayOrderParam.java} | 2 +- .../daxpay/sdk/param/pay/SimplePayParam.java | 7 + ...dParam.java => QueryRefundOrderParam.java} | 2 +- .../platform/daxpay/sdk/util/PaySignUtil.java | 10 +- .../daxpay/sdk/query/QueryPayOrderTest.java | 4 +- .../sdk/query/QueryRefundOrderTest.java | 4 +- .../daxpay/sdk/util/PayParamSignTest.java | 38 +++++- daxpay-single/daxpay-single-admin/pom.xml | 3 +- .../daxpay/param/pay/PayChannelParam.java | 3 +- .../daxpay/param/pay/SimplePayParam.java | 3 +- .../platform/daxpay/util/PaySignUtil.java | 8 +- daxpay-single/daxpay-single-gateway/pom.xml | 2 +- daxpay-single/daxpay-single-service/pom.xml | 3 +- .../voucher/service/VoucherPayService.java | 18 ++- .../core/order/pay/builder/PayBuilder.java | 37 ++--- .../order/pay/entity/PayChannelOrder.java | 3 + .../pay/service/PayChannelOrderService.java | 12 +- .../payment/pay/strategy/AliPayStrategy.java | 12 +- .../pay/strategy/WalletPayStrategy.java | 21 +-- .../pay/strategy/WeChatPayStrategy.java | 26 ++-- .../core/timeout/task/PayExpiredTimeTask.java | 4 + .../PayParamSignTest.java} | 27 ++-- daxpay-single/daxpay-single-start/pom.xml | 10 +- .../src/main/resources/application-dev.yml | 9 ++ pom.xml | 3 +- 41 files changed, 589 insertions(+), 121 deletions(-) create mode 100644 daxpay-single-demo/pom.xml create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/DaxPaySingleDemoApp.java create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayDemoProperties.java create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayKitConfiguration.java create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/controller/DaxPayCashierController.java create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/param/CashierSimplePayParam.java create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/result/PayOrderResult.java create mode 100644 daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/service/DaxPayCashierService.java create mode 100644 daxpay-single-demo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/SortMapParam.java rename daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/{QueryPayParam.java => QueryPayOrderParam.java} (92%) rename daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/{QueryRefundParam.java => QueryRefundOrderParam.java} (92%) rename daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/{payment/common/service/PaymentSignServiceTest.java => util/PayParamSignTest.java} (68%) diff --git a/_doc/ChangeLog.md b/_doc/ChangeLog.md index 8375bb8b6..619c7cade 100644 --- a/_doc/ChangeLog.md +++ b/_doc/ChangeLog.md @@ -1,21 +1,16 @@ # CHANGELOG -## [v1.0.1] 支付之路: 多商户适配 -- 新增: 钱包支持多商户和多应用 -- 新增: 储值卡支持多商户和多应用 -- 新增: 网关同步记录进行保存 -- 新增: 钱包支持设置开通时的默认金额 -- 新增: 储值卡多卡支付演示 -- 优化: 结算台支持多商户 -- 优化: 重构支付消息通知结构, 使后期支持多种消息中间件 -- 优化: 拆分网关同步相关代码 -- 优化: 保存各渠道的支付单 - -## [v1.0.0] 支付之路 -- 支持单渠道支付、聚合支付、组合支付、退款功能 -- 单渠道支付:支持支付宝、微信、现金、钱包、储值卡等多种支付方式 -- 聚合支付:支持微信或支付宝使用同一个码 -- 组合支付:支持多种同步支付和一个异步支付(微信、支付宝)进行组合支付 -- 支持退款:部分对款、全部退款等方式 -- 储值卡:支持单卡支付、多卡支付,退款时支持退款到原储值卡中,也支持将余额退到同一个卡上 -- 支付宝:支持web支付、wap支付、扫码支付、付款码支付、APP支付 -- 微信:wap支付、扫码支付、付款码支付、APP支付、公众号/小程序支付 +## [v2.0.0] +- 支持请求参数签名和验签机制,已经支持SHA256和 +- 去除调用时的用户概念,作为独立的支付网关使用 +- 支持支付功能、已对支付宝、微信进行支持 +- 支持支付订单超时自动进行关闭 +- 支持支付订单关闭功能,关闭 +- 支持支付退款功能,可以进行全部退款或部分退款 +- 支持支付同步功能,通过同步接口可以获取第三方支付网关的状态 +- 支持支付和退款订单的修复功能,根据取第三方支付网关订单的状态,对订单进行修正,如支付同步、退款同步、消息回调等可触发 +- 部分支付对账功能,已经实现支付宝和微信对账单下载解析和保存的功能 +- 支持对各支付通道进行管理,包括是否启用、显示Logo图等 +- 支持对支付网关对外暴露的接口进行管理,支持启停用、是否验签、是否消息通知等功能 +- 组合支付已预先进行支持,支持一个异步支付+多个同步支付通道组合进行收单支付 +- 提供Java版本SDK,简化业务系统对支付网关的调用 +- 对回调记录、同步记录、修复记录、关闭记录、进行报错 diff --git a/_doc/Task.md b/_doc/Task.md index 378123125..dae8d3b5f 100644 --- a/_doc/Task.md +++ b/_doc/Task.md @@ -1,10 +1,15 @@ +2.0.1 里程碑 +- 支持各类回调通知 +- 现金、钱包、储值卡功能进行重构 +- + 2.0.0 重构进度 - 已经完成的 - [x] 参数签名和验签机制 - [x] 开放接口供第三方调用 - [x] 将零散的上下文对象进行抽取为统一的上下文对象 - [x] 拆分原有的策略类,实现粒度更细 - - [x] 去除用户概念,作为独立的支付网关使用, 不与其他系统产生耦合性 + - [x] 去除用户概念,作为独立的支付网关使用,不与其他系统产生耦合性 - 2023-12-31: - [x] 支付关闭相关逻辑 - [x] 各支付通道补充相关未实现的逻辑 @@ -55,7 +60,7 @@ - 2024-01-13: - [x] (前端) 微信/支付宝支付通道配置 - [x] 支付通道支持停用 - - [x] 请求支付网关时区退款号以R开头, 用于与支付ID的区分 + - [x] ~~请求支付网关时区退款号以R开头, 用于与支付ID的区分~~ - [x] (前端) 平台配置 - 2024-01-15: - [x] 优化支付相关订单和记录的查询条件 diff --git a/daxpay-single-demo/pom.xml b/daxpay-single-demo/pom.xml new file mode 100644 index 000000000..1eadf3b43 --- /dev/null +++ b/daxpay-single-demo/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + cn.bootx.platform + dax-pay + 2.0.0 + + + daxpay-single-demo + + + 8 + 8 + UTF-8 + + + + + + org.projectlombok + lombok + provided + + + + + cn.bootx.platform + service-baseapi + ${bootx-platform.version} + + + + + cn.bootx.platform + daxpay-single-sdk + 2.0.0 + + + + diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/DaxPaySingleDemoApp.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/DaxPaySingleDemoApp.java new file mode 100644 index 000000000..3e9f2711e --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/DaxPaySingleDemoApp.java @@ -0,0 +1,12 @@ +package cn.bootx.platform.daxpay.demo; + +import org.springframework.context.annotation.ComponentScan; + +/** + * 支付演示模块 + * @author xxm + * @since 2024/2/8 + */ +@ComponentScan +public class DaxPaySingleDemoApp { +} diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayDemoProperties.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayDemoProperties.java new file mode 100644 index 000000000..6c548420c --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayDemoProperties.java @@ -0,0 +1,28 @@ +package cn.bootx.platform.daxpay.demo.config; + +import cn.bootx.platform.daxpay.sdk.code.SignTypeEnum; +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 演示模块配置类 + * @author xxm + * @since 2024/2/8 + */ +@Getter +@Setter +@ConfigurationProperties(prefix = "dax-pay.demo") +public class DaxPayDemoProperties { + /** 服务地址 */ + private String serverUrl; + + /** 签名方式 */ + private SignTypeEnum signType = SignTypeEnum.MD5; + + /** 签名秘钥 */ + private String signSecret; + + /** 请求超时时间 */ + private int reqTimeout = 30000; +} diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayKitConfiguration.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayKitConfiguration.java new file mode 100644 index 000000000..5ec45a6a5 --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/config/DaxPayKitConfiguration.java @@ -0,0 +1,37 @@ +package cn.bootx.platform.daxpay.demo.config; + +import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig; +import cn.bootx.platform.daxpay.sdk.net.DaxPayKit; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.event.ApplicationStartedEvent; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; + +/** + * 初始化 + * @author xxm + * @since 2024/2/8 + */ +@Configuration +@RequiredArgsConstructor +@EnableConfigurationProperties(DaxPayDemoProperties.class) +public class DaxPayKitConfiguration { + + private final DaxPayDemoProperties daxPayDemoProperties; + + /** + * 初始化 + */ + @EventListener(ApplicationStartedEvent.class) +// @EventListener(webstarteve.class) + public void initDaxPayKit(){ + DaxPayConfig config = DaxPayConfig.builder() + .serviceUrl(daxPayDemoProperties.getServerUrl()) + .signType(daxPayDemoProperties.getSignType()) + .signSecret(daxPayDemoProperties.getSignSecret()) + .reqTimeout(daxPayDemoProperties.getReqTimeout()) + .build(); + DaxPayKit.initConfig(config); + } +} diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/controller/DaxPayCashierController.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/controller/DaxPayCashierController.java new file mode 100644 index 000000000..ef0d198a3 --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/controller/DaxPayCashierController.java @@ -0,0 +1,39 @@ +package cn.bootx.platform.daxpay.demo.controller; + +import cn.bootx.platform.common.core.rest.Res; +import cn.bootx.platform.common.core.rest.ResResult; +import cn.bootx.platform.common.core.util.ValidationUtil; +import cn.bootx.platform.daxpay.demo.param.CashierSimplePayParam; +import cn.bootx.platform.daxpay.demo.result.PayOrderResult; +import cn.bootx.platform.daxpay.demo.service.DaxPayCashierService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +/** + * 结算台演示 + * @author xxm + * @since 2024/2/8 + */ +@Tag(name = "结算台演示") +@RestController +@RequestMapping("/demo/cashier") +@RequiredArgsConstructor +public class DaxPayCashierController { + + private final DaxPayCashierService cashierService; + + @Operation(summary = "创建支付订单并发起") + @PostMapping("/simplePayCashier") + public ResResult simplePayCashier(@RequestBody CashierSimplePayParam param){ + ValidationUtil.validateParam(param); + return Res.ok(cashierService.simplePayCashier(param)); + } + + @Operation(summary = "查询支付订单") + @GetMapping("/queryPayOrder") + public ResResult queryPayOrder(Long paymentId){ + return Res.ok(cashierService.queryPayOrder(paymentId)); + } +} diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/param/CashierSimplePayParam.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/param/CashierSimplePayParam.java new file mode 100644 index 000000000..a45b4cd11 --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/param/CashierSimplePayParam.java @@ -0,0 +1,47 @@ +package cn.bootx.platform.daxpay.demo.param; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; + +/** + * 结算台简单支付参数(单通道支付) + * + * @author xxm + * @since 2022/2/23 + */ +@Data +@Accessors(chain = true) +@Schema(title = "结算台简单支付参数(单通道支付)") +public class CashierSimplePayParam { + + @Schema(description = "业务号") + @NotNull + private String businessNo; + + @Schema(description = "标题") + @NotNull + private String title; + + @Schema(description = "金额") + @NotNull + private BigDecimal amount; + + @Schema(description = "openId(微信支付时使用)") + private String openId; + + @Schema(description = "支付渠道") + @NotNull + private String channel; + + @Schema(description = "支付方式") + @NotNull + private String payWay; + + @Schema(description = "付款码") + private String authCode; + +} diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/result/PayOrderResult.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/result/PayOrderResult.java new file mode 100644 index 000000000..0810cba2c --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/result/PayOrderResult.java @@ -0,0 +1,31 @@ +package cn.bootx.platform.daxpay.demo.result; + +import cn.bootx.platform.daxpay.sdk.code.PayChannelEnum; +import lombok.Getter; +import lombok.Setter; + +/** + * 发起支付后响应对象 + * @author xxm + * @since 2024/2/8 + */ +@Getter +@Setter +public class PayOrderResult { + /** 支付ID */ + private Long paymentId; + + /** 是否是异步支付 */ + private boolean asyncPay; + + /** + * 异步支付通道 + * @see PayChannelEnum + */ + private String asyncChannel; + + + /** 支付参数体(通常用于发起异步支付的参数) */ + private String payBody; + +} diff --git a/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/service/DaxPayCashierService.java b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/service/DaxPayCashierService.java new file mode 100644 index 000000000..56895ae14 --- /dev/null +++ b/daxpay-single-demo/src/main/java/cn/bootx/platform/daxpay/demo/service/DaxPayCashierService.java @@ -0,0 +1,128 @@ +package cn.bootx.platform.daxpay.demo.service; + +import cn.bootx.platform.common.core.exception.BizException; +import cn.bootx.platform.common.spring.util.WebServletUtil; +import cn.bootx.platform.daxpay.demo.param.CashierSimplePayParam; +import cn.bootx.platform.daxpay.demo.result.PayOrderResult; +import cn.bootx.platform.daxpay.sdk.code.PayChannelEnum; +import cn.bootx.platform.daxpay.sdk.code.PayStatusEnum; +import cn.bootx.platform.daxpay.sdk.code.PayWayEnum; +import cn.bootx.platform.daxpay.sdk.model.pay.PayOrderModel; +import cn.bootx.platform.daxpay.sdk.model.pay.QueryPayOrderModel; +import cn.bootx.platform.daxpay.sdk.net.DaxPayKit; +import cn.bootx.platform.daxpay.sdk.param.channel.AliPayParam; +import cn.bootx.platform.daxpay.sdk.param.channel.WeChatPayParam; +import cn.bootx.platform.daxpay.sdk.param.pay.QueryPayOrderParam; +import cn.bootx.platform.daxpay.sdk.param.pay.SimplePayParam; +import cn.bootx.platform.daxpay.sdk.response.DaxPayResult; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.extra.servlet.ServletUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * 支付结算台服务类 + * @author xxm + * @since 2024/2/8 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DaxPayCashierService { + + /** + * 结算台简单支付, 创建支付订单并拉起支付 + */ + public PayOrderResult simplePayCashier(CashierSimplePayParam param){ + // 将参数转换为简单支付参数 + SimplePayParam simplePayParam = new SimplePayParam(); + simplePayParam.setBusinessNo(param.getBusinessNo()); + int amount = param.getAmount() + .multiply(BigDecimal.valueOf(100)) + .intValue(); + simplePayParam.setTitle(param.getTitle()); + simplePayParam.setAmount(amount); + simplePayParam.setChannel(param.getChannel()); + simplePayParam.setPayWay(param.getPayWay()); + + // 支付宝通道 + if (Objects.equals(PayChannelEnum.ALI.getCode(), param.getChannel())){ + // 付款码支付 + if (Objects.equals(PayWayEnum.BARCODE.getCode(), param.getPayWay())){ + AliPayParam aliPayParam = new AliPayParam(); + aliPayParam.setAuthCode(param.getAuthCode()); + simplePayParam.setChannelParam(aliPayParam); + } + } + // 微信通道 + if (Objects.equals(PayChannelEnum.WECHAT.getCode(), param.getChannel())){ + WeChatPayParam wechatPayParam = new WeChatPayParam(); + // 付款码支付 + if (Objects.equals(PayWayEnum.BARCODE.getCode(), param.getPayWay())){ + wechatPayParam.setAuthCode(param.getAuthCode()); + simplePayParam.setChannelParam(wechatPayParam); + } + // 微信jsapi 方式支付 + if (Objects.equals(PayWayEnum.JSAPI.getCode(), param.getPayWay())){ + wechatPayParam.setOpenId(param.getOpenId()); + simplePayParam.setChannelParam(wechatPayParam); + } + } + String ip = Optional.ofNullable(WebServletUtil.getRequest()) + .map(ServletUtil::getClientIP) + .orElse("127.0.0.1"); + simplePayParam.setClientIp(ip); + // 异步回调地址 + simplePayParam.setNotifyUrl("http://localhost:9000"); + // 同步回调地址 + simplePayParam.setReturnUrl("http://localhost:9000"); + + // 发起支付 + DaxPayResult execute = DaxPayKit.execute(simplePayParam); + // 判断是否支付成功 + if (execute.getCode() != 0){ + throw new BizException(execute.getMsg()); + } + // 判断是否发起支付成功 + PayOrderModel payOrderModel = execute.getData(); + + // 状态 支付中或支付完成返回 + List list = Arrays.asList(PayStatusEnum.PROGRESS.getCode(), PayStatusEnum.SUCCESS.getCode()); + if (list.contains(payOrderModel.getStatus())){ + PayOrderResult payOrderResult = new PayOrderResult(); + BeanUtil.copyProperties(payOrderModel, payOrderResult); + return payOrderResult; + } else { + throw new BizException("订单状态异常,无法进行支付"); + } + } + + /** + * 订单查询接口 + */ + public boolean queryPayOrder(Long paymentId){ + QueryPayOrderParam queryPayOrderParam = new QueryPayOrderParam(); + queryPayOrderParam.setPaymentId(paymentId); + DaxPayResult execute = DaxPayKit.execute(queryPayOrderParam); + if (execute.getCode() != 0){ + throw new BizException(execute.getMsg()); + } + QueryPayOrderModel data = execute.getData(); + String status = data.getStatus(); + if (Objects.equals(status, PayStatusEnum.PROGRESS.getCode())){ + return false; + } + else if (Objects.equals(status, PayStatusEnum.SUCCESS.getCode())){ + return true; + } else { + throw new BizException("订单状态不是支付中或支付完成,请检查"); + } + } +} diff --git a/daxpay-single-demo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/daxpay-single-demo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..e992c2cf1 --- /dev/null +++ b/daxpay-single-demo/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +cn.bootx.platform.daxpay.demo.DaxPaySingleDemoApp diff --git a/daxpay-single-sdk/pom.xml b/daxpay-single-sdk/pom.xml index 702ebd851..e6f2bcde2 100644 --- a/daxpay-single-sdk/pom.xml +++ b/daxpay-single-sdk/pom.xml @@ -51,23 +51,34 @@ + cn.hutool hutool-all ${hutool.version} + ch.qos.logback logback-classic ${logback-classic.version} provided + org.projectlombok lombok ${lombok.version} provided + + + org.jetbrains + annotations + 13.0 + provided + + junit junit diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/model/pay/PayOrderModel.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/model/pay/PayOrderModel.java index 800782b6c..412b4511d 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/model/pay/PayOrderModel.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/model/pay/PayOrderModel.java @@ -8,7 +8,7 @@ import lombok.Setter; import lombok.ToString; /** - * 支付相应对象 + * 发起支付后响应对象 * @author xxm * @since 2024/2/2 */ diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/net/DaxPayKit.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/net/DaxPayKit.java index b8b38483f..36f7adcf6 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/net/DaxPayKit.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/net/DaxPayKit.java @@ -8,6 +8,7 @@ import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; import lombok.experimental.UtilityClass; +import lombok.extern.slf4j.Slf4j; import java.util.Objects; @@ -16,12 +17,14 @@ import java.util.Objects; * @author xxm * @since 2024/2/2 */ +@Slf4j @UtilityClass public class DaxPayKit { private DaxPayConfig config; public void initConfig(DaxPayConfig config){ + log.debug("DaxPayKit初始化..."); DaxPayKit.config = config; } @@ -54,12 +57,14 @@ public class DaxPayKit { } } String data = JSONUtil.toJsonStr(request); + log.debug("请求参数:{}", data); String path = config.getServiceUrl() + request.path(); HttpResponse execute = HttpUtil.createPost(path) .body(data, ContentType.JSON.getValue()) .timeout(config.getReqTimeout()) .execute(); String body = execute.body(); + log.debug("响应参数:{}", body); return request.toModel(body); } } diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/ChannelParam.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/ChannelParam.java index 461e6862c..80bca877b 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/ChannelParam.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/ChannelParam.java @@ -5,5 +5,5 @@ package cn.bootx.platform.daxpay.sdk.param; * @author xxm * @since 2023/12/17 */ -public interface ChannelParam { +public interface ChannelParam extends SortMapParam { } diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/SortMapParam.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/SortMapParam.java new file mode 100644 index 000000000..8ddd5d3ce --- /dev/null +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/SortMapParam.java @@ -0,0 +1,9 @@ +package cn.bootx.platform.daxpay.sdk.param; + +/** + * 标示字段可以转换为MAP进行排序 + * @author xxm + * @since 2024/2/8 + */ +public interface SortMapParam { +} diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/QueryPayParam.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/QueryPayOrderParam.java similarity index 92% rename from daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/QueryPayParam.java rename to daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/QueryPayOrderParam.java index e830ca982..89f0e67af 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/QueryPayParam.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/QueryPayOrderParam.java @@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode; */ @EqualsAndHashCode(callSuper = true) @Data -public class QueryPayParam extends DaxPayRequest { +public class QueryPayOrderParam extends DaxPayRequest { /** 支付ID */ private Long paymentId; diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/SimplePayParam.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/SimplePayParam.java index 9c357185d..7057bbf80 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/SimplePayParam.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/pay/SimplePayParam.java @@ -15,6 +15,7 @@ import cn.hutool.json.JSONUtil; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.jetbrains.annotations.NotNull; /** * 简单支付请求参数 @@ -27,9 +28,11 @@ import lombok.ToString; public class SimplePayParam extends DaxPayRequest { /** 业务号 */ + @NotNull private String businessNo; /** 支付标题 */ + @NotNull private String title; /** 支付描述 */ @@ -42,17 +45,21 @@ public class SimplePayParam extends DaxPayRequest { private String quitUrl; /** + * 支付通道 * @see PayChannelEnum#getCode() */ + @NotNull private String channel; /** * 支付方式编码 * @see PayWayEnum#getCode() */ + @NotNull private String payWay; /** 支付金额 */ + @NotNull private Integer amount; /** diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/QueryRefundParam.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/QueryRefundOrderParam.java similarity index 92% rename from daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/QueryRefundParam.java rename to daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/QueryRefundOrderParam.java index 15c3c38e7..f714659ce 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/QueryRefundParam.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/param/refund/QueryRefundOrderParam.java @@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode; */ @EqualsAndHashCode(callSuper = true) @Data -public class QueryRefundParam extends DaxPayRequest { +public class QueryRefundOrderParam extends DaxPayRequest { /** 退款ID */ private Long refundId; diff --git a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/util/PaySignUtil.java b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/util/PaySignUtil.java index a86764b3f..8415ae451 100644 --- a/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/util/PaySignUtil.java +++ b/daxpay-single-sdk/src/main/java/cn/bootx/platform/daxpay/sdk/util/PaySignUtil.java @@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.sdk.util; import cn.bootx.platform.daxpay.sdk.param.SortMapParam; import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.json.JSONUtil; @@ -94,9 +95,8 @@ public class PaySignUtil { } } - /** - * 把所有元素排序, 并拼接成字符, 用于签名 + * 把所有元素排序, 并拼接成字符, 用于签名, 同时会过滤掉 " 和 \ 字符 */ public static String createLinkString(Map params) { String connStr = "&"; @@ -118,10 +118,12 @@ public class PaySignUtil { .append(connStr); } } - return content.toString(); + String s = content.toString(); + s = StrUtil.replace(s,"\\",""); + s = StrUtil.replace(s,"\"",""); + return s; } - /** * 生成16进制 MD5 字符串 * diff --git a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryPayOrderTest.java b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryPayOrderTest.java index 5152271c8..9758efd9b 100644 --- a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryPayOrderTest.java +++ b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryPayOrderTest.java @@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.sdk.query; import cn.bootx.platform.daxpay.sdk.model.pay.QueryPayOrderModel; import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig; import cn.bootx.platform.daxpay.sdk.net.DaxPayKit; -import cn.bootx.platform.daxpay.sdk.param.pay.QueryPayParam; +import cn.bootx.platform.daxpay.sdk.param.pay.QueryPayOrderParam; import cn.bootx.platform.daxpay.sdk.response.DaxPayResult; import org.junit.Before; import org.junit.Test; @@ -27,7 +27,7 @@ public class QueryPayOrderTest { @Test public void testPay() { - QueryPayParam param = new QueryPayParam(); + QueryPayOrderParam param = new QueryPayOrderParam(); param.setBusinessNo("2"); diff --git a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryRefundOrderTest.java b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryRefundOrderTest.java index 54c5e4305..86fbff96d 100644 --- a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryRefundOrderTest.java +++ b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/query/QueryRefundOrderTest.java @@ -3,7 +3,7 @@ package cn.bootx.platform.daxpay.sdk.query; import cn.bootx.platform.daxpay.sdk.model.refund.QueryRefundOrderModel; import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig; import cn.bootx.platform.daxpay.sdk.net.DaxPayKit; -import cn.bootx.platform.daxpay.sdk.param.refund.QueryRefundParam; +import cn.bootx.platform.daxpay.sdk.param.refund.QueryRefundOrderParam; import cn.bootx.platform.daxpay.sdk.response.DaxPayResult; import org.junit.Before; import org.junit.Test; @@ -27,7 +27,7 @@ public class QueryRefundOrderTest { @Test public void testPay() { - QueryRefundParam param = new QueryRefundParam(); + QueryRefundOrderParam param = new QueryRefundOrderParam(); param.setRefundId(1755263825769361408L); diff --git a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/util/PayParamSignTest.java b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/util/PayParamSignTest.java index 04e8c130e..a3016fc83 100644 --- a/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/util/PayParamSignTest.java +++ b/daxpay-single-sdk/src/test/java/cn/bootx/platform/daxpay/sdk/util/PayParamSignTest.java @@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.sdk.util; import cn.bootx.platform.daxpay.sdk.code.PayChannelEnum; import cn.bootx.platform.daxpay.sdk.code.PayWayEnum; +import cn.bootx.platform.daxpay.sdk.param.channel.WeChatPayParam; import cn.bootx.platform.daxpay.sdk.param.pay.PayChannelParam; import cn.bootx.platform.daxpay.sdk.param.pay.PayParam; import lombok.extern.slf4j.Slf4j; @@ -43,7 +44,42 @@ public class PayParamSignTest { Map map = PaySignUtil.toMap(param); log.info("转换为有序MAP后的内容: {}",map); String data = PaySignUtil.createLinkString(map); - log.info("将MAP拼接字符串: {}",data); + log.info("将MAP拼接字符串, 并过滤掉特殊字符: {}",data); + String sign = "123456"; + data += "&sign="+sign; + data = data.toUpperCase(); + log.info("添加秘钥并转换为大写的字符串: {}",data); + log.info("MD5: {}",PaySignUtil.md5(data)); + log.info("HmacSHA256: {}",PaySignUtil.hmacSha256(data,sign)); + } + + /** + * 多层嵌套对象签名测试 + */ + @Test + public void sign2(){ + + PayParam param = new PayParam(); + param.setClientIp("127.0.0.1"); + + param.setBusinessNo("P0002"); + param.setTitle("测试接口支付"); + PayChannelParam payChannelParam = new PayChannelParam(); + payChannelParam.setChannel(PayChannelEnum.WECHAT.getCode()); + payChannelParam.setWay(PayWayEnum.QRCODE.getCode()); + payChannelParam.setAmount(1); + WeChatPayParam weChatPayParam = new WeChatPayParam(); + weChatPayParam.setOpenId("6688812"); + weChatPayParam.setAuthCode("123456"); + payChannelParam.setChannelParam(weChatPayParam); + + List payChannels = Collections.singletonList(payChannelParam); + param.setPayChannels(payChannels); + + Map map = PaySignUtil.toMap(param); + log.info("转换为有序MAP后的内容: {}",map); + String data = PaySignUtil.createLinkString(map).replaceAll("\\\"","").replaceAll("\"",""); + log.info("将MAP拼接字符串, 并过滤掉特殊字符: {}",data); String sign = "123456"; data += "&sign="+sign; data = data.toUpperCase(); diff --git a/daxpay-single/daxpay-single-admin/pom.xml b/daxpay-single/daxpay-single-admin/pom.xml index 02d5a3a57..dcf62f05e 100644 --- a/daxpay-single/daxpay-single-admin/pom.xml +++ b/daxpay-single/daxpay-single-admin/pom.xml @@ -16,8 +16,9 @@ cn.bootx.platform daxpay-single-service - ${dax.version} + ${daxpay.version} + cn.bootx.platform diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayChannelParam.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayChannelParam.java index 088117f2b..813068cb1 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayChannelParam.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/PayChannelParam.java @@ -11,6 +11,7 @@ import lombok.Data; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; +import java.util.Map; /** * 同意下单支付方式参数 @@ -48,5 +49,5 @@ public class PayChannelParam { * @see WalletPayParam */ @Schema(description = "附加支付参数") - private String channelParam; + private Map channelParam; } diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/SimplePayParam.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/SimplePayParam.java index ea7f6dc47..92577e6c4 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/SimplePayParam.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/param/pay/SimplePayParam.java @@ -16,6 +16,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; +import java.util.Map; /** * 简单下单参数 @@ -71,7 +72,7 @@ public class SimplePayParam extends PayCommonParam{ * @see WalletPayParam */ @Schema(description = "附加通道支付参数") - private String channelParam; + private Map channelParam; } diff --git a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PaySignUtil.java b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PaySignUtil.java index 48012f478..fe4d703ff 100644 --- a/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PaySignUtil.java +++ b/daxpay-single/daxpay-single-core/src/main/java/cn/bootx/platform/daxpay/util/PaySignUtil.java @@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.util; import cn.bootx.platform.common.core.util.LocalDateTimeUtil; import cn.hutool.core.util.ClassUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.json.JSONUtil; @@ -99,7 +100,7 @@ public class PaySignUtil { /** - * 把所有元素排序, 并拼接成字符, 用于签名 + * 把所有元素排序, 并拼接成字符, 用于签名, 同时会过滤掉 " 和 \ 字符 */ public static String createLinkString(Map params) { String connStr = "&"; @@ -121,7 +122,10 @@ public class PaySignUtil { .append(connStr); } } - return content.toString(); + String s = content.toString(); + s = StrUtil.replace(s,"\\",""); + s = StrUtil.replace(s,"\"",""); + return s; } diff --git a/daxpay-single/daxpay-single-gateway/pom.xml b/daxpay-single/daxpay-single-gateway/pom.xml index 130cdce50..10bef0624 100644 --- a/daxpay-single/daxpay-single-gateway/pom.xml +++ b/daxpay-single/daxpay-single-gateway/pom.xml @@ -15,7 +15,7 @@ cn.bootx.platform daxpay-single-service - ${dax.version} + ${daxpay.version} diff --git a/daxpay-single/daxpay-single-service/pom.xml b/daxpay-single/daxpay-single-service/pom.xml index 4cf0b71f6..2941968b0 100644 --- a/daxpay-single/daxpay-single-service/pom.xml +++ b/daxpay-single/daxpay-single-service/pom.xml @@ -70,7 +70,7 @@ cn.bootx.platform daxpay-single-core - ${dax.version} + ${daxpay.version} @@ -109,6 +109,7 @@ junit-jupiter test + com.baomidou lock4j-redis-template-spring-boot-starter diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/voucher/service/VoucherPayService.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/voucher/service/VoucherPayService.java index cd042905f..3b8849301 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/voucher/service/VoucherPayService.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/channel/voucher/service/VoucherPayService.java @@ -1,5 +1,8 @@ package cn.bootx.platform.daxpay.service.core.channel.voucher.service; +import cn.bootx.platform.daxpay.exception.pay.PayFailureException; +import cn.bootx.platform.daxpay.param.channel.VoucherPayParam; +import cn.bootx.platform.daxpay.param.pay.PayChannelParam; import cn.bootx.platform.daxpay.service.code.VoucherCode; import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherLogManager; import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherManager; @@ -9,17 +12,17 @@ import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherLog; import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherPayOrder; import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherRecord; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; -import cn.bootx.platform.daxpay.exception.pay.PayFailureException; -import cn.bootx.platform.daxpay.param.channel.VoucherPayParam; -import cn.bootx.platform.daxpay.param.pay.PayChannelParam; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONException; -import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Map; + /** * 储值卡支付 * @@ -46,9 +49,10 @@ public class VoucherPayService { VoucherPayParam voucherPayParam; try { // 储值卡参数验证 - String extraParamsJson = payChannelParam.getChannelParam(); - if (StrUtil.isNotBlank(extraParamsJson)) { - voucherPayParam = JSONUtil.toBean(extraParamsJson, VoucherPayParam.class); + + Map channelParam = payChannelParam.getChannelParam(); + if (CollUtil.isNotEmpty(channelParam)) { + voucherPayParam = BeanUtil.toBean(channelParam, VoucherPayParam.class); } else { throw new PayFailureException("储值卡支付参数错误"); } 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 610499f79..98ab5a8a8 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 @@ -2,6 +2,9 @@ package cn.bootx.platform.daxpay.service.core.order.pay.builder; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.code.PayStatusEnum; +import cn.bootx.platform.daxpay.param.pay.PayChannelParam; +import cn.bootx.platform.daxpay.param.pay.PayParam; +import cn.bootx.platform.daxpay.result.pay.PayResult; import cn.bootx.platform.daxpay.service.common.context.AsyncPayLocal; import cn.bootx.platform.daxpay.service.common.context.NoticeLocal; import cn.bootx.platform.daxpay.service.common.context.PlatformLocal; @@ -9,20 +12,15 @@ import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal; 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.entity.RefundableInfo; -import cn.bootx.platform.daxpay.param.pay.PayParam; -import cn.bootx.platform.daxpay.param.pay.PayChannelParam; -import cn.bootx.platform.daxpay.result.pay.PayResult; -import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import lombok.experimental.UtilityClass; import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.stream.Collectors; /** * 支付对象构建器 @@ -91,28 +89,19 @@ public class PayBuilder { * 构建订单关联通道信息 */ public PayChannelOrder buildPayChannelOrder(PayChannelParam channelParam) { - return new PayChannelOrder() + PayChannelOrder payChannelOrder = new PayChannelOrder() .setAsync(PayChannelEnum.ASYNC_TYPE_CODE.contains(channelParam.getChannel())) .setChannel(channelParam.getChannel()) .setPayWay(channelParam.getWay()) .setAmount(channelParam.getAmount()) - .setRefundableBalance(channelParam.getAmount()) - .setChannelExtra(channelParam.getChannelParam()); - } + .setRefundableBalance(channelParam.getAmount()); - /** - * 构建支付订单的可退款信息列表 - */ - @Deprecated - private List buildRefundableInfo(List payChannelParamList) { - if (CollectionUtil.isEmpty(payChannelParamList)) { - return Collections.emptyList(); + Map cp = channelParam.getChannelParam(); + if (CollUtil.isNotEmpty(cp)){ + payChannelOrder.setChannelExtra(JSONUtil.toJsonStr(cp)); } - return payChannelParamList.stream() - .map(o-> new RefundableInfo() - .setChannel(o.getChannel()) - .setAmount(o.getAmount())) - .collect(Collectors.toList()); + + return payChannelOrder; } 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 af36a76ee..176c8cdee 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 @@ -11,6 +11,8 @@ import cn.bootx.platform.daxpay.service.core.order.pay.convert.PayOrderConvert; import cn.bootx.platform.daxpay.service.dto.order.pay.PayChannelOrderDto; import cn.bootx.table.modify.annotation.DbColumn; import cn.bootx.table.modify.annotation.DbTable; +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; @@ -66,6 +68,7 @@ public class PayChannelOrder extends MpCreateEntity implements EntityBaseFunctio * @see WalletPayParam */ @DbColumn(comment = "附加支付参数") + @TableField(updateStrategy = FieldStrategy.NEVER) private String channelExtra; /** 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 c0bde41a7..1b852cd6b 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 @@ -12,6 +12,7 @@ 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.PayRefundChannelOrder; import cn.bootx.platform.daxpay.service.dto.order.pay.PayChannelOrderDto; +import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -19,6 +20,7 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -57,6 +59,12 @@ public class PayChannelOrderService { PayStatusEnum payStatus = asyncPayInfo.isPayComplete() ? PayStatusEnum.SUCCESS : PayStatusEnum.PROGRESS; // 判断新发起的 Optional payOrderChannelOpt = channelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payChannelParam.getChannel()); + // 扩展信息处理 + Map channelParam = payChannelParam.getChannelParam(); + String channelParamStr = null; + if (Objects.nonNull(channelParam)){ + channelParamStr = JSONUtil.toJsonStr(channelParam); + } if (!payOrderChannelOpt.isPresent()){ PayChannelOrder payChannelOrder = new PayChannelOrder(); // 替换原有的的支付通道信息 @@ -68,7 +76,7 @@ public class PayChannelOrderService { .setAmount(payChannelParam.getAmount()) .setRefundableBalance(payChannelParam.getAmount()) .setPayTime(LocalDateTime.now()) - .setChannelExtra(payChannelParam.getChannelParam()) + .setChannelExtra(channelParamStr) .setStatus(payStatus.getCode()); channelOrderManager.deleteByPaymentIdAndAsync(payOrder.getId()); channelOrderManager.save(payChannelOrder); @@ -77,7 +85,7 @@ public class PayChannelOrderService { payOrderChannelOpt.get() .setPayWay(payChannelParam.getWay()) .setPayTime(LocalDateTime.now()) - .setChannelExtra(payChannelParam.getChannelParam()) + .setChannelExtra(channelParamStr) .setStatus(payStatus.getCode()); channelOrderManager.updateById(payOrderChannelOpt.get()); } diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/AliPayStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/AliPayStrategy.java index 4eca935f3..365b71dee 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/AliPayStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/AliPayStrategy.java @@ -10,13 +10,15 @@ import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfig import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayService; import cn.bootx.platform.daxpay.service.core.order.pay.service.PayChannelOrderService; import cn.bootx.platform.daxpay.service.func.AbsPayStrategy; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONException; -import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import java.util.Map; + import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; /** @@ -52,9 +54,9 @@ public class AliPayStrategy extends AbsPayStrategy { public void doBeforePayHandler() { try { // 支付宝参数验证 - String extraParamsJson = this.getPayChannelParam().getChannelParam(); - if (StrUtil.isNotBlank(extraParamsJson)) { - this.aliPayParam = JSONUtil.toBean(extraParamsJson, AliPayParam.class); + Map channelParam = this.getPayChannelParam().getChannelParam(); + if (CollUtil.isNotEmpty(channelParam)) { + this.aliPayParam = BeanUtil.toBean(channelParam, AliPayParam.class); } else { this.aliPayParam = new AliPayParam(); diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WalletPayStrategy.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WalletPayStrategy.java index a5b2a3712..973afa6b7 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WalletPayStrategy.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/payment/pay/strategy/WalletPayStrategy.java @@ -1,23 +1,24 @@ package cn.bootx.platform.daxpay.service.core.payment.pay.strategy; import cn.bootx.platform.daxpay.code.PayChannelEnum; -import cn.bootx.platform.daxpay.service.code.WalletCode; -import cn.bootx.platform.daxpay.service.core.channel.wallet.entity.Wallet; -import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayService; -import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayOrderService; -import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletQueryService; import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.exception.waller.WalletBannedException; import cn.bootx.platform.daxpay.exception.waller.WalletLackOfBalanceException; +import cn.bootx.platform.daxpay.service.code.WalletCode; +import cn.bootx.platform.daxpay.service.core.channel.wallet.entity.Wallet; +import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayOrderService; +import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletPayService; +import cn.bootx.platform.daxpay.service.core.channel.wallet.service.WalletQueryService; import cn.bootx.platform.daxpay.service.func.AbsPayStrategy; import cn.bootx.platform.daxpay.service.param.channel.wallet.WalletPayParam; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONException; -import cn.hutool.json.JSONUtil; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import java.util.Map; import java.util.Objects; import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; @@ -54,9 +55,9 @@ public class WalletPayStrategy extends AbsPayStrategy { WalletPayParam walletPayParam = new WalletPayParam(); try { // 钱包参数验证 - String extraParamsJson = this.getPayChannelParam().getChannelParam(); - if (StrUtil.isNotBlank(extraParamsJson)) { - walletPayParam = JSONUtil.toBean(extraParamsJson, WalletPayParam.class); + Map channelParam = this.getPayChannelParam().getChannelParam(); + if (CollUtil.isNotEmpty(channelParam)) { + walletPayParam = BeanUtil.toBean(channelParam, WalletPayParam.class); } } catch (JSONException e) { throw new PayFailureException("支付参数错误"); 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 8796d181a..2581cb9d9 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 @@ -2,7 +2,6 @@ package cn.bootx.platform.daxpay.service.core.payment.pay.strategy; import cn.bootx.platform.daxpay.code.PayChannelEnum; import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException; -import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.bootx.platform.daxpay.param.pay.PayChannelParam; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService; @@ -10,13 +9,14 @@ import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPaySer import cn.bootx.platform.daxpay.service.core.order.pay.service.PayChannelOrderService; import cn.bootx.platform.daxpay.service.func.AbsPayStrategy; import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayParam; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONException; -import cn.hutool.json.JSONUtil; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import java.util.Map; + import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; /** @@ -53,18 +53,12 @@ public class WeChatPayStrategy extends AbsPayStrategy { */ @Override public void doBeforePayHandler() { - try { - // 微信参数验证 - String extraParamsJson = this.getPayChannelParam().getChannelParam(); - if (StrUtil.isNotBlank(extraParamsJson)) { - this.weChatPayParam = JSONUtil.toBean(extraParamsJson, WeChatPayParam.class); - } - else { - this.weChatPayParam = new WeChatPayParam(); - } - } - catch (JSONException e) { - throw new PayFailureException("支付参数错误"); + // 微信参数验证 + Map channelParam = this.getPayChannelParam().getChannelParam(); + if (CollUtil.isNotEmpty(channelParam)) { + this.weChatPayParam = BeanUtil.toBean(channelParam, WeChatPayParam.class); + } else { + this.weChatPayParam = new WeChatPayParam(); } // 检查金额 diff --git a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/timeout/task/PayExpiredTimeTask.java b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/timeout/task/PayExpiredTimeTask.java index 26b6489d2..d58086530 100644 --- a/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/timeout/task/PayExpiredTimeTask.java +++ b/daxpay-single/daxpay-single-service/src/main/java/cn/bootx/platform/daxpay/service/core/timeout/task/PayExpiredTimeTask.java @@ -54,6 +54,10 @@ public class PayExpiredTimeTask { paySyncParam.setPaymentId(paymentId); paySyncService.sync(paySyncParam); } catch (Exception e) { + // 如果是未查询到取消支付订单, 则删除这个任务 + if (Objects.equals("未查询到支付订单", e.getMessage())){ + repository.removeKeys(expiredKey); + } log.error("超时取消任务 异常", e); } finally { lockTemplate.releaseLock(lock); diff --git a/daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/payment/common/service/PaymentSignServiceTest.java b/daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/util/PayParamSignTest.java similarity index 68% rename from daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/payment/common/service/PaymentSignServiceTest.java rename to daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/util/PayParamSignTest.java index 89da03e41..62bf97788 100644 --- a/daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/payment/common/service/PaymentSignServiceTest.java +++ b/daxpay-single/daxpay-single-service/src/test/java/cn/bootx/platform/daxpay/core/util/PayParamSignTest.java @@ -1,17 +1,18 @@ -package cn.bootx.platform.daxpay.core.payment.common.service; +package cn.bootx.platform.daxpay.core.util; import cn.bootx.platform.daxpay.param.channel.AliPayParam; import cn.bootx.platform.daxpay.param.channel.WeChatPayParam; import cn.bootx.platform.daxpay.param.pay.PayChannelParam; import cn.bootx.platform.daxpay.param.pay.PayParam; import cn.bootx.platform.daxpay.util.PaySignUtil; -import cn.hutool.json.JSONUtil; +import cn.hutool.core.bean.BeanUtil; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * 支付签名服务 @@ -19,7 +20,7 @@ import java.util.List; * @since 2023/12/24 */ @Slf4j -class PaymentSignServiceTest { +class PayParamSignTest { @Test void toMap() { @@ -40,7 +41,7 @@ class PaymentSignServiceTest { p1.setWay("wx_app"); AliPayParam aliPayParam = new AliPayParam(); aliPayParam.setAuthCode("6688"); - p1.setChannelParam(JSONUtil.toJsonStr(aliPayParam)); + p1.setChannelParam(BeanUtil.beanToMap(aliPayParam,false,true)); PayChannelParam p2 = new PayChannelParam(); p2.setAmount(100); @@ -49,17 +50,21 @@ class PaymentSignServiceTest { WeChatPayParam weChatPayParam = new WeChatPayParam(); weChatPayParam.setOpenId("w2qsz2xawe3gbhyyff28fs01fd"); weChatPayParam.setAuthCode("8866"); - p2.setChannelParam(JSONUtil.toJsonStr(weChatPayParam)); + p2.setChannelParam(BeanUtil.beanToMap(aliPayParam,false,true)); List payWays = Arrays.asList(p1, p2); payParam.setPayChannels(payWays); - // 签名 - String sign = "123456"; - String md5Sign = PaySignUtil.md5Sign(payParam, sign); - String hmacSha256Sign = PaySignUtil.hmacSha256Sign(payParam, sign); - log.info("MD5: {}",md5Sign); - log.info("HmacSHA256: {}", hmacSha256Sign); + Map map = PaySignUtil.toMap(payParam); + log.info("转换为有序MAP后的内容: {}",map); + String data = PaySignUtil.createLinkString(map); + log.info("将MAP拼接字符串, 并过滤掉特殊字符: {}",data); + String sign = "123456"; + data += "&sign="+sign; + data = data.toUpperCase(); + log.info("添加秘钥并转换为大写的字符串: {}",data); + log.info("MD5: {}",PaySignUtil.md5(data)); + log.info("HmacSHA256: {}",PaySignUtil.hmacSha256(data,sign)); } } diff --git a/daxpay-single/daxpay-single-start/pom.xml b/daxpay-single/daxpay-single-start/pom.xml index 44ab94d0e..c48e97ef9 100644 --- a/daxpay-single/daxpay-single-start/pom.xml +++ b/daxpay-single/daxpay-single-start/pom.xml @@ -16,13 +16,13 @@ cn.bootx.platform daxpay-single-gateway - ${dax.version} + ${daxpay.version} cn.bootx.platform daxpay-single-admin - ${dax.version} + ${daxpay.version} @@ -35,7 +35,11 @@ common-starter-code-gen - + + cn.bootx.platform + daxpay-single-demo + ${daxpay.version} + diff --git a/daxpay-single/daxpay-single-start/src/main/resources/application-dev.yml b/daxpay-single/daxpay-single-start/src/main/resources/application-dev.yml index 3d15b6d62..999300b06 100644 --- a/daxpay-single/daxpay-single-start/src/main/resources/application-dev.yml +++ b/daxpay-single/daxpay-single-start/src/main/resources/application-dev.yml @@ -72,6 +72,7 @@ bootx: base-packages: "[支付网关API]": cn.bootx.platform.daxpay.gateway "[支付管理接口]": cn.bootx.platform.daxpay.admin + "[支付演示模块]": cn.bootx.platform.daxpay.demo "[BootxPlatform接口]": - cn.bootx.platform.common - cn.bootx.platform.starter @@ -150,3 +151,11 @@ dromara: enableStorage: true # 文件存储路径 storage-path: D:/data/files/ +# 支付系统配置 +dax-pay: + # 演示模块 + demo: + # 网关地址 + server-url: http://localhost:9000 + # 签名秘钥 + sign-secret: 123456 diff --git a/pom.xml b/pom.xml index 02a0341f0..1868f4d23 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ daxpay-single daxpay-single-sdk + daxpay-single-demo @@ -29,7 +30,7 @@ 1.3.6.1 - 2.0.0 + 2.0.0 1.7.30 3.16.8