feat 签名算法支持嵌套参数优化, 新增支付演示模块

This commit is contained in:
bootx
2024-02-09 00:32:16 +08:00
parent 9aeeee0ac0
commit a1efe54da4
41 changed files with 589 additions and 121 deletions

View File

@@ -1,21 +1,16 @@
# CHANGELOG # CHANGELOG
## [v1.0.1] 支付之路: 多商户适配 ## [v2.0.0]
- 新增: 钱包支持多商户和多应用 - 支持请求参数签名和验签机制已经支持SHA256和
- 新增: 储值卡支持多商户和多应 - 去除调用时的用户概念,作为独立的支付网关使
- 新增: 网关同步记录进行保存 - 支持支付功能、已对支付宝、微信进行支持
- 新增: 钱包支持设置开通时的默认金额 - 支持支付订单超时自动进行关闭
- 新增: 储值卡多卡支付演示 - 支持支付订单关闭功能,关闭
- 优化: 结算台支持多商户 - 支持支付退款功能,可以进行全部退款或部分退款
- 优化: 重构支付消息通知结构, 使后期支持多种消息中间件 - 支持支付同步功能,通过同步接口可以获取第三方支付网关的状态
- 优化: 拆分网关同步相关代码 - 支持支付和退款订单的修复功能,根据取第三方支付网关订单的状态,对订单进行修正,如支付同步、退款同步、消息回调等可触发
- 优化: 保存各渠道的支付单 - 部分支付对账功能,已经实现支付宝和微信对账单下载解析和保存的功能
- 支持对各支付通道进行管理包括是否启用、显示Logo图等
## [v1.0.0] 支付之路 - 支持对支付网关对外暴露的接口进行管理,支持启停用、是否验签、是否消息通知等功能
- 支持单渠道支付、聚合支付、组合支付、退款功能 - 组合支付已预先进行支持,支持一个异步支付+多个同步支付通道组合进行收单支付
- 单渠道支付:支持支付宝、微信、现金、钱包、储值卡等多种支付方式 - 提供Java版本SDK简化业务系统对支付网关的调用
- 聚合支付:支持微信或支付宝使用同一个码 - 对回调记录、同步记录、修复记录、关闭记录、进行报错
- 组合支付:支持多种同步支付和一个异步支付(微信、支付宝)进行组合支付
- 支持退款:部分对款、全部退款等方式
- 储值卡:支持单卡支付、多卡支付,退款时支持退款到原储值卡中,也支持将余额退到同一个卡上
- 支付宝支持web支付、wap支付、扫码支付、付款码支付、APP支付
- 微信wap支付、扫码支付、付款码支付、APP支付、公众号/小程序支付

View File

@@ -1,10 +1,15 @@
2.0.1 里程碑
- 支持各类回调通知
- 现金、钱包、储值卡功能进行重构
-
2.0.0 重构进度 2.0.0 重构进度
- 已经完成的 - 已经完成的
- [x] 参数签名和验签机制 - [x] 参数签名和验签机制
- [x] 开放接口供第三方调用 - [x] 开放接口供第三方调用
- [x] 将零散的上下文对象进行抽取为统一的上下文对象 - [x] 将零散的上下文对象进行抽取为统一的上下文对象
- [x] 拆分原有的策略类,实现粒度更细 - [x] 拆分原有的策略类,实现粒度更细
- [x] 去除用户概念,作为独立的支付网关使用, 不与其他系统产生耦合性 - [x] 去除用户概念,作为独立的支付网关使用,不与其他系统产生耦合性
- 2023-12-31: - 2023-12-31:
- [x] 支付关闭相关逻辑 - [x] 支付关闭相关逻辑
- [x] 各支付通道补充相关未实现的逻辑 - [x] 各支付通道补充相关未实现的逻辑
@@ -55,7 +60,7 @@
- 2024-01-13: - 2024-01-13:
- [x] (前端) 微信/支付宝支付通道配置 - [x] (前端) 微信/支付宝支付通道配置
- [x] 支付通道支持停用 - [x] 支付通道支持停用
- [x] 请求支付网关时区退款号以R开头, 用于与支付ID的区分 - [x] ~~请求支付网关时区退款号以R开头, 用于与支付ID的区分~~
- [x] (前端) 平台配置 - [x] (前端) 平台配置
- 2024-01-15: - 2024-01-15:
- [x] 优化支付相关订单和记录的查询条件 - [x] 优化支付相关订单和记录的查询条件

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.bootx.platform</groupId>
<artifactId>dax-pay</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>daxpay-single-demo</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Bootx Platform 基础API -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>service-baseapi</artifactId>
<version>${bootx-platform.version}</version>
</dependency>
<!-- 支付SDK 结算台 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-sdk</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -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 {
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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<PayOrderResult> simplePayCashier(@RequestBody CashierSimplePayParam param){
ValidationUtil.validateParam(param);
return Res.ok(cashierService.simplePayCashier(param));
}
@Operation(summary = "查询支付订单")
@GetMapping("/queryPayOrder")
public ResResult<Boolean> queryPayOrder(Long paymentId){
return Res.ok(cashierService.queryPayOrder(paymentId));
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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<PayOrderModel> execute = DaxPayKit.execute(simplePayParam);
// 判断是否支付成功
if (execute.getCode() != 0){
throw new BizException(execute.getMsg());
}
// 判断是否发起支付成功
PayOrderModel payOrderModel = execute.getData();
// 状态 支付中或支付完成返回
List<String> 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<QueryPayOrderModel> 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("订单状态不是支付中或支付完成,请检查");
}
}
}

