feat 支付回调通知和签名相关方法抽取

This commit is contained in:
nws
2024-01-07 23:47:24 +08:00
parent 500807062a
commit 19296ffb15
50 changed files with 716 additions and 285 deletions

View File

@@ -35,17 +35,22 @@
- [x] 订单取消/修复/取消/同步等操作添加分布式锁, 防止出现重复操作
- [x] 增加支付修复记录
- 2024-01-07:
- [x] 抽取签名工具类
- [ ] 增加消息通知机制(通知客户端)
- [ ] (前端) 更新代码为platform最新版本
- [ ] (前端) 平台配置
- [ ] (前端) 接口配置
- **任务池**
- [ ] 退款状态同步逻辑
- [ ] 退款回调的处理
- [ ] 支付状态同步处理考虑退款情况
- **任务池**
- 支付状态同步处理退款情况
- 支付配置支持数据库配置和配置文件配置
- 增加回调机制(通知客户端)
- 增加消息通知机制(通知客户端)
- 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况
- 支付平台全局性配置
- 微信消息通知相关配置
- 钉钉消息通知配置
- [ ] 支付配置支持数据库配置和配置文件配置
- [ ] 增加回调机制(通知客户端)
- [ ] 增加消息通知机制(通知客户端)
- [ ] 新增支付单预警功能, 处理支付单与网关状态不一致且无法自动修复的情况
- [ ] 支付平台全局性配置
- [ ] 微信消息通知相关配置
- [ ] 钉钉消息通知配置
- 支付宝接口升级为V3接口
- 微信支付接口更新为V3接口
-

View File

@@ -0,0 +1,12 @@
package cn.bootx.platform.daxpay.code;
/**
* 支付系统相关常量
* @author xxm
* @since 2024/1/7
*/
public interface DaxPayCode {
/** 签名字段 */
String FIELD_SIGN = "sign";
}

View File

