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

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

View File

@@ -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<String, Object> channelParam = payChannelParam.getChannelParam();
if (CollUtil.isNotEmpty(channelParam)) {
voucherPayParam = BeanUtil.toBean(channelParam, VoucherPayParam.class);
} else {
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.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<RefundableInfo> buildRefundableInfo(List<PayChannelParam> payChannelParamList) {
if (CollectionUtil.isEmpty(payChannelParamList)) {
return Collections.emptyList();
Map<String, Object> 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;
}

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.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;
/**

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.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<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()){
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());
}

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.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<String, Object> channelParam = this.getPayChannelParam().getChannelParam();
if (CollUtil.isNotEmpty(channelParam)) {
this.aliPayParam = BeanUtil.toBean(channelParam, AliPayParam.class);
}
else {
this.aliPayParam = new AliPayParam();

View File

@@ -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<String, Object> channelParam = this.getPayChannelParam().getChannelParam();
if (CollUtil.isNotEmpty(channelParam)) {
walletPayParam = BeanUtil.toBean(channelParam, WalletPayParam.class);
}
} catch (JSONException e) {
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.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<String, Object> channelParam = this.getPayChannelParam().getChannelParam();
if (CollUtil.isNotEmpty(channelParam)) {
this.weChatPayParam = BeanUtil.toBean(channelParam, WeChatPayParam.class);
} else {
this.weChatPayParam = new WeChatPayParam();
}
// 检查金额

View File

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

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.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<PayChannelParam> 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<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));
}
}