View File

@@ -0,0 +1 @@
cn.bootx.platform.daxpay.demo.DaxPaySingleDemoApp

View File

@@ -51,23 +51,34 @@
</properties> </properties>
<dependencies> <dependencies>
<!-- 工具类 -->
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>${hutool.version}</version> <version>${hutool.version}</version>
</dependency> </dependency>
<!-- 日志框架 -->
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>${logback-classic.version}</version> <version>${logback-classic.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Lombok -->
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>${lombok.version}</version> <version>${lombok.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- 注解标识 -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<scope>provided</scope>
</dependency>
<!-- 测试类 -->
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>

View File

@@ -8,7 +8,7 @@ import lombok.Setter;
import lombok.ToString; import lombok.ToString;
/** /**
* 支付相应对象 * 发起支付后响应对象
* @author xxm * @author xxm
* @since 2024/2/2 * @since 2024/2/2
*/ */

View File

@@ -8,6 +8,7 @@ import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil; import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects; import java.util.Objects;
@@ -16,12 +17,14 @@ import java.util.Objects;
* @author xxm * @author xxm
* @since 2024/2/2 * @since 2024/2/2
*/ */
@Slf4j
@UtilityClass @UtilityClass
public class DaxPayKit { public class DaxPayKit {
private DaxPayConfig config; private DaxPayConfig config;
public void initConfig(DaxPayConfig config){ public void initConfig(DaxPayConfig config){
log.debug("DaxPayKit初始化...");
DaxPayKit.config = config; DaxPayKit.config = config;
} }
@@ -54,12 +57,14 @@ public class DaxPayKit {
} }
} }
String data = JSONUtil.toJsonStr(request); String data = JSONUtil.toJsonStr(request);
log.debug("请求参数:{}", data);
String path = config.getServiceUrl() + request.path(); String path = config.getServiceUrl() + request.path();
HttpResponse execute = HttpUtil.createPost(path) HttpResponse execute = HttpUtil.createPost(path)
.body(data, ContentType.JSON.getValue()) .body(data, ContentType.JSON.getValue())
.timeout(config.getReqTimeout()) .timeout(config.getReqTimeout())
.execute(); .execute();
String body = execute.body(); String body = execute.body();
log.debug("响应参数:{}", body);
return request.toModel(body); return request.toModel(body);
} }
} }

View File

@@ -5,5 +5,5 @@ package cn.bootx.platform.daxpay.sdk.param;
* @author xxm * @author xxm
* @since 2023/12/17 * @since 2023/12/17
*/ */
public interface ChannelParam { public interface ChannelParam extends SortMapParam {
} }

View File