@@ -1,9 +1,9 @@
package cn.bootx.platform.daxpay.param;
/**
* 通道支付参数标识
* 通道支付参数标识接口
* @author xxm
* @since 2023/12/17
*/
public interface ChannelParam {
public interface IChannelParam {
}

View File

@@ -1,6 +1,6 @@
package cn.bootx.platform.daxpay.param.channel;
import cn.bootx.platform.daxpay.param.ChannelParam;
import cn.bootx.platform.daxpay.param.IChannelParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -10,7 +10,7 @@ import lombok.Data;
*/
@Data
@Schema(title = "支付宝支付参数")
public class AliPayParam implements ChannelParam {
public class AliPayParam implements IChannelParam {
@Schema(description = "授权码(主动扫描用户的付款码)")
private String authCode;

View File

@@ -1,6 +1,6 @@
package cn.bootx.platform.daxpay.param.channel;
import cn.bootx.platform.daxpay.param.ChannelParam;
import cn.bootx.platform.daxpay.param.IChannelParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -12,7 +12,7 @@ import lombok.Data;
*/
@Data
@Schema(title = "储值卡支付参数")
public class VoucherPayParam implements ChannelParam {
public class VoucherPayParam implements IChannelParam {
@Schema(description = "储值卡号")
private String cardNo;

View File

@@ -1,6 +1,6 @@
package cn.bootx.platform.daxpay.param.channel;
import cn.bootx.platform.daxpay.param.ChannelParam;
import cn.bootx.platform.daxpay.param.IChannelParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -12,7 +12,7 @@ import lombok.Data;
*/
@Data
@Schema(title = "钱包支付参数")
public class WalletPayParam implements ChannelParam {
public class WalletPayParam implements IChannelParam {
@Schema(description = "钱包ID")
private Long walletId;

View File

@@ -1,6 +1,6 @@
package cn.bootx.platform.daxpay.param.channel;
import cn.bootx.platform.daxpay.param.ChannelParam;
import cn.bootx.platform.daxpay.param.IChannelParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -10,7 +10,7 @@ import lombok.Data;
*/
@Data
@Schema(title = "微信支付参数")
public class WeChatPayParam implements ChannelParam {
public class WeChatPayParam implements IChannelParam {
@Schema(description = "微信openId")
private String openId;

View File

@@ -19,8 +19,8 @@ import javax.validation.constraints.NotNull;
* @since 2020/12/8
*/
@Data
@Schema(title = "支付方式参数")
public class PayWayParam {
@Schema(title = "支付通道参数")
public class PayChannelParam {
/**
* @see PayChannelEnum#getCode()

View File

@@ -1,7 +1,6 @@
package cn.bootx.platform.daxpay.param.pay;
import cn.bootx.platform.daxpay.serializer.TimestampToLocalDateTimeDeserializer;
import cn.bootx.platform.daxpay.util.PayUtil;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -9,7 +8,6 @@ import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 支付公共参数
@@ -68,8 +66,5 @@ public abstract class PayCommonParam {
* 4. 嵌套对象转换成先转换成MAP再序列化为字符串
* 5. 支持两层嵌套, 更多层级嵌套未测试, 可能会导致不可预知的问题
*/
public Map<String,String> toMap(){
return PayUtil.toMap(this);
}
}

View File

@@ -41,8 +41,8 @@ public class PayParam extends PayCommonParam{
@Schema(description = "用户付款中途退出返回商户网站的地址(部分支付场景中可用)")
private String quitUrl;
@Schema(description = "支付方式信息参数")
@NotNull(message = "支付方式信息参数不可为空")
@Schema(description = "支付通道信息参数")
@NotNull(message = "支付通道信息参数不可为空")
@Valid
private List<PayWayParam> payWays;
private List<PayChannelParam> payChannels;
}

View File

@@ -16,7 +16,4 @@ public class PayCommonResult {
@Schema(description = "请求ID")
private String reqId = MDC.get(CommonCode.TRACE_ID);
@Schema(description = "商户扩展参数,回调时会原样返回")
private String attach;
}

View File

@@ -4,15 +4,17 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 统一下单响应参数
* @author xxm
* @since 2023/12/17
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(title = "统一下单响应参数")
public class PayResult {
public class PayResult extends PayCommonResult{
@Schema(description = "支付ID")
private Long paymentId;

View File

@@ -2,15 +2,17 @@ package cn.bootx.platform.daxpay.result.pay;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 退款响应参数
* @author xxm
* @since 2023/12/18
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Schema(title = "退款响应参数")
public class RefundResult {
public class RefundResult extends PayCommonResult{
@Schema(description = "退款ID")
private Long refundId;

View File

@@ -0,0 +1,195 @@
package cn.bootx.platform.daxpay.util;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.HmacAlgorithm;
import cn.hutool.json.JSONUtil;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import static cn.bootx.platform.daxpay.code.DaxPayCode.FIELD_SIGN;
/**
* 如果需要进行签名,
* 1. 参数名ASCII码从小到大排序字典序
* 2. 如果参数的值为空不参与签名
* 3. 参数名不区分大小写
* 4. 嵌套对象转换成先转换成MAP再序列化为字符串
* 5. 支持两层嵌套, 更多层级嵌套未测试, 可能会导致不可预知的问题
*/
@UtilityClass
public class PaySignUtil {
/**
* 将参数转换为map对象. 使用ChatGPT生成
* 1. 参数名ASCII码从小到大排序字典序
* 2. 如果参数的值为空不参与签名;
* 3. 参数名不区分大小写;
*/
public Map<String, String> toMap(Object object) {
Map<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
toMap(object, map);
return map;
}
/**
* 将参数转换为map对象. 使用ChatGPT生成, 仅局限于对请求支付相关参数进行签名
*/
@SneakyThrows
private void toMap(Object object, Map<String, String> map) {
Class<?> clazz = object.getClass();
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
Object fieldValue = field.get(object);
if (fieldValue != null) {
// 基础类型及包装类 和 字符串类型
if (ClassUtil.isBasicType(field.getType())|| field.getType().equals(String.class)) {
String fieldValueString = String.valueOf(fieldValue);
map.put(fieldName, fieldValueString);
}
// java8时间类型 转为时间戳
else if (field.getType().equals(LocalDateTime.class)) {
LocalDateTime localDateTime = (LocalDateTime) fieldValue;
long timestamp = LocalDateTimeUtil.timestamp(localDateTime);
map.put(fieldName, String.valueOf(timestamp));
}
// 集合类型
else if (Collection.class.isAssignableFrom(field.getType())) {
Collection<?> collection = (Collection<?>) fieldValue;
if (!collection.isEmpty()) {
List<Map<String, String>> maps = collection.stream()
.filter(Objects::nonNull)
.map(item -> {
Map<String, String> nestedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
toMap(item, nestedMap);
return nestedMap;
})
.collect(Collectors.toList());
map.put(fieldName, JSONUtil.toJsonStr(maps));
}
// 其他类型
} else {
Map<String, String> nestedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
toMap(fieldValue, nestedMap);
String nestedJson = JSONUtil.toJsonStr(map);
map.put(fieldName, nestedJson);
}
}
}
clazz = clazz.getSuperclass();
}
}
/**
* 把所有元素排序, 并拼接成字符, 用于签名
*/
public static String createLinkString(Map<String, String> params) {
String connStr = "&";
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
StringBuilder content = new StringBuilder();
for (int i = 0; i < keys.size(); i++) {
String key = keys.get(i);
String value = params.get(key);
// 拼接时,不包括最后一个&字符
if (i == keys.size() - 1) {
content.append(key)
.append("=")
.append(value);
} else {
content.append(key)
.append("=")
.append(value)
.append(connStr);
}
}
return content.toString();
}
/**
* 生成16进制 MD5 字符串
*
* @param data 数据
* @return MD5 字符串
*/
public String md5(String data) {
return SecureUtil.md5(data);
}
/**
* 生成16进制的 sha256 字符串
*
* @param data 数据
* @param signKey 密钥
* @return sha256 字符串
*/
public String hmacSha256(String data, String signKey) {
return SecureUtil.hmac(HmacAlgorithm.HmacSHA256, signKey).digestHex(data);
}
/**
* 生成待签名字符串
* @param object 待签名对象
* @param signKey 签名Key
* @return 待签名字符串
*/
public String signString(Object object, String signKey){
// 签名
Map<String, String> map = toMap(object);
// 生成签名前先去除sign参数
map.remove(FIELD_SIGN);
// 创建待签名字符串
String data = createLinkString(map);
// 将签名key追加到字符串最后
return "&key=" + signKey;
}
/**
* md5方式进行签名
*
* @return 签名值
*/
public String md5Sign(Object object, String signKey){
String data = signString(object, signKey);
return md5(data);
}
/**
* hmacSha256方式进行签名
*
* @return 签名值
*/
public String hmacSha256Sign(Object object, String signKey){
String data = signString(object, signKey);
return hmacSha256(data, signKey);
}
/**
* MD5签名验证
*/
public boolean verifyMd5Sign(Object object, String signKey, String sign){
String md5Sign = md5Sign(object, signKey);
return md5Sign.equals(sign);
}
/**
* hmacSha256签名验证
*/
public boolean verifyHmacSha256Sign(Object object, String signKey, String sign){
String hmacSha256Sign = hmacSha256Sign(object, signKey);
return hmacSha256Sign.equals(sign);
}
}

View File

@@ -4,100 +4,31 @@ import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
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.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.json.JSONUtil;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.List;
/**
*
* 支付工具类
* @author xxm
* @since 2023/12/24
*/
@UtilityClass
public class PayUtil {
/**
* 将参数转换为map对象. 使用ChatGPT生成
* 1. 参数名ASCII码从小到大排序字典序
* 2. 如果参数的值为空不参与签名;
* 3. 参数名不区分大小写;
*/
public Map<String, String> toMap(Object object) {
Map<String, String> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
toMap(object, map);
return map;
}
/**
* 将参数转换为map对象. 使用ChatGPT生成, 仅局限于对请求支付相关参数进行签名
*/
@SneakyThrows
private void toMap(Object object, Map<String, String> map) {
Class<?> clazz = object.getClass();
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String fieldName = field.getName();
Object fieldValue = field.get(object);
if (fieldValue != null) {
// 基础类型及包装类 和 字符串类型
if (ClassUtil.isBasicType(field.getType())|| field.getType().equals(String.class)) {
String fieldValueString = String.valueOf(fieldValue);
map.put(fieldName, fieldValueString);
}
// java8时间类型 转为时间戳
else if (field.getType().equals(LocalDateTime.class)) {
LocalDateTime localDateTime = (LocalDateTime) fieldValue;
long timestamp = LocalDateTimeUtil.timestamp(localDateTime);
map.put(fieldName, String.valueOf(timestamp));
}
// 集合类型
else if (Collection.class.isAssignableFrom(field.getType())) {
Collection<?> collection = (Collection<?>) fieldValue;
if (!collection.isEmpty()) {
List<Map<String, String>> maps = collection.stream()
.filter(Objects::nonNull)
.map(item -> {
Map<String, String> nestedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
toMap(item, nestedMap);
return nestedMap;
})
.collect(Collectors.toList());
map.put(fieldName, JSONUtil.toJsonStr(maps));
}
// 其他类型
} else {
Map<String, String> nestedMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
toMap(fieldValue, nestedMap);
String nestedJson = JSONUtil.toJsonStr(map);
map.put(fieldName, nestedJson);
}
}
}
clazz = clazz.getSuperclass();
}
}
/**
* 检查异步支付方式
*/
public void validationAsyncPay(PayParam payParam) {
// 组合支付时只允许有一个异步支付方式
List<PayWayParam> payModeList = payParam.getPayWays();
List<PayChannelParam> payModeList = payParam.getPayChannels();
long asyncPayCount = payModeList.stream()
.map(PayWayParam::getChannel)
.map(PayChannelParam::getChannel)
.map(PayChannelEnum::findByCode)
.filter(PayChannelEnum.ASYNC_TYPE::contains)
.count();
@@ -110,10 +41,10 @@ public class PayUtil {
/**
* 检查支付金额
*/
public void validationAmount(List<PayWayParam> payModeList) {
for (PayWayParam payWayParam : payModeList) {
public void validationAmount(List<PayChannelParam> payModeList) {
for (PayChannelParam payChannelParam : payModeList) {
// 支付金额小于等于零
if (payWayParam.getAmount() < 0) {
if (payChannelParam.getAmount() < 0) {
throw new PayAmountAbnormalException();
}
}
@@ -150,9 +81,9 @@ public class PayUtil {
/**
* 判断是否有异步支付
*/
public boolean isNotSync(List<PayWayParam> payWayParams) {
return payWayParams.stream()
.map(PayWayParam::getChannel)
public boolean isNotSync(List<PayChannelParam> payChannelParams) {
return payChannelParams.stream()
.map(PayChannelParam::getChannel)
.noneMatch(PayChannelEnum.ASYNC_TYPE_CODE::contains);
}
}

View File

@@ -1,9 +1,13 @@
package cn.bootx.platform.daxpay.gateway.controller;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.rest.Res;
import cn.bootx.platform.common.core.rest.ResResult;
import cn.bootx.platform.daxpay.service.core.timeout.task.PayExpiredTimeTask;
import cn.bootx.platform.daxpay.service.core.timeout.task.PayWaitOrderSyncTask;
import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@@ -11,6 +15,8 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
/**
* 测试
* @author xxm
@@ -23,6 +29,7 @@ import org.springframework.web.bind.annotation.RestController;
public class TestController {
private final PayExpiredTimeTask expiredTimeTask;;
private final PayWaitOrderSyncTask waitOrderSyncTask;
private final LockTemplate lockTemplate;
@Operation(summary = "同步")
@GetMapping("/sync")
@@ -37,4 +44,26 @@ public class TestController {
return Res.ok();
}
@Operation(summary = "锁测试1")
@GetMapping("/lock1")
// @Lock4j(keys = "#name", acquireTimeout = 50)
public ResResult<String> lock1(String name){
LockInfo lock = lockTemplate.lock(name, 10000, 10);
if (Objects.isNull(lock)){
throw new BizException("未获取到锁");
}
System.out.println("进来了......");
ThreadUtil.sleep(10000);
lockTemplate.releaseLock(lock);
return Res.ok(name);
}
@Operation(summary = "锁测试2")
@GetMapping("/lock2")
// @Lock4j(keys = "#name", acquireTimeout = 50)
public ResResult<String> lock2(String name){
return Res.ok(name);
}
}

View File

@@ -15,9 +15,12 @@ public class ApiInfoLocal {
/** 当前支付接口编码 */
private String apiCode;
/** 是否开启回调通知 */
/** 是否开启通知 */
private boolean notice;
/** 只有异步支付才进行通知 */
private boolean onlyAsyncNotice;
/** 请求参数是否签名 */
private boolean reqSign;

View File

@@ -37,10 +37,24 @@ public class AliPayConfig extends MpBaseEntity implements EntityBaseFunction<Ali
@DbColumn(comment = "支付宝商户appId")
private String appId;
/** 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数必须外网可以正常访问 */
/**
* 服务器异步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
* 1. 需http://或者https://格式的完整路径,
* 2. 不能加?id=123这类自定义参数必须外网可以正常访问
* 3. 调用顺序 支付宝网关 -> 本网关进行处理 -> 发送消息通知业务系统
*/
@DbColumn(comment = "异步通知页面路径")
private String notifyUrl;
/**
* 服务器同步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
* 1. 需http://或者https://格式的完整路径,
* 2. 不能加?id=123这类自定义参数必须外网可以正常访问
* 3. 消息顺序 支付宝网关 -> 本网关进行处理 -> 重定向到业务系统中
*/
@DbColumn(comment = "同步通知页面路径")
private String returnUrl;
/** 请求网关地址 */
@DbColumn(comment = "请求网关地址")
private String serverUrl;

View File

@@ -8,7 +8,7 @@ import cn.bootx.platform.daxpay.service.core.channel.alipay.dao.AliPayOrderManag
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.service.PayOrderChannelService;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -39,22 +39,22 @@ public class AliPayOrderService {
/**
* 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建支付宝支付单
*/
public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) {
public void updatePaySuccess(PayOrder payOrder, PayChannelParam payChannelParam) {
// 更新支付宝异步支付类型信息
payOrder.setAsyncPay(true).setAsyncChannel(PayChannelEnum.ALI.getCode());
payOrderChannelService.updateChannel(payWayParam,payOrder);
payOrderChannelService.updateChannel(payChannelParam,payOrder);
// 更新支付宝可退款类型信息
List<OrderRefundableInfo> refundableInfos = payOrder.getRefundableInfos();
refundableInfos.removeIf(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getChannel()));
refundableInfos.add(new OrderRefundableInfo()
.setChannel(PayChannelEnum.ALI.getCode())
.setAmount(payWayParam.getAmount())
.setAmount(payChannelParam.getAmount())
);
payOrder.setRefundableInfos(refundableInfos);
// 如果支付完成(付款码情况) 调用 updateSyncSuccess 创建支付宝支付记录
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
this.updateAsyncSuccess(payOrder, payWayParam.getAmount());
this.updateAsyncSuccess(payOrder, payChannelParam.getAmount());
}
}

View File

@@ -11,7 +11,7 @@ import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.util.PayUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
@@ -48,13 +48,13 @@ public class AliPayService {
/**
* 支付前检查支付方式是否可用
*/
public void validation(PayWayParam payWayParam, AliPayConfig alipayConfig) {
public void validation(PayChannelParam payChannelParam, AliPayConfig alipayConfig) {
if (CollUtil.isEmpty(alipayConfig.getPayWays())){
throw new PayFailureException("支付宝未配置可用的支付方式");
}
// 发起的支付类型是否在支持的范围内
PayWayEnum payWayEnum = Optional.ofNullable(AliPayWay.findByCode(payWayParam.getWay()))
PayWayEnum payWayEnum = Optional.ofNullable(AliPayWay.findByCode(payChannelParam.getWay()))
.orElseThrow(() -> new PayFailureException("非法的支付宝支付类型"));
if (!alipayConfig.getPayWays().contains(payWayEnum.getCode())) {
throw new PayFailureException("该支付宝支付方式不可用");
@@ -64,29 +64,29 @@ public class AliPayService {
/**
* 调起支付
*/
public void pay(PayOrder payOrder, PayWayParam payWayParam, AliPayParam aliPayParam, AliPayConfig alipayConfig) {
Integer amount = payWayParam.getAmount();
public void pay(PayOrder payOrder, PayChannelParam payChannelParam, AliPayParam aliPayParam, AliPayConfig alipayConfig) {
Integer amount = payChannelParam.getAmount();
String payBody = null;
// 异步线程存储
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();
// wap支付
if (Objects.equals(payWayParam.getWay(), PayWayEnum.WAP.getCode())) {
if (Objects.equals(payChannelParam.getWay(), PayWayEnum.WAP.getCode())) {
payBody = this.wapPay(amount, payOrder, alipayConfig);
}
// 程序支付
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.APP.getCode())) {
else if (Objects.equals(payChannelParam.getWay(), PayWayEnum.APP.getCode())) {
payBody = this.appPay(amount, payOrder, alipayConfig);
}
// pc支付
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.WEB.getCode())) {
else if (Objects.equals(payChannelParam.getWay(), PayWayEnum.WEB.getCode())) {
payBody = this.webPay(amount, payOrder, alipayConfig);
}
// 二维码支付
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.QRCODE.getCode())) {
else if (Objects.equals(payChannelParam.getWay(), PayWayEnum.QRCODE.getCode())) {
payBody = this.qrCodePay(amount, payOrder, alipayConfig);
}
// 付款码支付
else if (Objects.equals(payWayParam.getWay(), PayWayEnum.BARCODE.getCode())) {
else if (Objects.equals(payChannelParam.getWay(), PayWayEnum.BARCODE.getCode())) {
String tradeNo = this.barCode(amount, payOrder, aliPayParam, alipayConfig);
asyncPayInfo.setTradeNo(tradeNo);
}
@@ -112,10 +112,10 @@ public class AliPayService {
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setBizModel(model);
// 异步回调必须到当前系统中
// 异步回调必须到当前支付网关系统中, 然后系统负责转发
request.setNotifyUrl(alipayConfig.getNotifyUrl());
// 同步回调地址
request.setReturnUrl(noticeInfo.getReturnUrl());
// 同步回调地址必须到当前支付网关系统中, 然后系统负责跳转
request.setReturnUrl(alipayConfig.getReturnUrl());
try {
// 通过GET方式的请求, 返回URL的响应, 默认是POST方式的请求, 返回的是表单响应
@@ -172,7 +172,7 @@ public class AliPayService {
// 异步回调必须到当前系统中
request.setNotifyUrl(alipayConfig.getNotifyUrl());
// 同步回调
request.setReturnUrl(noticeInfo.getReturnUrl());
request.setReturnUrl(alipayConfig.getReturnUrl());
try {
// 通过GET方式的请求, 返回URL的响应, 默认是POST方式的请求, 返回的是表单响应
AlipayTradePagePayResponse response = AliPayApi.pageExecute(request, Method.GET.name());

View File

@@ -5,7 +5,7 @@ import cn.bootx.platform.daxpay.service.core.channel.cash.dao.CashPayOrderManage
import cn.bootx.platform.daxpay.service.core.channel.cash.entity.CashPayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -28,7 +28,7 @@ public class CashService {
/**
* 支付
*/
public void pay(PayWayParam payMode, PayOrder payment, PayParam payParam) {
public void pay(PayChannelParam payMode, PayOrder payment, PayParam payParam) {
CashPayOrder walletPayment = new CashPayOrder();
walletPayment.setPaymentId(payment.getId())
.setBusinessNo(payParam.getBusinessNo())

View File

@@ -7,7 +7,7 @@ import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherPayOr
import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherRecord;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -31,7 +31,7 @@ public class VoucherPayOrderService {
/**
* 添加支付记录
*/
public void savePayment(PayOrder payOrder, PayParam payParam, PayWayParam payMode, VoucherRecord voucherRecord) {
public void savePayment(PayOrder payOrder, PayParam payParam, PayChannelParam payMode, VoucherRecord voucherRecord) {
VoucherPayOrder voucherPayOrder = new VoucherPayOrder().setVoucherRecord(voucherRecord);
voucherPayOrder.setPaymentId(payOrder.getId())
.setBusinessNo(payParam.getBusinessNo())

View File

@@ -11,7 +11,7 @@ import cn.bootx.platform.daxpay.service.core.channel.voucher.entity.VoucherRecor
import cn.bootx.platform.daxpay.service.core.record.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.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONUtil;
@@ -42,11 +42,11 @@ public class VoucherPayService {
/**
* 获取并检查储值卡
*/
public Voucher getAndCheckVoucher(PayWayParam payWayParam) {
public Voucher getAndCheckVoucher(PayChannelParam payChannelParam) {
VoucherPayParam voucherPayParam;
try {
// 储值卡参数验证
String extraParamsJson = payWayParam.getChannelExtra();
String extraParamsJson = payChannelParam.getChannelExtra();
if (StrUtil.isNotBlank(extraParamsJson)) {
voucherPayParam = JSONUtil.toBean(extraParamsJson, VoucherPayParam.class);
} else {

View File

@@ -7,7 +7,7 @@ import cn.bootx.platform.daxpay.service.core.channel.wallet.entity.Wallet;
import cn.bootx.platform.daxpay.service.core.channel.wallet.entity.WalletPayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -31,7 +31,7 @@ public class WalletPayOrderService {
/**
* 保存钱包支付记录
*/
public void savePayment(PayOrder payOrder, PayParam payParam, PayWayParam payMode, Wallet wallet) {
public void savePayment(PayOrder payOrder, PayParam payParam, PayChannelParam payMode, Wallet wallet) {
WalletPayOrder walletPayOrder = new WalletPayOrder().setWalletId(wallet.getId());
walletPayOrder.setPaymentId(payOrder.getId())
.setBusinessNo(payParam.getBusinessNo())

View File

@@ -41,6 +41,24 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "微信应用appId")
private String wxAppId;
/**
* 服务器异步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
* 1. 需http://或者https://格式的完整路径,
* 2. 不能加?id=123这类自定义参数必须外网可以正常访问
* 3. 消息顺序 微信网关 -> 本网关进行处理 -> 发送消息通知业务系统
*/
@DbColumn(comment = "异步通知路径")
private String notifyUrl;
/**
* 服务器同步通知页面路径, 需要填写本网关服务的地址, 不可以直接填写业务系统的地址
* 1. 需http://或者https://格式的完整路径,
* 2. 不能加?id=123这类自定义参数必须外网可以正常访问
* 3. 消息顺序 微信网关 -> 本网关进行处理 -> 重定向到业务系统中
*/
@DbColumn(comment = "同步通知路径")
private String returnUrl;
/** 商户平台「API安全」中的 APIv2 密钥 */
@TableField(updateStrategy = FieldStrategy.ALWAYS)
@BigField
@@ -69,10 +87,6 @@ public class WeChatPayConfig extends MpBaseEntity implements EntityBaseFunction<
@DbColumn(comment = "API证书中p12证书Base64")
private String p12;
/** 这个是跳转到当前网关服务上的, 不是通知到客户系统的 */
@DbColumn(comment = "异步通知路径")
private String notifyUrl;
/** 是否沙箱环境 */
@DbColumn(comment = "是否沙箱环境")
private boolean sandbox;

View File

@@ -10,7 +10,7 @@ import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayOrde
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.service.PayOrderChannelService;
import cn.bootx.platform.daxpay.service.core.record.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -40,23 +40,23 @@ public class WeChatPayOrderService {
/**
* 支付调起成功 更新payment中异步支付类型信息, 如果支付完成, 创建微信支付单
*/
public void updatePaySuccess(PayOrder payOrder, PayWayParam payWayParam) {
public void updatePaySuccess(PayOrder payOrder, PayChannelParam payChannelParam) {
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();;
payOrder.setAsyncPay(true).setAsyncChannel(PayChannelEnum.WECHAT.getCode());
payOrderChannelService.updateChannel(payWayParam,payOrder);
payOrderChannelService.updateChannel(payChannelParam,payOrder);
// 更新微信可退款类型信息
List<OrderRefundableInfo> refundableInfos = payOrder.getRefundableInfos();
refundableInfos.removeIf(payTypeInfo -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payTypeInfo.getChannel()));
refundableInfos.add(new OrderRefundableInfo()
.setChannel(PayChannelEnum.WECHAT.getCode())
.setAmount(payWayParam.getAmount())
.setAmount(payChannelParam.getAmount())
);
payOrder.setRefundableInfos(refundableInfos);
// 如果支付完成(付款码情况) 调用 updateSyncSuccess 创建微信支付记录
if (Objects.equals(payOrder.getStatus(), PayStatusEnum.SUCCESS.getCode())) {
this.createWeChatOrder(payOrder, payWayParam.getAmount());
this.createWeChatOrder(payOrder, payChannelParam.getAmount());
}
}

View File

@@ -15,7 +15,7 @@ import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.payment.sync.service.PaySyncService;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.param.channel.wechat.WeChatPayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.result.pay.PaySyncResult;
import cn.bootx.platform.daxpay.util.PayUtil;
import cn.hutool.core.date.DatePattern;
@@ -62,13 +62,13 @@ public class WeChatPayService {
/**
* 校验
*/
public void validation(PayWayParam payWayParam, WeChatPayConfig weChatPayConfig) {
public void validation(PayChannelParam payChannelParam, WeChatPayConfig weChatPayConfig) {
List<String> payWays = weChatPayConfig.getPayWays();
if (CollUtil.isEmpty(payWays)){
throw new PayFailureException("未配置微信支付方式");
}
PayWayEnum payWayEnum = Optional.ofNullable(WeChatPayWay.findByCode(payWayParam.getWay()))
PayWayEnum payWayEnum = Optional.ofNullable(WeChatPayWay.findByCode(payChannelParam.getWay()))
.orElseThrow(() -> new PayFailureException("非法的微信支付类型"));
if (!payWays.contains(payWayEnum.getCode())) {
throw new PayFailureException("该微信支付方式不可用");
@@ -78,13 +78,13 @@ public class WeChatPayService {
/**
* 支付
*/
public void pay(PayOrder payOrder, WeChatPayParam weChatPayParam, PayWayParam payWayParam, WeChatPayConfig weChatPayConfig) {
public void pay(PayOrder payOrder, WeChatPayParam weChatPayParam, PayChannelParam payChannelParam, WeChatPayConfig weChatPayConfig) {
Integer amount = payWayParam.getAmount();
Integer amount = payChannelParam.getAmount();
String totalFee = String.valueOf(amount);
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();;
String payBody = null;
PayWayEnum payWayEnum = PayWayEnum.findByCode(payWayParam.getWay());
PayWayEnum payWayEnum = PayWayEnum.findByCode(payChannelParam.getWay());
// wap支付
if (payWayEnum == PayWayEnum.WAP) {

View File

@@ -0,0 +1,33 @@
package cn.bootx.platform.daxpay.service.core.notice.result;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayWayEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 各支付通道参数
* @author xxm
* @since 2024/1/7
*/
@Data
@Accessors(chain = true)
@Schema(title = "各支付通道参数")
public class PayChannelResult {
/**
* @see PayChannelEnum#getCode()
*/
@Schema(description = "支付通道编码")
private String channel;
/**
* @see PayWayEnum#getCode()
*/
@Schema(description = "支付方式编码")
private String way;
@Schema(description = "支付金额")
private Integer amount;
}

View File

@@ -0,0 +1,61 @@
package cn.bootx.platform.daxpay.service.core.notice.result;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
/**
* 支付异步通知类
* @author xxm
* @since 2024/1/7
*/
@Data
@Accessors(chain = true)
@Schema(title = "支付异步通知类")
public class PayNoticeResult {
@Schema(description = "支付ID")
private Long paymentId;
@Schema(description = "业务号")
private String businessNo;
@Schema(description = "是否是异步支付")
private boolean asyncPay;
/**
* @see PayChannelEnum#ASYNC_TYPE_CODE
*/
@Schema(description = "异步支付通道")
private String asyncPayChannel;
@Schema(description = "支付金额")
private Integer amount;
@Schema(description = "支付通道信息")
private List<PayChannelResult> payChannels;
/**
* @see PayStatusEnum
*/
@Schema(description = "支付状态")
private String status;
@Schema(description = "支付成功时间")
private LocalDateTime payTime;
@Schema(description = "支付创建时间")
private LocalDateTime createTime;
@Schema(description = "商户扩展参数,回调时会原样返回")
private String attach;
@Schema(description = "签名")
private String sign;
}

View File

@@ -0,0 +1,16 @@
package cn.bootx.platform.daxpay.service.core.notice.result;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 支付同步通知类
* @author xxm
* @since 2024/1/7
*/
@Data
@Accessors(chain = true)
@Schema(title = "支付同步通知类")
public class PayReturnResult {
}

View File

@@ -0,0 +1,105 @@
package cn.bootx.platform.daxpay.service.core.notice.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.daxpay.code.PaySignTypeEnum;
import cn.bootx.platform.daxpay.service.common.context.ApiInfoLocal;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.notice.result.PayChannelResult;
import cn.bootx.platform.daxpay.service.core.notice.result.PayNoticeResult;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderExtraManager;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.util.PaySignUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 支付相关消息通知
* @author xxm
* @since 2024/1/7
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PayNoticeService {
private final PayOrderManager payOrderManager;
private final PayOrderExtraManager payOrderExtraManager;
private final PayOrderChannelManager payOrderChannelManager;
/**
* 发送支付完成回调通知
*/
public void sendPayCallbackNotice(Long paymentId){
try {
// 获取通知地址和内容相关的信息
ApiInfoLocal apiInfo = PaymentContextLocal.get().getApiInfo();
PlatformLocal platform = PaymentContextLocal.get().getPlatform();
// 首先判断接口是开启了通知回调功能
if (apiInfo.isNotice()){
PayOrder payOrder = payOrderManager.findById(paymentId).orElseThrow(DataNotExistException::new);
// 判断是否是同步支付, 并且配置不进行消息通知
if (!payOrder.isAsyncPay() && apiInfo.isOnlyAsyncNotice()){
return;
}
// 判断客户端发送的请求是否需要发送回调通知
PayOrderExtra payOrderExtra = payOrderExtraManager.findById(paymentId).orElseThrow(DataNotExistException::new);
if (payOrderExtra.isNotNotify()){
return;
}
List<PayChannelResult> orderChannels = payOrderChannelManager.findAllByPaymentId(paymentId).stream()
.map(o->new PayChannelResult().setChannel(o.getChannel()).setWay(o.getPayWay()).setAmount(o.getAmount()))
.collect(Collectors.toList());
// 组装内容
PayNoticeResult payNoticeResult = new PayNoticeResult()
.setPaymentId(payOrder.getId())
.setAsyncPay(payOrder.isAsyncPay())
.setBusinessNo(payOrder.getBusinessNo())
.setAmount(payOrder.getAmount())
.setPayTime(payOrder.getPayTime())
.setCreateTime(payOrder.getCreateTime())
.setStatus(payOrder.getStatus())
.setAttach(payOrderExtra.getAttach())
.setPayChannels(orderChannels);
// 是否需要签名
if (apiInfo.isNoticeSign()){
// 签名
if (Objects.equals(platform.getSignType(), PaySignTypeEnum.MD5.getCode())){
payNoticeResult.setSign(PaySignUtil.md5Sign(payNoticeResult,platform.getSignSecret()));
} else {
payNoticeResult.setSign(PaySignUtil.hmacSha256Sign(payNoticeResult,platform.getSignSecret()));
}
}
// 地址
String notifyUrl = payOrderExtra.getNotifyUrl();
if (StrUtil.isBlank(notifyUrl)){
throw new DataNotExistException("通知地址为空");
}
// 发送请求数据
HttpResponse execute = HttpUtil.createPost(notifyUrl)
.body(JSONUtil.toJsonStr(payNoticeResult), ContentType.JSON.getValue())
.timeout(10000)
.execute();
log.info(execute.body());
}
} catch (Exception e) {
log.error("发送失败",e);
// 记录错误信息, 同时配置下次通知的时间
}
}
}

View File

@@ -1,18 +1,16 @@
package cn.bootx.platform.daxpay.service.core.payment.common.service;
import cn.bootx.platform.daxpay.code.PaySignTypeEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayCommonParam;
import cn.bootx.platform.daxpay.service.common.context.ApiInfoLocal;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayCommonParam;
import cn.hutool.core.util.StrUtil;
import com.ijpay.core.kit.PayKit;
import cn.bootx.platform.daxpay.util.PaySignUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Objects;
/**
@@ -25,7 +23,6 @@ import java.util.Objects;
@RequiredArgsConstructor
public class PaymentSignService {
private static final String FIELD_SIGN = "sign";
private final PaymentAssistService paymentAssistService;;
@@ -43,24 +40,15 @@ public class PaymentSignService {
}
// 参数转换为Map对象
PlatformLocal platform = PaymentContextLocal.get().getPlatform();
Map<String, String> params = param.toMap();
String signType = platform.getSignType();
// 生成签名前先去除sign参数
params.remove(FIELD_SIGN);
String data = PayKit.createLinkString(params);
if (Objects.equals(PaySignTypeEnum.HMAC_SHA256.getCode(), signType)){
// 签名验证
data += "&key=" + platform.getSignSecret();
String sha256 = PayKit.hmacSha256(data, platform.getSignSecret());
if (!Objects.equals(sha256, params.get(FIELD_SIGN))){
boolean verified = PaySignUtil.verifyHmacSha256Sign(param, platform.getSignSecret(), param.getSign());
if (!verified){
throw new PayFailureException("签名验证未通过");
}
} else if (Objects.equals(PaySignTypeEnum.MD5.getCode(), signType)){
data += "&key=" + platform.getSignSecret();
String md5 = PayKit.md5(data.toUpperCase());
String sign = StrUtil.toString(params.get(FIELD_SIGN));
// 签名验证
if (!md5.equalsIgnoreCase(sign)){
boolean verified = PaySignUtil.verifyMd5Sign(param, platform.getSignSecret(), param.getSign());
if (!verified){
throw new PayFailureException("签名验证未通过");
}
} else {

View File

@@ -2,7 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.pay.factory;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.service.core.payment.pay.strategy.*;
import cn.bootx.platform.daxpay.service.func.AbsPayStrategy;
import cn.hutool.core.collection.CollectionUtil;
@@ -29,12 +29,12 @@ public class PayStrategyFactory {
/**
* 根据传入的支付通道创建策略
* @param payWayParam 支付类型
* @param payChannelParam 支付类型
* @return 支付策略
*/
public AbsPayStrategy createAsyncFront(PayWayParam payWayParam) {
public AbsPayStrategy createAsyncFront(PayChannelParam payChannelParam) {
AbsPayStrategy strategy;
PayChannelEnum channelEnum = PayChannelEnum.findByCode(payWayParam.getChannel());
PayChannelEnum channelEnum = PayChannelEnum.findByCode(payChannelParam.getChannel());
switch (channelEnum) {
case ALI:
strategy = SpringUtil.getBean(AliPayStrategy.class);
@@ -57,7 +57,7 @@ public class PayStrategyFactory {
default:
throw new PayUnsupportedMethodException();
}
strategy.setPayWayParam(payWayParam);
strategy.setPayChannelParam(payChannelParam);
return strategy;
}
@@ -65,49 +65,49 @@ public class PayStrategyFactory {
* 根据传入的支付类型批量创建策略, 异步支付在后面
* 同步支付逻辑完后才执行异步支付的逻辑, 预防异步执行成功, 然同步执行失败. 导致异步支付无法回滚的问题
*/
public static List<AbsPayStrategy> createAsyncLast(List<PayWayParam> payWayParamList) {
return createAsyncFront(payWayParamList, false);
public static List<AbsPayStrategy> createAsyncLast(List<PayChannelParam> payChannelParamList) {
return createAsyncFront(payChannelParamList, false);
}
/**
* 根据传入的支付类型批量创建策略, 异步支付在前面 font
*/
public List<AbsPayStrategy> createAsyncFront(List<PayWayParam> payWayParamList) {
return createAsyncFront(payWayParamList, true);
public List<AbsPayStrategy> createAsyncFront(List<PayChannelParam> payChannelParamList) {
return createAsyncFront(payChannelParamList, true);
}
/**
* 根据传入的支付类型批量创建策略
* @param payWayParamList 支付类型
* @param payChannelParamList 支付类型
* @return 支付策略
*/
private List<AbsPayStrategy> createAsyncFront(List<PayWayParam> payWayParamList, boolean asyncFirst) {
if (CollectionUtil.isEmpty(payWayParamList)) {
private List<AbsPayStrategy> createAsyncFront(List<PayChannelParam> payChannelParamList, boolean asyncFirst) {
if (CollectionUtil.isEmpty(payChannelParamList)) {
return Collections.emptyList();
}
List<AbsPayStrategy> list = new ArrayList<>(payWayParamList.size());
List<AbsPayStrategy> list = new ArrayList<>(payChannelParamList.size());
// 同步支付
List<PayWayParam> syncPayWayParamList = payWayParamList.stream()
List<PayChannelParam> syncPayChannelParamList = payChannelParamList.stream()
.filter(Objects::nonNull)
.filter(payModeParam -> !ASYNC_TYPE_CODE.contains(payModeParam.getChannel()))
.collect(Collectors.toList());
// 异步支付
List<PayWayParam> asyncPayWayParamList = payWayParamList.stream()
List<PayChannelParam> asyncPayChannelParamList = payChannelParamList.stream()
.filter(Objects::nonNull)
.filter(payModeParam -> ASYNC_TYPE_CODE.contains(payModeParam.getChannel()))
.collect(Collectors.toList());
List<PayWayParam> sortList = new ArrayList<>(payWayParamList.size());
List<PayChannelParam> sortList = new ArrayList<>(payChannelParamList.size());
// 异步在后面
if (asyncFirst) {
sortList.addAll(asyncPayWayParamList);
sortList.addAll(syncPayWayParamList);
sortList.addAll(asyncPayChannelParamList);
sortList.addAll(syncPayChannelParamList);
} else {
sortList.addAll(syncPayWayParamList);
sortList.addAll(asyncPayWayParamList);
sortList.addAll(syncPayChannelParamList);
sortList.addAll(asyncPayChannelParamList);
}
// 此处有一个根据Type的反转排序

View File

@@ -6,7 +6,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
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;
@@ -65,7 +65,7 @@ public class PayAssistService {
*/
public void initExpiredTime(PayOrder order, PayParam payParam){
// 不是异步支付,没有超时时间
if (PayUtil.isNotSync(payParam.getPayWays())){
if (PayUtil.isNotSync(payParam.getPayChannels())){
return;
}
AsyncPayLocal asyncPayInfo = PaymentContextLocal.get().getAsyncPayInfo();
@@ -109,23 +109,23 @@ public class PayAssistService {
/**
* 获取异步支付参数
*/
public PayWayParam getAsyncPayParam(PayParam payParam, PayOrder payOrder) {
public PayChannelParam getAsyncPayParam(PayParam payParam, PayOrder payOrder) {
// 查询之前的支付方式
String asyncPayChannel = payOrder.getAsyncChannel();
PayOrderChannel payOrderChannel = payOrderChannelManager.findByPaymentIdAndChannel(payOrder.getId(), asyncPayChannel)
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
// 新的异步支付方式
PayWayParam payWayParam = payParam.getPayWays()
PayChannelParam payChannelParam = payParam.getPayChannels()
.stream()
.filter(payMode -> PayChannelEnum.ASYNC_TYPE_CODE.contains(payMode.getChannel()))
.findFirst()
.orElseThrow(() -> new PayFailureException("支付方式数据异常"));
// 新传入的金额是否一致
if (!Objects.equals(payOrderChannel.getAmount(), payWayParam.getAmount())){
if (!Objects.equals(payOrderChannel.getAmount(), payChannelParam.getAmount())){
throw new PayFailureException("传入的支付金额非法!与订单金额不一致");
}
return payWayParam;
return payChannelParam;
}
/**
@@ -139,7 +139,7 @@ public class PayAssistService {
PayOrderExtra payOrderExtra = PaymentBuilder.buildPayOrderExtra(payParam, payOrder.getId());
payOrderExtraManager.save(payOrderExtra);
// 构建支付通道表并保存
List<PayOrderChannel> payOrderChannels = PaymentBuilder.buildPayChannel(payParam.getPayWays())
List<PayOrderChannel> payOrderChannels = PaymentBuilder.buildPayChannel(payParam.getPayChannels())
.stream()
.peek(o -> o.setPaymentId(payOrder.getId()).setAsync(payOrder.isAsyncPay()))
.collect(Collectors.toList());
@@ -155,10 +155,12 @@ public class PayAssistService {
public void updatePayOrderExtra(PayParam payParam,Long paymentId){
PayOrderExtra payOrderExtra = payOrderExtraManager.findById(paymentId)
.orElseThrow(() -> new DataNotExistException("支付订单不存在"));
String notifyUrl = PaymentContextLocal.get().getNoticeInfo().getNotifyUrl();
payOrderExtra.setReqTime(payParam.getReqTime())
.setSign(payParam.getSign())
.setNotNotify(payParam.isNotNotify())
.setNotifyUrl(payParam.getNotifyUrl())
.setNotifyUrl(notifyUrl)
.setAttach(payParam.getAttach())
.setClientIp(payParam.getClientIp());
payOrderExtraManager.updateById(payOrderExtra);
}

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayUnsupportedMethodException;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.param.pay.SimplePayParam;
import cn.bootx.platform.daxpay.result.pay.PayResult;
import cn.bootx.platform.daxpay.service.core.payment.pay.factory.PayStrategyFactory;
@@ -92,13 +92,13 @@ public class PayService {
public PayResult simplePay(SimplePayParam simplePayParam) {
// 组装支付参数
PayParam payParam = new PayParam();
PayWayParam payWayParam = new PayWayParam();
payWayParam.setChannel(simplePayParam.getPayChannel());
payWayParam.setWay(simplePayParam.getPayWay());
payWayParam.setAmount(simplePayParam.getAmount());
payWayParam.setChannelExtra(simplePayParam.getChannelExtra());
PayChannelParam payChannelParam = new PayChannelParam();
payChannelParam.setChannel(simplePayParam.getPayChannel());
payChannelParam.setWay(simplePayParam.getPayWay());
payChannelParam.setAmount(simplePayParam.getAmount());
payChannelParam.setChannelExtra(simplePayParam.getChannelExtra());
BeanUtil.copyProperties(simplePayParam,payParam, CopyOptions.create().ignoreNullValue());
payParam.setPayWays(Collections.singletonList(payWayParam));
payParam.setPayChannels(Collections.singletonList(payChannelParam));
// 复用支付下单接口
return this.pay(payParam);
}
@@ -113,7 +113,7 @@ public class PayService {
}
// 2. 价格检测
PayUtil.validationAmount(payParam.getPayWays());
PayUtil.validationAmount(payParam.getPayChannels());
// 3. 创建支付相关的记录并返回支付订单对象
payOrder = payAssistService.createPayOrder(payParam);
@@ -131,7 +131,7 @@ public class PayService {
private void firstPayHandler(PayParam payParam, PayOrder payOrder) {
// 1.获取支付方式,通过工厂生成对应的策略组
List<AbsPayStrategy> paymentStrategyList = PayStrategyFactory.createAsyncLast(payParam.getPayWays());
List<AbsPayStrategy> paymentStrategyList = PayStrategyFactory.createAsyncLast(payParam.getPayChannels());
if (CollectionUtil.isEmpty(paymentStrategyList)) {
throw new PayUnsupportedMethodException();
}
@@ -149,7 +149,7 @@ public class PayService {
// 发起支付成功进行的执行方法
strategies.forEach(AbsPayStrategy::doSuccessHandler);
// 所有支付方式都是同步时, 对支付订单进行处理
if (PayUtil.isNotSync(payParam.getPayWays())) {
if (PayUtil.isNotSync(payParam.getPayChannels())) {
// 修改支付订单状态为成功
payOrderObj.setStatus(PayStatusEnum.SUCCESS.getCode());
payOrderObj.setPayTime(LocalDateTime.now());
@@ -171,8 +171,8 @@ public class PayService {
}
// 2.获取 异步支付通道,通过工厂生成对应的策略组(只包含异步支付的策略, 同步支付相关逻辑不再进行执行)
PayWayParam payWayParam = payAssistService.getAsyncPayParam(payParam, payOrder);
List<AbsPayStrategy> asyncStrategyList = PayStrategyFactory.createAsyncLast(Collections.singletonList(payWayParam));
PayChannelParam payChannelParam = payAssistService.getAsyncPayParam(payParam, payOrder);
List<AbsPayStrategy> asyncStrategyList = PayStrategyFactory.createAsyncLast(Collections.singletonList(payChannelParam));
// 3.初始化支付的参数
for (AbsPayStrategy paymentStrategy : asyncStrategyList) {

View File

@@ -11,7 +11,7 @@ import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONException;
import cn.hutool.json.JSONUtil;
@@ -56,7 +56,7 @@ public class AliPayStrategy extends AbsPayStrategy {
public void doBeforePayHandler() {
try {
// 支付宝参数验证
String extraParamsJson = this.getPayWayParam().getChannelExtra();
String extraParamsJson = this.getPayChannelParam().getChannelExtra();
if (StrUtil.isNotBlank(extraParamsJson)) {
this.aliPayParam = JSONUtil.toBean(extraParamsJson, AliPayParam.class);
}
@@ -68,13 +68,13 @@ public class AliPayStrategy extends AbsPayStrategy {
throw new PayFailureException("支付参数错误");
}
// 检查金额
PayWayParam payMode = this.getPayWayParam();
PayChannelParam payMode = this.getPayChannelParam();
if (payMode.getAmount() <= 0) {
throw new PayAmountAbnormalException();
}
// 检查并获取支付宝支付配置
this.initAlipayConfig();
aliPayService.validation(this.getPayWayParam(), alipayConfig);
aliPayService.validation(this.getPayChannelParam(), alipayConfig);
}
/**
@@ -82,7 +82,7 @@ public class AliPayStrategy extends AbsPayStrategy {
*/
@Override
public void doPayHandler() {
aliPayService.pay( this.getOrder(), this.getPayWayParam(), this.aliPayParam, this.alipayConfig);
aliPayService.pay( this.getOrder(), this.getPayChannelParam(), this.aliPayParam, this.alipayConfig);
}
/**
@@ -90,7 +90,7 @@ public class AliPayStrategy extends AbsPayStrategy {
*/
@Override
public void doSuccessHandler() {
aliPaymentService.updatePaySuccess(this.getOrder(), this.getPayWayParam());
aliPaymentService.updatePaySuccess(this.getOrder(), this.getPayChannelParam());
}
/**

View File

@@ -5,7 +5,7 @@ import cn.bootx.platform.daxpay.service.core.channel.cash.service.CashService;
import cn.bootx.platform.daxpay.service.core.record.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.exception.pay.PayAmountAbnormalException;
import cn.bootx.platform.daxpay.service.func.AbsPayStrategy;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
@@ -43,8 +43,8 @@ public class CashPayStrategy extends AbsPayStrategy {
@Override
public void doBeforePayHandler() {
// 检查金额
PayWayParam payWayParam = this.getPayWayParam();
if (payWayParam.getAmount() <= 0) {
PayChannelParam payChannelParam = this.getPayChannelParam();
if (payChannelParam.getAmount() <= 0) {
throw new PayAmountAbnormalException();
}
}
@@ -54,7 +54,7 @@ public class CashPayStrategy extends AbsPayStrategy {
*/
@Override
public void doPayHandler() {
cashService.pay(this.getPayWayParam(), this.getOrder(), this.getPayParam());
cashService.pay(this.getPayChannelParam(), this.getOrder(), this.getPayParam());
}
/**

View File

@@ -42,7 +42,7 @@ public class VoucherPayStrategy extends AbsPayStrategy {
@Override
public void doBeforePayHandler() {
// 获取并校验储值卡
this.voucher = voucherPayService.getAndCheckVoucher(this.getPayWayParam());
this.voucher = voucherPayService.getAndCheckVoucher(this.getPayChannelParam());
}
/**
@@ -54,11 +54,11 @@ public class VoucherPayStrategy extends AbsPayStrategy {
public void doPayHandler() {
VoucherRecord voucherRecord;
if (this.getOrder().isAsyncPay()){
voucherRecord = voucherPayService.freezeBalance(this.getPayWayParam().getAmount(), this.getOrder(), this.voucher);
voucherRecord = voucherPayService.freezeBalance(this.getPayChannelParam().getAmount(), this.getOrder(), this.voucher);
} else {
voucherRecord = voucherPayService.pay(this.getPayWayParam().getAmount(), this.getOrder(), this.voucher);
voucherRecord = voucherPayService.pay(this.getPayChannelParam().getAmount(), this.getOrder(), this.voucher);
}
voucherPayOrderService.savePayment(this.getOrder(), getPayParam(), getPayWayParam(), voucherRecord);
voucherPayOrderService.savePayment(this.getOrder(), getPayParam(), getPayChannelParam(), voucherRecord);
}
/**

View File

@@ -54,7 +54,7 @@ public class WalletPayStrategy extends AbsPayStrategy {
WalletPayParam walletPayParam = new WalletPayParam();
try {
// 钱包参数验证
String extraParamsJson = this.getPayWayParam().getChannelExtra();
String extraParamsJson = this.getPayChannelParam().getChannelExtra();
if (StrUtil.isNotBlank(extraParamsJson)) {
walletPayParam = JSONUtil.toBean(extraParamsJson, WalletPayParam.class);
}
@@ -71,7 +71,7 @@ public class WalletPayStrategy extends AbsPayStrategy {
throw new WalletBannedException();
}
// 判断余额
if (this.wallet.getBalance() < getPayWayParam().getAmount()) {
if (this.wallet.getBalance() < getPayChannelParam().getAmount()) {
throw new WalletLackOfBalanceException();
}
}
@@ -83,11 +83,11 @@ public class WalletPayStrategy extends AbsPayStrategy {
public void doPayHandler() {
// 异步支付方式时使用冻结方式
if (this.getOrder().isAsyncPay()){
walletPayService.freezeBalance(getPayWayParam().getAmount(), this.getOrder(), this.wallet);
walletPayService.freezeBalance(getPayChannelParam().getAmount(), this.getOrder(), this.wallet);
} else {
walletPayService.pay(getPayWayParam().getAmount(), this.getOrder(), this.wallet);
walletPayService.pay(getPayChannelParam().getAmount(), this.getOrder(), this.wallet);
}
walletPayOrderService.savePayment(this.getOrder(), this.getPayParam(), this.getPayWayParam(), this.wallet);
walletPayOrderService.savePayment(this.getOrder(), this.getPayParam(), this.getPayChannelParam(), this.wallet);
}
/**

View File

@@ -3,7 +3,7 @@ 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.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import cn.bootx.platform.daxpay.service.common.exception.ExceptionInfo;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.channel.wechat.service.WeChatPayCloseService;
@@ -60,7 +60,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
this.initWeChatPayConfig();
try {
// 微信参数验证
String extraParamsJson = this.getPayWayParam().getChannelExtra();
String extraParamsJson = this.getPayChannelParam().getChannelExtra();
if (StrUtil.isNotBlank(extraParamsJson)) {
this.weChatPayParam = JSONUtil.toBean(extraParamsJson, WeChatPayParam.class);
}
@@ -73,14 +73,14 @@ public class WeChatPayStrategy extends AbsPayStrategy {
}
// 检查金额
PayWayParam payMode = this.getPayWayParam();
PayChannelParam payMode = this.getPayChannelParam();
if (payMode.getAmount() <= 0) {
throw new PayAmountAbnormalException();
}
// 检查并获取微信支付配置
this.initWeChatPayConfig();
weChatPayService.validation(this.getPayWayParam(), weChatPayConfig);
weChatPayService.validation(this.getPayChannelParam(), weChatPayConfig);
}
/**
@@ -88,7 +88,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/
@Override
public void doPayHandler() {
weChatPayService.pay(this.getOrder(), this.weChatPayParam, this.getPayWayParam(), this.weChatPayConfig);
weChatPayService.pay(this.getOrder(), this.weChatPayParam, this.getPayChannelParam(), this.weChatPayConfig);
}
/**
@@ -96,7 +96,7 @@ public class WeChatPayStrategy extends AbsPayStrategy {
*/
@Override
public void doSuccessHandler() {
weChatPayOrderService.updatePaySuccess(this.getOrder(), this.getPayWayParam());
weChatPayOrderService.updatePaySuccess(this.getOrder(), this.getPayChannelParam());
}
/**

View File

@@ -11,7 +11,7 @@ import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.service.common.entity.OrderRefundableInfo;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
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;
@@ -42,16 +42,16 @@ public class PaymentBuilder {
.getAsyncPayInfo()
.getExpiredTime();
// 可退款信息
List<OrderRefundableInfo> refundableInfos = buildRefundableInfo(payParam.getPayWays());
List<OrderRefundableInfo> refundableInfos = buildRefundableInfo(payParam.getPayChannels());
// 计算总价
int sumAmount = payParam.getPayWays().stream()
.map(PayWayParam::getAmount)
int sumAmount = payParam.getPayChannels().stream()
.map(PayChannelParam::getAmount)
.filter(Objects::nonNull)
.reduce(Integer::sum)
.orElse(0);
// 是否有异步支付方式
Optional<String> asyncPay = payParam.getPayWays().stream()
.map(PayWayParam::getChannel)
Optional<String> asyncPay = payParam.getPayChannels().stream()
.map(PayChannelParam::getChannel)
.filter(PayChannelEnum.ASYNC_TYPE_CODE::contains)
.findFirst();
// 构建支付订单对象
@@ -62,7 +62,7 @@ public class PaymentBuilder {
.setStatus(PayStatusEnum.PROGRESS.getCode())
.setAmount(sumAmount)
.setExpiredTime(expiredTime)
.setCombinationPay(payParam.getPayWays().size() > 1)
.setCombinationPay(payParam.getPayChannels().size() > 1)
.setAsyncPay(asyncPay.isPresent())
.setAsyncChannel(asyncPay.orElse(null))
.setRefundableBalance(sumAmount);
@@ -83,6 +83,7 @@ public class PaymentBuilder {
.setNotifyUrl(noticeInfo.getNotifyUrl())
.setSign(payParam.getSign())
.setSignType(platform.getSignType())
.setAttach(payParam.getAttach())
.setApiVersion(payParam.getVersion())
.setReqTime(payParam.getReqTime());
payOrderExtra.setId(paymentId);
@@ -92,11 +93,11 @@ public class PaymentBuilder {
/**
* 构建订单关联通道信息
*/
public List<PayOrderChannel> buildPayChannel(List<PayWayParam> payWayParams) {
if (CollectionUtil.isEmpty(payWayParams)) {
public List<PayOrderChannel> buildPayChannel(List<PayChannelParam> payChannelParams) {
if (CollectionUtil.isEmpty(payChannelParams)) {
return Collections.emptyList();
}
return payWayParams.stream()
return payChannelParams.stream()
.map(o-> new PayOrderChannel()
.setChannel(o.getChannel())
.setPayWay(o.getWay())
@@ -108,11 +109,11 @@ public class PaymentBuilder {
/**
* 构建支付订单的可退款信息列表
*/
private List<OrderRefundableInfo> buildRefundableInfo(List<PayWayParam> payWayParamList) {
if (CollectionUtil.isEmpty(payWayParamList)) {
private List<OrderRefundableInfo> buildRefundableInfo(List<PayChannelParam> payChannelParamList) {
if (CollectionUtil.isEmpty(payChannelParamList)) {
return Collections.emptyList();
}
return payWayParamList.stream()
return payChannelParamList.stream()
.map(o-> new OrderRefundableInfo()
.setChannel(o.getChannel())
.setAmount(o.getAmount()))

View File

@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
@@ -17,6 +18,14 @@ import java.util.Optional;
@Repository
@RequiredArgsConstructor
public class PayOrderChannelManager extends BaseManager<PayOrderChannelMapper, PayOrderChannel> {
/**
* 根据订单查找
*/
public List<PayOrderChannel> findAllByPaymentId(Long paymentId){
return findAllByField(PayOrderChannel::getPaymentId,paymentId);
}
/**
* 根据订单id和支付通道查询
*/

View File

@@ -7,6 +7,7 @@ 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
@@ -42,6 +43,8 @@ public class PayOrderExtra extends MpBaseEntity {
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String notifyUrl;
/** 同步通知 */
/** 签名类型 */
@DbColumn(comment = "签名类型")
private String signType;
@@ -50,6 +53,9 @@ public class PayOrderExtra extends MpBaseEntity {
@DbColumn(comment = "签名,以最后一次为准")
private String sign;
@Schema(description = "商户扩展参数,回调时会原样返回")
private String attach;
/** API版本号 */
@DbColumn(comment = "API版本号")
private String apiVersion;

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.core.record.pay.dao.PayOrderChannelManager;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrderChannel;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -27,22 +27,22 @@ public class PayOrderChannelService {
* 更新支付订单的通道信息
*/
@Transactional(rollbackFor = Exception.class)
public void updateChannel(PayWayParam payWayParam, PayOrder payOrder){
public void updateChannel(PayChannelParam payChannelParam, PayOrder payOrder){
Optional<PayOrderChannel> payOrderChannelOpt = payOrderChannelManager.findByPaymentIdAndChannel(payOrder.getId(), PayChannelEnum.WECHAT.getCode());
if (!payOrderChannelOpt.isPresent()){
payOrderChannelManager.deleteByPaymentIdAndAsync(payOrder.getId());
payOrderChannelManager.save(new PayOrderChannel()
.setPaymentId(payOrder.getId())
.setChannel(PayChannelEnum.ALI.getCode())
.setAmount(payWayParam.getAmount())
.setPayWay(payWayParam.getWay())
.setChannelExtra(payWayParam.getChannelExtra())
.setAmount(payChannelParam.getAmount())
.setPayWay(payChannelParam.getWay())
.setChannelExtra(payChannelParam.getChannelExtra())
.setAsync(true)
);
} else {
payOrderChannelOpt.get()
.setChannelExtra(payWayParam.getChannelExtra())
.setPayWay(payWayParam.getWay());
.setChannelExtra(payChannelParam.getChannelExtra())
.setPayWay(payChannelParam.getWay());
payOrderChannelManager.updateById(payOrderChannelOpt.get());
}
}

View File

@@ -43,6 +43,9 @@ public class PayApiConfig extends MpBaseEntity implements EntityBaseFunction<Pay
@DbColumn(comment = "是否开启回调通知")
private boolean notice;
@DbColumn(comment = "只有异步支付才进行通知")
private boolean onlyAsyncNotice;
@DbColumn(comment = "默认回调地址")
private String noticeUrl;

View File

@@ -8,7 +8,6 @@ import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -30,9 +29,9 @@ public class PayExpiredTimeTask {
private final LockTemplate lockTemplate;
@Scheduled(cron = "*/5 * * * * ?")
// @Scheduled(cron = "*/5 * * * * ?")
public void task(){
log.info("执行超时取消任务....");
log.debug("执行超时取消任务....");
Set<String> expiredKeys = repository.getExpiredKeys(LocalDateTime.now());
for (String expiredKey : expiredKeys) {
LockInfo lock = lockTemplate.lock("payment:expired:" + expiredKey,10000,0);

View File

@@ -29,7 +29,7 @@ public class PayWaitOrderSyncTask {
private final LockTemplate lockTemplate;
public void task(){
log.info("开始同步支付订单");
log.debug("开始同步支付订单状态");
// 从超时订单列表中获取到未超时的订单号
Set<String> keys = repository.getNormalKeysBy30Day();
for (String key : keys) {

View File

@@ -4,7 +4,7 @@ import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.service.common.exception.ExceptionInfo;
import cn.bootx.platform.daxpay.service.core.record.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.param.pay.PayParam;
import cn.bootx.platform.daxpay.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.param.pay.PayChannelParam;
import lombok.Getter;
import lombok.Setter;
@@ -26,7 +26,7 @@ public abstract class AbsPayStrategy implements PayStrategy{
private PayParam payParam = null;
/** 支付方式参数 支付参数中的与这个不一致, 以这个为准 */
private PayWayParam payWayParam = null;
private PayChannelParam payChannelParam = null;
/**
* 策略标识

View File

@@ -2,17 +2,16 @@ package cn.bootx.platform.daxpay.core.payment.common.service;
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.param.pay.PayWayParam;
import cn.bootx.platform.daxpay.util.PaySignUtil;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.kit.PayKit;
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;
/**
* 支付签名服务
@@ -35,7 +34,7 @@ class PaymentSignServiceTest {
// 传入的话需要传输时间戳
payParam.setReqTime(LocalDateTime.now());
PayWayParam p1 = new PayWayParam();
PayChannelParam p1 = new PayChannelParam();
p1.setAmount(100);
p1.setChannel("wechat");
p1.setWay("wx_app");
@@ -43,7 +42,7 @@ class PaymentSignServiceTest {
aliPayParam.setAuthCode("6688");
p1.setChannelExtra(JSONUtil.toJsonStr(aliPayParam));
PayWayParam p2 = new PayWayParam();
PayChannelParam p2 = new PayChannelParam();
p2.setAmount(100);
p2.setChannel("wechat");
p2.setWay("wx_app");
@@ -51,19 +50,15 @@ class PaymentSignServiceTest {
weChatPayParam.setOpenId("w2qsz2xawe3gbhyyff28fs01fd");
weChatPayParam.setAuthCode("8866");
p2.setChannelExtra(JSONUtil.toJsonStr(weChatPayParam));
List<PayWayParam> payWays = Arrays.asList(p1, p2);
payParam.setPayWays(payWays);
Map<String, String> map = payParam.toMap();
log.info("map: {}",map);
String data = PayKit.createLinkString(map);
List<PayChannelParam> payWays = Arrays.asList(p1, p2);
payParam.setPayChannels(payWays);
log.info("拼接字符串: {}",data);
// 签名
String sign = "123456";
data += "&sign="+sign;
data = data.toUpperCase();
log.info("添加秘钥并转换为大写的字符串: {}",data);
log.info("MD5: {}",PayKit.md5(data));
log.info("HmacSHA256: {}",PayKit.hmacSha256(data,sign));
String md5Sign = PaySignUtil.md5Sign(payParam, sign);
String hmacSha256Sign = PaySignUtil.hmacSha256Sign(payParam, sign);
log.info("MD5: {}",md5Sign);
log.info("HmacSHA256: {}", hmacSha256Sign);
}

View File

@@ -146,3 +146,17 @@ table-modify:
database-type: mysql
fail-fast: true
scan-package: cn.bootx.platform.daxpay
dromara:
#文件存储配置,
x-file-storage:
#默认使用的存储平台
default-platform: local
#缩略图后缀,例如【.min.jpg】【.png】
thumbnail-suffix: ".min.jpg"
local-plus:
# 不启用自带的访问映射, 使用自定义的访问实现
# 存储平台标识
- platform: local
enableStorage: true
# 文件存储路径
storage-path: D:/data/files/