@@ -0,0 +1,9 @@
package cn.bootx.platform.daxpay.sdk.param;
/**
* 标示字段可以转换为MAP进行排序
* @author xxm
* @since 2024/2/8
*/
public interface SortMapParam {
}

View File

@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class QueryPayParam extends DaxPayRequest<QueryPayOrderModel> { public class QueryPayOrderParam extends DaxPayRequest<QueryPayOrderModel> {
/** 支付ID */ /** 支付ID */
private Long paymentId; private Long paymentId;

View File

@@ -15,6 +15,7 @@ import cn.hutool.json.JSONUtil;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import org.jetbrains.annotations.NotNull;
/** /**
* 简单支付请求参数 * 简单支付请求参数
@@ -27,9 +28,11 @@ import lombok.ToString;
public class SimplePayParam extends DaxPayRequest<PayOrderModel> { public class SimplePayParam extends DaxPayRequest<PayOrderModel> {
/** 业务号 */ /** 业务号 */
@NotNull
private String businessNo; private String businessNo;
/** 支付标题 */ /** 支付标题 */
@NotNull
private String title; private String title;
/** 支付描述 */ /** 支付描述 */
@@ -42,17 +45,21 @@ public class SimplePayParam extends DaxPayRequest<PayOrderModel> {
private String quitUrl; private String quitUrl;
/** /**
* 支付通道
* @see PayChannelEnum#getCode() * @see PayChannelEnum#getCode()
*/ */
@NotNull
private String channel; private String channel;
/** /**
* 支付方式编码 * 支付方式编码
* @see PayWayEnum#getCode() * @see PayWayEnum#getCode()
*/ */
@NotNull
private String payWay; private String payWay;
/** 支付金额 */ /** 支付金额 */
@NotNull
private Integer amount; private Integer amount;
/** /**

View File

@@ -15,7 +15,7 @@ import lombok.EqualsAndHashCode;
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
public class QueryRefundParam extends DaxPayRequest<QueryRefundOrderModel> { public class QueryRefundOrderParam extends DaxPayRequest<QueryRefundOrderModel> {
/** 退款ID */ /** 退款ID */
private Long refundId; private Long refundId;

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.sdk.util;
import cn.bootx.platform.daxpay.sdk.param.SortMapParam; import cn.bootx.platform.daxpay.sdk.param.SortMapParam;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
@@ -94,9 +95,8 @@ public class PaySignUtil {
} }
} }
/** /**
* 把所有元素排序, 并拼接成字符, 用于签名 * 把所有元素排序, 并拼接成字符, 用于签名, 同时会过滤掉 " 和 \ 字符
*/ */
public static String createLinkString(Map<String, String> params) { public static String createLinkString(Map<String, String> params) {
String connStr = "&"; String connStr = "&";
@@ -118,10 +118,12 @@ public class PaySignUtil {
.append(connStr); .append(connStr);
} }
} }
return content.toString(); String s = content.toString();
s = StrUtil.replace(s,"\\","");
s = StrUtil.replace(s,"\"","");
return s;
} }
/** /**
* 生成16进制 MD5 字符串 * 生成16进制 MD5 字符串
* *

View File

@@ -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.model.pay.QueryPayOrderModel;
import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig; import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig;
import cn.bootx.platform.daxpay.sdk.net.DaxPayKit; 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 cn.bootx.platform.daxpay.sdk.response.DaxPayResult;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -27,7 +27,7 @@ public class QueryPayOrderTest {
@Test @Test
public void testPay() { public void testPay() {
QueryPayParam param = new QueryPayParam(); QueryPayOrderParam param = new QueryPayOrderParam();
param.setBusinessNo("2"); param.setBusinessNo("2");

View File

@@ -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.model.refund.QueryRefundOrderModel;
import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig; import cn.bootx.platform.daxpay.sdk.net.DaxPayConfig;
import cn.bootx.platform.daxpay.sdk.net.DaxPayKit; 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 cn.bootx.platform.daxpay.sdk.response.DaxPayResult;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -27,7 +27,7 @@ public class QueryRefundOrderTest {
@Test @Test
public void testPay() { public void testPay() {
QueryRefundParam param = new QueryRefundParam(); QueryRefundOrderParam param = new QueryRefundOrderParam();
param.setRefundId(1755263825769361408L); param.setRefundId(1755263825769361408L);

View File

@@ -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.PayChannelEnum;
import cn.bootx.platform.daxpay.sdk.code.PayWayEnum; 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.PayChannelParam;
import cn.bootx.platform.daxpay.sdk.param.pay.PayParam; import cn.bootx.platform.daxpay.sdk.param.pay.PayParam;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -43,7 +44,42 @@ public class PayParamSignTest {
Map<String, String> map = PaySignUtil.toMap(param); Map<String, String> map = PaySignUtil.toMap(param);
log.info("转换为有序MAP后的内容: {}",map); log.info("转换为有序MAP后的内容: {}",map);
String data = PaySignUtil.createLinkString(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<PayChannelParam> payChannels = Collections.singletonList(payChannelParam);
param.setPayChannels(payChannels);
Map<String, String> map = PaySignUtil.toMap(param);
log.info("转换为有序MAP后的内容: {}",map);
String data = PaySignUtil.createLinkString(map).replaceAll("\\\"","").replaceAll("\"","");
log.info("将MAP拼接字符串, 并过滤掉特殊字符: {}",data);
String sign = "123456"; String sign = "123456";
data += "&sign="+sign; data += "&sign="+sign;
data = data.toUpperCase(); data = data.toUpperCase();

View File

@@ -16,8 +16,9 @@
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-service</artifactId> <artifactId>daxpay-single-service</artifactId>
<version>${dax.version}</version> <version>${daxpay.version}</version>
</dependency> </dependency>
<!-- 安全认证 --> <!-- 安全认证 -->
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>

View File

@@ -11,6 +11,7 @@ import lombok.Data;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.Map;
/** /**
* 同意下单支付方式参数 * 同意下单支付方式参数
@@ -48,5 +49,5 @@ public class PayChannelParam {
* @see WalletPayParam * @see WalletPayParam
*/ */
@Schema(description = "附加支付参数") @Schema(description = "附加支付参数")
private String channelParam; private Map<String,Object> channelParam;
} }

View File

@@ -16,6 +16,7 @@ import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Map;
/** /**
* 简单下单参数 * 简单下单参数
@@ -71,7 +72,7 @@ public class SimplePayParam extends PayCommonParam{
* @see WalletPayParam * @see WalletPayParam
*/ */
@Schema(description = "附加通道支付参数") @Schema(description = "附加通道支付参数")
private String channelParam; private Map<String, Object> channelParam;
} }

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.util;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil; import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm; import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
@@ -99,7 +100,7 @@ public class PaySignUtil {
/** /**
* 把所有元素排序, 并拼接成字符, 用于签名 * 把所有元素排序, 并拼接成字符, 用于签名, 同时会过滤掉 " 和 \ 字符
*/ */
public static String createLinkString(Map<String, String> params) { public static String createLinkString(Map<String, String> params) {
String connStr = "&"; String connStr = "&";
@@ -121,7 +122,10 @@ public class PaySignUtil {
.append(connStr); .append(connStr);
} }
} }
return content.toString(); String s = content.toString();
s = StrUtil.replace(s,"\\","");
s = StrUtil.replace(s,"\"","");
return s;
} }

View File

@@ -15,7 +15,7 @@
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-service</artifactId> <artifactId>daxpay-single-service</artifactId>
<version>${dax.version}</version> <version>${daxpay.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@@ -70,7 +70,7 @@
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-core</artifactId> <artifactId>daxpay-single-core</artifactId>
<version>${dax.version}</version> <version>${daxpay.version}</version>
</dependency> </dependency>
<!-- 支付宝支付 --> <!-- 支付宝支付 -->
@@ -109,6 +109,7 @@
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- 分布式锁 -->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId> <artifactId>lock4j-redis-template-spring-boot-starter</artifactId>

View File

@@ -1,5 +1,8 @@
package cn.bootx.platform.daxpay.service.core.channel.voucher.service; 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.code.VoucherCode;
import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherLogManager; import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherLogManager;
import cn.bootx.platform.daxpay.service.core.channel.voucher.dao.VoucherManager; 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.VoucherPayOrder;
import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherRecord; 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.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException; import cn.hutool.core.bean.BeanUtil;
import cn.bootx.platform.daxpay.param.channel.VoucherPayParam; import cn.hutool.core.collection.CollUtil;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONException; import cn.hutool.json.JSONException;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Map;
/** /**
* 储值卡支付 * 储值卡支付
* *
@@ -46,9 +49,10 @@ public class VoucherPayService {
VoucherPayParam voucherPayParam; VoucherPayParam voucherPayParam;
try { try {
// 储值卡参数验证 // 储值卡参数验证
String extraParamsJson = payChannelParam.getChannelParam();
if (StrUtil.isNotBlank(extraParamsJson)) { Map<String, Object> channelParam = payChannelParam.getChannelParam();
voucherPayParam = JSONUtil.toBean(extraParamsJson, VoucherPayParam.class); if (CollUtil.isNotEmpty(channelParam)) {
voucherPayParam = BeanUtil.toBean(channelParam, VoucherPayParam.class);
} else { } else {
throw new PayFailureException("储值卡支付参数错误"); throw new PayFailureException("储值卡支付参数错误");
} }

View File

@@ -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.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum; 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.AsyncPayLocal;
import cn.bootx.platform.daxpay.service.common.context.NoticeLocal; import cn.bootx.platform.daxpay.service.common.context.NoticeLocal;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal; 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.PayChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderExtra; import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.entity.RefundableInfo; import cn.hutool.core.collection.CollUtil;
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.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Collections; import java.util.Map;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
/** /**
* 支付对象构建器 * 支付对象构建器
@@ -91,28 +89,19 @@ public class PayBuilder {
* 构建订单关联通道信息 * 构建订单关联通道信息
*/ */
public PayChannelOrder buildPayChannelOrder(PayChannelParam channelParam) { public PayChannelOrder buildPayChannelOrder(PayChannelParam channelParam) {
return new PayChannelOrder() PayChannelOrder payChannelOrder = new PayChannelOrder()
.setAsync(PayChannelEnum.ASYNC_TYPE_CODE.contains(channelParam.getChannel())) .setAsync(PayChannelEnum.ASYNC_TYPE_CODE.contains(channelParam.getChannel()))
.setChannel(channelParam.getChannel()) .setChannel(channelParam.getChannel())
.setPayWay(channelParam.getWay()) .setPayWay(channelParam.getWay())
.setAmount(channelParam.getAmount()) .setAmount(channelParam.getAmount())
.setRefundableBalance(channelParam.getAmount()) .setRefundableBalance(channelParam.getAmount());
.setChannelExtra(channelParam.getChannelParam());
}
/** Map<String, Object> cp = channelParam.getChannelParam();
* 构建支付订单的可退款信息列表 if (CollUtil.isNotEmpty(cp)){
*/ payChannelOrder.setChannelExtra(JSONUtil.toJsonStr(cp));
@Deprecated
private List<RefundableInfo> buildRefundableInfo(List<PayChannelParam> payChannelParamList) {
if (CollectionUtil.isEmpty(payChannelParamList)) {
return Collections.emptyList();
} }
return payChannelParamList.stream()
.map(o-> new RefundableInfo() return payChannelOrder;
.setChannel(o.getChannel())
.setAmount(o.getAmount()))
.collect(Collectors.toList());
} }

View File

@@ -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.platform.daxpay.service.dto.order.pay.PayChannelOrderDto;
import cn.bootx.table.modify.annotation.DbColumn; import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable; 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 com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -66,6 +68,7 @@ public class PayChannelOrder extends MpCreateEntity implements EntityBaseFunctio
* @see WalletPayParam * @see WalletPayParam
*/ */
@DbColumn(comment = "附加支付参数") @DbColumn(comment = "附加支付参数")
@TableField(updateStrategy = FieldStrategy.NEVER)
private String channelExtra; private String channelExtra;
/** /**

View File

@@ -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.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder; import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder;
import cn.bootx.platform.daxpay.service.dto.order.pay.PayChannelOrderDto; import cn.bootx.platform.daxpay.service.dto.order.pay.PayChannelOrderDto;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -19,6 +20,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@@ -57,6 +59,12 @@ public class PayChannelOrderService {
PayStatusEnum payStatus = asyncPayInfo.isPayComplete() ? PayStatusEnum.SUCCESS : PayStatusEnum.PROGRESS; PayStatusEnum payStatus = asyncPayInfo.isPayComplete() ? PayStatusEnum.SUCCESS : PayStatusEnum.PROGRESS;
// 判断新发起的 // 判断新发起的
Optional<PayChannelOrder> payOrderChannelOpt = channelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payChannelParam.getChannel()); Optional<PayChannelOrder> payOrderChannelOpt = channelOrderManager.findByPaymentIdAndChannel(payOrder.getId(), payChannelParam.getChannel());
// 扩展信息处理
Map<String, Object> channelParam = payChannelParam.getChannelParam();
String channelParamStr = null;
if (Objects.nonNull(channelParam)){
channelParamStr = JSONUtil.toJsonStr(channelParam);
}
if (!payOrderChannelOpt.isPresent()){ if (!payOrderChannelOpt.isPresent()){
PayChannelOrder payChannelOrder = new PayChannelOrder(); PayChannelOrder payChannelOrder = new PayChannelOrder();
// 替换原有的的支付通道信息 // 替换原有的的支付通道信息
@@ -68,7 +76,7 @@ public class PayChannelOrderService {
.setAmount(payChannelParam.getAmount()) .setAmount(payChannelParam.getAmount())
.setRefundableBalance(payChannelParam.getAmount()) .setRefundableBalance(payChannelParam.getAmount())
.setPayTime(LocalDateTime.now()) .setPayTime(LocalDateTime.now())
.setChannelExtra(payChannelParam.getChannelParam()) .setChannelExtra(channelParamStr)
.setStatus(payStatus.getCode()); .setStatus(payStatus.getCode());
channelOrderManager.deleteByPaymentIdAndAsync(payOrder.getId()); channelOrderManager.deleteByPaymentIdAndAsync(payOrder.getId());
channelOrderManager.save(payChannelOrder); channelOrderManager.save(payChannelOrder);
@@ -77,7 +85,7 @@ public class PayChannelOrderService {
payOrderChannelOpt.get() payOrderChannelOpt.get()
.setPayWay(payChannelParam.getWay()) .setPayWay(payChannelParam.getWay())
.setPayTime(LocalDateTime.now()) .setPayTime(LocalDateTime.now())
.setChannelExtra(payChannelParam.getChannelParam()) .setChannelExtra(channelParamStr)
.setStatus(payStatus.getCode()); .setStatus(payStatus.getCode());
channelOrderManager.updateById(payOrderChannelOpt.get()); channelOrderManager.updateById(payOrderChannelOpt.get());
} }

View File

@@ -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.channel.alipay.service.AliPayService;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayChannelOrderService; 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.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.JSONException;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Map;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/** /**
@@ -52,9 +54,9 @@ public class AliPayStrategy extends AbsPayStrategy {
public void doBeforePayHandler() { public void doBeforePayHandler() {
try { try {
// 支付宝参数验证 // 支付宝参数验证
String extraParamsJson = this.getPayChannelParam().getChannelParam(); Map<String, Object> channelParam = this.getPayChannelParam().getChannelParam();
if (StrUtil.isNotBlank(extraParamsJson)) { if (CollUtil.isNotEmpty(channelParam)) {
this.aliPayParam = JSONUtil.toBean(extraParamsJson, AliPayParam.class); this.aliPayParam = BeanUtil.toBean(channelParam, AliPayParam.class);
} }
else { else {
this.aliPayParam = new AliPayParam(); this.aliPayParam = new AliPayParam();

View File

@@ -1,23 +1,24 @@
package cn.bootx.platform.daxpay.service.core.payment.pay.strategy; package cn.bootx.platform.daxpay.service.core.payment.pay.strategy;
import cn.bootx.platform.daxpay.code.PayChannelEnum; 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.pay.PayFailureException;
import cn.bootx.platform.daxpay.exception.waller.WalletBannedException; import cn.bootx.platform.daxpay.exception.waller.WalletBannedException;
import cn.bootx.platform.daxpay.exception.waller.WalletLackOfBalanceException; 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.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.service.param.channel.wallet.WalletPayParam; 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.JSONException;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
@@ -54,9 +55,9 @@ public class WalletPayStrategy extends AbsPayStrategy {
WalletPayParam walletPayParam = new WalletPayParam(); WalletPayParam walletPayParam = new WalletPayParam();
try { try {
// 钱包参数验证 // 钱包参数验证
String extraParamsJson = this.getPayChannelParam().getChannelParam(); Map<String, Object> channelParam = this.getPayChannelParam().getChannelParam();
if (StrUtil.isNotBlank(extraParamsJson)) { if (CollUtil.isNotEmpty(channelParam)) {
walletPayParam = JSONUtil.toBean(extraParamsJson, WalletPayParam.class); walletPayParam = BeanUtil.toBean(channelParam, WalletPayParam.class);
} }
} catch (JSONException e) { } catch (JSONException e) {
throw new PayFailureException("支付参数错误"); throw new PayFailureException("支付参数错误");

View File

@@ -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.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException; 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.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig; import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayConfigService; 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.core.order.pay.service.PayChannelOrderService;
import cn.bootx.platform.daxpay.service.func.AbsPayStrategy; import cn.bootx.platform.daxpay.service.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayParam; import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayParam;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONException; import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Map;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
/** /**
@@ -53,18 +53,12 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/ */
@Override @Override
public void doBeforePayHandler() { public void doBeforePayHandler() {
try { // 微信参数验证
// 微信参数验证 Map<String, Object> channelParam = this.getPayChannelParam().getChannelParam();
String extraParamsJson = this.getPayChannelParam().getChannelParam(); if (CollUtil.isNotEmpty(channelParam)) {
if (StrUtil.isNotBlank(extraParamsJson)) { this.weChatPayParam = BeanUtil.toBean(channelParam, WeChatPayParam.class);
this.weChatPayParam = JSONUtil.toBean(extraParamsJson, WeChatPayParam.class); } else {
} this.weChatPayParam = new WeChatPayParam();
else {
this.weChatPayParam = new WeChatPayParam();
}
}
catch (JSONException e) {
throw new PayFailureException("支付参数错误");
} }
// 检查金额 // 检查金额

View File

@@ -54,6 +54,10 @@ public class PayExpiredTimeTask {
paySyncParam.setPaymentId(paymentId); paySyncParam.setPaymentId(paymentId);
paySyncService.sync(paySyncParam); paySyncService.sync(paySyncParam);
} catch (Exception e) { } catch (Exception e) {
// 如果是未查询到取消支付订单, 则删除这个任务
if (Objects.equals("未查询到支付订单", e.getMessage())){
repository.removeKeys(expiredKey);
}
log.error("超时取消任务 异常", e); log.error("超时取消任务 异常", e);
} finally { } finally {
lockTemplate.releaseLock(lock); lockTemplate.releaseLock(lock);

View File

@@ -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.AliPayParam;
import cn.bootx.platform.daxpay.param.channel.WeChatPayParam; import cn.bootx.platform.daxpay.param.channel.WeChatPayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam; import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.param.pay.PayParam; import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.util.PaySignUtil; import cn.bootx.platform.daxpay.util.PaySignUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 支付签名服务 * 支付签名服务
@@ -19,7 +20,7 @@ import java.util.List;
* @since 2023/12/24 * @since 2023/12/24
*/ */
@Slf4j @Slf4j
class PaymentSignServiceTest { class PayParamSignTest {
@Test @Test
void toMap() { void toMap() {
@@ -40,7 +41,7 @@ class PaymentSignServiceTest {
p1.setWay("wx_app"); p1.setWay("wx_app");
AliPayParam aliPayParam = new AliPayParam(); AliPayParam aliPayParam = new AliPayParam();
aliPayParam.setAuthCode("6688"); aliPayParam.setAuthCode("6688");
p1.setChannelParam(JSONUtil.toJsonStr(aliPayParam)); p1.setChannelParam(BeanUtil.beanToMap(aliPayParam,false,true));
PayChannelParam p2 = new PayChannelParam(); PayChannelParam p2 = new PayChannelParam();
p2.setAmount(100); p2.setAmount(100);
@@ -49,17 +50,21 @@ class PaymentSignServiceTest {
WeChatPayParam weChatPayParam = new WeChatPayParam(); WeChatPayParam weChatPayParam = new WeChatPayParam();
weChatPayParam.setOpenId("w2qsz2xawe3gbhyyff28fs01fd"); weChatPayParam.setOpenId("w2qsz2xawe3gbhyyff28fs01fd");
weChatPayParam.setAuthCode("8866"); weChatPayParam.setAuthCode("8866");
p2.setChannelParam(JSONUtil.toJsonStr(weChatPayParam)); p2.setChannelParam(BeanUtil.beanToMap(aliPayParam,false,true));
List<PayChannelParam> payWays = Arrays.asList(p1, p2); List<PayChannelParam> payWays = Arrays.asList(p1, p2);
payParam.setPayChannels(payWays); 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<String, String> 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));
} }
} }

View File

@@ -16,13 +16,13 @@
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-gateway</artifactId> <artifactId>daxpay-single-gateway</artifactId>
<version>${dax.version}</version> <version>${daxpay.version}</version>
</dependency> </dependency>
<!-- 管理平台 --> <!-- 管理平台 -->
<dependency> <dependency>
<groupId>cn.bootx.platform</groupId> <groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-admin</artifactId> <artifactId>daxpay-single-admin</artifactId>
<version>${dax.version}</version> <version>${daxpay.version}</version>
</dependency> </dependency>
<!-- 定时任务 --> <!-- 定时任务 -->
<dependency> <dependency>
@@ -35,7 +35,11 @@
<artifactId>common-starter-code-gen</artifactId> <artifactId>common-starter-code-gen</artifactId>
</dependency> </dependency>
<!-- 支付演示模块 --> <!-- 支付演示模块 -->
<dependency>
<groupId>cn.bootx.platform</groupId>
<artifactId>daxpay-single-demo</artifactId>
<version>${daxpay.version}</version>
</dependency>
</dependencies> </dependencies>

View File

@@ -72,6 +72,7 @@ bootx:
base-packages: base-packages:
"[支付网关API]": cn.bootx.platform.daxpay.gateway "[支付网关API]": cn.bootx.platform.daxpay.gateway
"[支付管理接口]": cn.bootx.platform.daxpay.admin "[支付管理接口]": cn.bootx.platform.daxpay.admin
"[支付演示模块]": cn.bootx.platform.daxpay.demo
"[BootxPlatform接口]": "[BootxPlatform接口]":
- cn.bootx.platform.common - cn.bootx.platform.common
- cn.bootx.platform.starter - cn.bootx.platform.starter
@@ -150,3 +151,11 @@ dromara:
enableStorage: true enableStorage: true
# 文件存储路径 # 文件存储路径
storage-path: D:/data/files/ storage-path: D:/data/files/
# 支付系统配置
dax-pay:
# 演示模块
demo:
# 网关地址
server-url: http://localhost:9000
# 签名秘钥
sign-secret: 123456

View File

@@ -20,6 +20,7 @@
<modules> <modules>
<module>daxpay-single</module> <module>daxpay-single</module>
<module>daxpay-single-sdk</module> <module>daxpay-single-sdk</module>
<module>daxpay-single-demo</module>
</modules> </modules>
<properties> <properties>
@@ -29,7 +30,7 @@
<!-- 二方库版本 --> <!-- 二方库版本 -->
<bootx-platform.version>1.3.6.1</bootx-platform.version> <bootx-platform.version>1.3.6.1</bootx-platform.version>
<dax.version>2.0.0</dax.version> <daxpay.version>2.0.0</daxpay.version>
<!-- 三方库 --> <!-- 三方库 -->
<slf4j.version>1.7.30</slf4j.version> <slf4j.version>1.7.30</slf4j.version>
<redisson.version>3.16.8</redisson.version> <redisson.version>3.16.8</redisson.version>