ref 回调相关逻辑调整, 去除继承关系. 补充签名

This commit is contained in:
DaxPay
2024-04-26 18:04:45 +08:00
parent 69b6b91f06
commit bf135eec40
30 changed files with 420 additions and 267 deletions

View File

@@ -31,6 +31,11 @@ public class CallbackLocal {
*/
private String outTradeNo;
/**
* 通道
*/
private String channel;
/**
* 三方支付系统返回状态
* @see PayStatusEnum 支付状态

View File

@@ -4,11 +4,15 @@ import cn.bootx.platform.common.core.util.CertUtil;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.service.code.PayCallbackStatusEnum;
import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum;
import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum;
import cn.bootx.platform.daxpay.service.common.context.CallbackLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.channel.alipay.entity.AliPayConfig;
import cn.bootx.platform.daxpay.service.func.AbsCallbackStrategy;
import cn.bootx.platform.daxpay.service.core.payment.callback.service.PayCallbackService;
import cn.bootx.platform.daxpay.service.core.payment.callback.service.RefundCallbackService;
import cn.bootx.platform.daxpay.service.core.record.callback.service.PayCallbackRecordService;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
@@ -16,11 +20,11 @@ import cn.hutool.json.JSONUtil;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConstants;
import com.alipay.api.internal.util.AlipaySignature;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
@@ -35,25 +39,65 @@ import static cn.bootx.platform.daxpay.service.code.AliPayCode.*;
*/
@Slf4j
@Service
public class AliPayCallbackService extends AbsCallbackStrategy {
@RequiredArgsConstructor
public class AliPayCallbackService {
@Resource
private AliPayConfigService aliasConfigService;
private final AliPayConfigService aliPayConfigService;
private final PayCallbackRecordService callbackService;
private final PayCallbackService payCallbackService;
private final RefundCallbackService refundCallbackService;
/**
* 策略标识
* 回调处理入口
*/
@Override
public PayChannelEnum getChannel() {
return PayChannelEnum.ALI;
public String callback(Map<String, String> params){
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
try {
// 将参数写入到上下文中
callbackInfo.getCallbackParam().putAll(params);
// 判断并保存回调类型
PaymentTypeEnum callbackType = this.getCallbackType();
callbackInfo.setCallbackType(callbackType)
.setChannel(PayChannelEnum.ALI.getCode());
// 验证消息
if (!this.verifyNotify()) {
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("验证信息格式不通过");
// 消息有问题, 保存记录并返回
callbackService.saveCallbackRecord();
return null;
}
// 提前设置订单修复的来源
PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK);
if (callbackType == PaymentTypeEnum.PAY){
// 解析支付数据并放处理
this.resolvePayData();
payCallbackService.payCallback();
} else {
// 解析退款数据并放处理
this.resolveRefundData();
refundCallbackService.refundCallback();
}
callbackService.saveCallbackRecord();
return this.getReturnMsg();
} catch (Exception e) {
log.error("回调处理失败", e);
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("回调处理失败: "+e.getMessage());
callbackService.saveCallbackRecord();
throw e;
}
}
/**
* 验证信息格式是否合法
*/
@SneakyThrows
@Override
public boolean verifyNotify() {
Map<String, String> params =PaymentContextLocal.get().getCallbackInfo().getCallbackParam();
String callReq = JSONUtil.toJsonStr(params);
@@ -63,7 +107,7 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
log.error("支付宝回调报文appId为空");
return false;
}
AliPayConfig alipayConfig = aliasConfigService.getConfig();
AliPayConfig alipayConfig = aliPayConfigService.getConfig();
if (Objects.isNull(alipayConfig)) {
log.error("支付宝支付配置不存在");
return false;
@@ -84,7 +128,6 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
/**
* 解析支付数据并放到上下文中
*/
@Override
public void resolvePayData() {
CallbackLocal callback = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callback.getCallbackParam();
@@ -112,7 +155,6 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
* 解析退款回调数据并放到上下文中
* 注意: 支付宝退款没有网关订单号, 网关订单号是支付单的
*/
@Override
public void resolveRefundData() {
CallbackLocal callback = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callback.getCallbackParam();
@@ -138,7 +180,6 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
*
* @see PaymentTypeEnum
*/
@Override
public PaymentTypeEnum getCallbackType() {
CallbackLocal callback = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callback.getCallbackParam();
@@ -152,9 +193,8 @@ public class AliPayCallbackService extends AbsCallbackStrategy {
}
/**
* 返回响应结果
* 返回
*/
@Override
public String getReturnMsg() {
return "success";
}

View File

@@ -4,11 +4,16 @@ import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
import cn.bootx.platform.daxpay.service.code.PayCallbackStatusEnum;
import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum;
import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum;
import cn.bootx.platform.daxpay.service.code.UnionPayCode;
import cn.bootx.platform.daxpay.service.common.context.CallbackLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.channel.union.entity.UnionPayConfig;
import cn.bootx.platform.daxpay.service.core.payment.callback.service.PayCallbackService;
import cn.bootx.platform.daxpay.service.core.payment.callback.service.RefundCallbackService;
import cn.bootx.platform.daxpay.service.core.record.callback.service.PayCallbackRecordService;
import cn.bootx.platform.daxpay.service.func.AbsCallbackStrategy;
import cn.bootx.platform.daxpay.service.sdk.union.api.UnionPayKit;
import cn.hutool.core.date.DatePattern;
@@ -35,15 +40,62 @@ import static cn.bootx.platform.daxpay.service.code.UnionPayCode.*;
@Slf4j
@Service
@RequiredArgsConstructor
public class UnionPayCallbackService extends AbsCallbackStrategy {
public class UnionPayCallbackService {
@Resource
private UnionPayConfigService unionPayConfigService;
private final UnionPayConfigService unionPayConfigService;
private final PayCallbackRecordService callbackService;
private final PayCallbackService payCallbackService;
private final RefundCallbackService refundCallbackService;
/**
* 回调处理入口
*/
public String callback(Map<String, String> params){
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
try {
// 将参数写入到上下文中
callbackInfo.getCallbackParam().putAll(params);
// 判断并保存回调类型
PaymentTypeEnum callbackType = this.getCallbackType();
callbackInfo.setCallbackType(callbackType)
.setChannel(PayChannelEnum.ALI.getCode());
// 验证消息
if (!this.verifyNotify()) {
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("验证信息格式不通过");
// 消息有问题, 保存记录并返回
callbackService.saveCallbackRecord();
return null;
}
// 提前设置订单修复的来源
PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK);
if (callbackType == PaymentTypeEnum.PAY){
// 解析支付数据并放处理
this.resolvePayData();
payCallbackService.payCallback();
} else {
// 解析退款数据并放处理
this.resolveRefundData();
refundCallbackService.refundCallback();
}
callbackService.saveCallbackRecord();
return this.getReturnMsg();
} catch (Exception e) {
log.error("回调处理失败", e);
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("回调处理失败: "+e.getMessage());
callbackService.saveCallbackRecord();
throw e;
}
}
/**
* 验证信息格式
*/
@Override
public boolean verifyNotify() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> params = callbackInfo.getCallbackParam();
@@ -64,7 +116,6 @@ public class UnionPayCallbackService extends AbsCallbackStrategy {
*
* @see PaymentTypeEnum
*/
@Override
public PaymentTypeEnum getCallbackType() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> params = callbackInfo.getCallbackParam();
@@ -79,7 +130,6 @@ public class UnionPayCallbackService extends AbsCallbackStrategy {
/**
* 解析支付回调数据并放到上下文中
*/
@Override
public void resolvePayData() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callbackInfo.getCallbackParam();
@@ -107,7 +157,6 @@ public class UnionPayCallbackService extends AbsCallbackStrategy {
/**
* 解析退款回调数据并放到上下文中
*/
@Override
public void resolveRefundData() {
// 云闪付需要延迟半秒再进行处理, 不然会出现业务未处理完, 但回调已经到达的情况
ThreadUtil.sleep(100);
@@ -139,16 +188,8 @@ public class UnionPayCallbackService extends AbsCallbackStrategy {
/**
* 返回响应结果
*/
@Override
public String getReturnMsg() {
return "success";
}
/**
* 策略标识
*/
@Override
public PayChannelEnum getChannel() {
return PayChannelEnum.UNION_PAY;
}
}

View File

@@ -4,16 +4,23 @@ import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
import cn.bootx.platform.daxpay.service.code.PayCallbackStatusEnum;
import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum;
import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum;
import cn.bootx.platform.daxpay.service.common.context.CallbackLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.channel.alipay.service.AliPayConfigService;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.payment.callback.service.PayCallbackService;
import cn.bootx.platform.daxpay.service.core.payment.callback.service.RefundCallbackService;
import cn.bootx.platform.daxpay.service.core.record.callback.service.PayCallbackRecordService;
import cn.bootx.platform.daxpay.service.func.AbsCallbackStrategy;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.kit.WxPayKit;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -34,22 +41,59 @@ import static cn.bootx.platform.daxpay.service.code.WeChatPayCode.*;
*/
@Slf4j
@Service
public class WeChatPayCallbackService extends AbsCallbackStrategy {
@Resource
private WeChatPayConfigService weChatPayConfigService;
@RequiredArgsConstructor
public class WeChatPayCallbackService {
private final WeChatPayConfigService weChatPayConfigService;
private final PayCallbackRecordService callbackService;
private final PayCallbackService payCallbackService;
private final RefundCallbackService refundCallbackService;
/**
* 策略标识
* 回调处理入口
*/
@Override
public PayChannelEnum getChannel() {
return PayChannelEnum.WECHAT;
public String callback(Map<String, String> params){
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
try {
// 将参数写入到上下文中
callbackInfo.getCallbackParam().putAll(params);
// 判断并保存回调类型
PaymentTypeEnum callbackType = this.getCallbackType();
callbackInfo.setCallbackType(callbackType)
.setChannel(PayChannelEnum.ALI.getCode());
// 验证消息
if (!this.verifyNotify()) {
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("验证信息格式不通过");
// 消息有问题, 保存记录并返回
callbackService.saveCallbackRecord();
return null;
}
// 提前设置订单修复的来源
PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK);
if (callbackType == PaymentTypeEnum.PAY){
// 解析支付数据并放处理
this.resolvePayData();
payCallbackService.payCallback();
} else {
// 解析退款数据并放处理
this.resolveRefundData();
refundCallbackService.refundCallback();
}
callbackService.saveCallbackRecord();
return this.getReturnMsg();
} catch (Exception e) {
log.error("回调处理失败", e);
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("回调处理失败: "+e.getMessage());
callbackService.saveCallbackRecord();
throw e;
}
}
/**
* 验证回调消息
*/
@Override
public boolean verifyNotify() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> params = callbackInfo.getCallbackParam();
@@ -79,7 +123,6 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
/**
* 解析支付数据放到上下文中
*/
@Override
public void resolvePayData() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callbackInfo.getCallbackParam();
@@ -105,7 +148,6 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
/**
* 解析退款回调数据并放到上下文中, 微信退款通知返回的数据需要进行解密
*/
@Override
public void resolveRefundData() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callbackInfo.getCallbackParam();
@@ -145,7 +187,6 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
*
* @see PaymentTypeEnum
*/
@Override
public PaymentTypeEnum getCallbackType() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
Map<String, String> callbackParam = callbackInfo.getCallbackParam();
@@ -159,7 +200,6 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
/**
* 返回响应结果
*/
@Override
public String getReturnMsg() {
Map<String, String> xml = new HashMap<>(4);
xml.put(RETURN_CODE, "SUCCESS");

View File

@@ -1,15 +0,0 @@
package cn.bootx.platform.daxpay.service.core.order.refund.convert;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
*
* @author xxm
* @since 2024/1/17
*/
@Mapper
public interface RefundOrderChannelConvert {
RefundOrderChannelConvert CONVERT = Mappers.getMapper(RefundOrderChannelConvert.class);
}

View File

@@ -2,7 +2,9 @@ package cn.bootx.platform.daxpay.service.core.order.refund.convert;
import cn.bootx.platform.daxpay.result.order.RefundOrderResult;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrderExtra;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderDto;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderExtraDto;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@@ -17,6 +19,8 @@ public interface RefundOrderConvert {
RefundOrderDto convert(RefundOrder in);
RefundOrderExtraDto convert(RefundOrderExtra in);
RefundOrderResult convertResult(RefundOrder in);

View File

@@ -1,9 +1,12 @@
package cn.bootx.platform.daxpay.service.core.order.refund.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpBaseEntity;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.channel.WalletPayParam;
import cn.bootx.platform.daxpay.param.channel.WeChatPayParam;
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderConvert;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderExtraDto;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
@@ -25,7 +28,7 @@ import java.time.LocalDateTime;
@Accessors(chain = true)
@TableName("pay_refund_order_extra")
@DbTable(comment = "退款订单扩展信息")
public class RefundOrderExtra extends MpBaseEntity {
public class RefundOrderExtra extends MpBaseEntity implements EntityBaseFunction<RefundOrderExtraDto> {
/** 异步通知地址 */
@DbColumn(comment = "异步通知地址")
@@ -54,4 +57,8 @@ public class RefundOrderExtra extends MpBaseEntity {
@DbColumn(comment = "支付终端ip")
private String clientIp;
@Override
public RefundOrderExtraDto toDto() {
return RefundOrderConvert.CONVERT.convert(this);
}
}

View File

@@ -8,9 +8,12 @@ import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.param.payment.refund.QueryRefundParam;
import cn.bootx.platform.daxpay.result.order.RefundOrderResult;
import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderConvert;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundOrderExtraManager;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrderExtra;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderDto;
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundOrderExtraDto;
import cn.bootx.platform.daxpay.service.param.order.RefundOrderQuery;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -31,7 +34,7 @@ import java.util.Optional;
@RequiredArgsConstructor
public class RefundOrderQueryService {
private final RefundOrderManager refundOrderManager;
private final RefundOrderExtraManager refundOrderExtraManager;
/**
* 分页查询
@@ -49,6 +52,14 @@ public class RefundOrderQueryService {
.orElseThrow(() -> new DataNotExistException("退款订单不存在"));
}
/**
* 根据id查询扩展信息
*/
public RefundOrderExtraDto findExtraById(Long id) {
return refundOrderExtraManager.findById(id).map(RefundOrderExtra::toDto)
.orElseThrow(() -> new DataNotExistException("退款订单扩展信息不存在"));
}
/**
* 根据退款号和商户退款号查询
*/

View File

@@ -6,6 +6,7 @@ import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.payment.pay.PayCloseParam;
import cn.bootx.platform.daxpay.result.pay.PayCloseResult;
import cn.bootx.platform.daxpay.result.pay.PayResult;
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.order.pay.entity.PayOrder;
@@ -65,12 +66,12 @@ public class PayCloseService {
* 关闭支付记录
*/
private PayCloseResult close(PayOrder payOrder) {
PayCloseResult result = new PayCloseResult();
try {
// 状态检查, 只有支付中可以进行取消支付
if (!Objects.equals(payOrder.getStatus(), PayStatusEnum.PROGRESS.getCode())) {
throw new PayFailureException("订单不是支付中, 无法进行关闭订单");
}
AbsPayCloseStrategy strategy = PayCloseStrategyFactory.create(payOrder.getChannel());
// 设置支付订单
strategy.setOrder(payOrder);
@@ -78,15 +79,18 @@ public class PayCloseService {
strategy.doBeforeCloseHandler();
// 执行关闭策略
strategy.doCloseHandler();
// 成功处理
this.successHandler(payOrder);
// 签名
this.sign(result);
// 返回结果
return this.sign(new PayCloseResult());
return result;
} catch (Exception e) {
// 记录关闭失败的记录
this.saveRecord(payOrder, false, e.getMessage());
PayCloseResult payCloseResult = new PayCloseResult();
payCloseResult.setCode("1").setMsg(e.getMessage());
return this.sign(payCloseResult);
result.setCode("1").setMsg(e.getMessage());
this.sign(result);
return result;
}
}
@@ -123,7 +127,7 @@ public class PayCloseService {
/**
* 对返回结果进行签名
*/
private PayCloseResult sign(PayCloseResult result){
private void sign(PayCloseResult result){
PlatformLocal platformInfo = PaymentContextLocal.get()
.getPlatformInfo();
String signType = platformInfo.getSignType();
@@ -134,6 +138,5 @@ public class PayCloseService {
} else {
throw new PayFailureException("未获取到签名方式,请检查");
}
return result;
}
}

View File

@@ -26,7 +26,7 @@ public class PaymentSignService {
private final PaymentAssistService paymentAssistService;;
/**
* 签名
* 入参签名校验
*/
public void verifySign(PaymentCommonParam param) {
// 先触发上下文的初始化

View File

@@ -245,11 +245,19 @@ public class PayAssistService {
payResult.setStatus(payOrder.getStatus());
// 设置支付参数
PayLocal asyncPayInfo = PaymentContextLocal.get().getPayInfo();;
PayLocal asyncPayInfo = PaymentContextLocal.get()
.getPayInfo();
if (StrUtil.isNotBlank(asyncPayInfo.getPayBody())) {
payResult.setPayBody(asyncPayInfo.getPayBody());
}
// 签名
this.sign(payResult);
return payResult;
}
/**
* 对返回
*/
public void sign(PayResult payResult) {
// 进行签名
PlatformLocal platformInfo = PaymentContextLocal.get()
.getPlatformInfo();
@@ -261,6 +269,5 @@ public class PayAssistService {
} else {
throw new PayFailureException("未获取到签名方式,请检查");
}
return payResult;
}
}

View File

@@ -86,7 +86,7 @@ public class PayService {
}
/**
* 首次支付
* 首次支付 无事务
* 拆分为多阶段1. 保存订单记录信息 2 调起支付 3. 支付成功后操作
*/
public PayResult firstPay(PayParam payParam){
@@ -104,6 +104,7 @@ public class PayService {
payStrategy.doPayHandler();
} catch (Exception e) {
payOrder.setErrorMsg(e.getMessage());
// 这个方法没有事务, 所以可以正常更新
payOrderService.updateById(payOrder);
throw e;
}
@@ -182,7 +183,6 @@ public class PayService {
payOrderService.updateById(payOrder);
// 如果支付完成 发送通知
if (Objects.equals(payOrder.getStatus(), SUCCESS.getCode())){
// 查询通道订单
clientNoticeService.registerPayNotice(payOrder, payOrderExtra);
}
return payAssistService.buildResult(payOrder);

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.refund.service;
import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.bootx.platform.common.core.util.CollUtil;
import cn.bootx.platform.common.core.util.ValidationUtil;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
@@ -13,12 +14,15 @@ import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundOrderExtraManager;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.RefundOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.RefundOrderExtra;
import cn.bootx.platform.daxpay.service.core.payment.notice.service.ClientNoticeService;
import cn.bootx.platform.daxpay.service.core.payment.refund.factory.RefundStrategyFactory;
import cn.bootx.platform.daxpay.service.func.AbsRefundStrategy;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
@@ -52,26 +56,31 @@ public class RefundService {
private final PayOrderQueryService payOrderQueryService;
private final LockTemplate lockTemplate;
private final RefundOrderExtraManager refundOrderExtraManager;
/**
* 分支付通道进行退款
* 1. 创建退款订单和通道订单并保存(单独事务)
* 1. 创建退款订单(单独事务)
* 2. 调用API发起退款(异步退款)
* 3. 根据API返回信息更新退款订单信息
*/
public RefundResult refund(RefundParam param){
RefundResult result = new RefundResult()
.setRefundNo(param.getBizRefundNo())
.setBizRefundNo(param.getBizRefundNo());
// 参数校验
ValidationUtil.validateParam(param);
// 加锁
LockInfo lock = lockTemplate.lock("payment:refund:" + param.getBizRefundNo(),10000,200);
if (Objects.isNull(lock)){
throw new RepetitiveOperationException("退款处理中,请勿重复操作");
result.setMsg("退款处理中,请勿重复操作");
return result;
}
try {
// 判断是否是首次发起退款
Optional<RefundOrder> refund = refundOrderManager.findByBizRefundNo(param.getBizRefundNo());
if (refund.isPresent()){
return this.repeatRefund(refund.get());
return this.repeatRefund(refund.get(),param);
} else {
return this.firstRefund(param);
}
@@ -90,26 +99,24 @@ public class RefundService {
.orElseThrow(() -> new DataNotExistException("支付订单不存在"));
// 检查退款参数
refundAssistService.checkAndParam(param, payOrder);
// ----------------------------- 发起退款操作 --------------------------------------------
// 通过退款参数获取退款策略
AbsRefundStrategy refundStrategy = RefundStrategyFactory.create(payOrder.getChannel());
// 设置支付订单数据
refundStrategy.setPayOrder(payOrder);
// 进行退款前预处理
refundStrategy.doBeforeRefundHandler();
// 退款操作的预处理, 使用独立的新事物进行发起, 返回创建成功的退款订单, 成功后才可以进行下一阶段的操作
// 退款操作的预处理, 对支付订单进行预扣款, 返回创建成功的退款订单, 成功后才可以进行下一阶段的操作
RefundOrder refundOrder = SpringUtil.getBean(this.getClass()).preRefundMethod(param, payOrder);
try {
// 3.2 执行退款策略
// 执行退款策略
refundStrategy.doRefundHandler();
}
catch (Exception e) {
// 5. 失败处理, 所有记录都会回滚, 可以调用重新
// 记录处理失败状态
PaymentContextLocal.get().getRefundInfo().setStatus(RefundStatusEnum.FAIL);
// 记录退款失败的记录
// 更新退款失败的记录
refundAssistService.updateOrderByError(refundOrder);
return refundAssistService.buildResult(refundOrder);
}
SpringUtil.getBean(this.getClass()).successHandler(refundOrder, payOrder);
return refundAssistService.buildResult(refundOrder);
@@ -133,31 +140,37 @@ public class RefundService {
/**
* 重新发起退款处理
* 1. 查出相关退款订单
* 2. 构建退款策略, 发起退款
* 2. 更新退款扩展参数
* 3. 构建退款策略, 发起退款
*/
public RefundResult repeatRefund(RefundOrder refundOrder){
public RefundResult repeatRefund(RefundOrder refundOrder, RefundParam param){
// 退款失败才可以重新发起退款, 重新发起退款
if (!Objects.equals(refundOrder.getStatus(), RefundStatusEnum.FAIL.getCode())){
throw new PayFailureException("只有失败状态的才可以重新发起退款");
}
// 获取支付订单
PayOrder payOrder = payOrderQueryService.findByBizOrOrderNo(refundOrder.getOrderNo(), refundOrder.getBizOrderNo())
.orElseThrow(() -> new DataNotExistException("支付订单不存在"));
RefundOrderExtra refundOrderExtra = refundOrderExtraManager.findById(refundOrder.getId())
.orElseThrow(() -> new DataNotExistException("退款订单扩展信息不存在"));
AbsRefundStrategy refundStrategy = RefundStrategyFactory.create(refundOrder.getRefundNo());
// 设置退款订单对象
refundStrategy.setRefundOrder(refundOrder);
// 退款前准备操作
refundStrategy.doBeforeRefundHandler();
// 进行发起退款前的操作, 更新扩展记录信息
this.updateExtra(refundOrderExtra, param);
try {
// 执行退款策略
refundStrategy.doRefundHandler();
}
catch (Exception e) {
// 失败处理, 所有记录都会回滚, 可以调用退款重试
// 记录处理失败状态
PaymentContextLocal.get().getRefundInfo().setStatus(RefundStatusEnum.FAIL);
// 记录退款失败的记录
refundAssistService.updateOrderByError(refundOrder);
// 返回错误响应对象
return refundAssistService.buildResult(refundOrder);
}
// 退款发起成功处理
SpringUtil.getBean(this.getClass()).successHandler(refundOrder, payOrder);
@@ -165,7 +178,21 @@ public class RefundService {
}
/**
* 成功处理, 更新退款订单, 退款通道订单, 支付订单, 支付通道订单
* 更新退款订单扩展信息
*/
private void updateExtra(RefundOrderExtra refundOrderExtra, RefundParam param){
refundOrderExtra.setAttach(param.getAttach())
.setClientIp(param.getClientIp())
.setNotifyUrl(param.getNotifyUrl())
.setReqTime(param.getReqTime());
if (CollUtil.isNotEmpty(param.getExtraParam())){
refundOrderExtra.setExtraParam(JSONUtil.toJsonStr(param.getExtraParam()));
}
refundOrderExtraManager.updateById(refundOrderExtra);
}
/**
* 成功处理, 更新退款订单, 支付订单,
*/
@Transactional(rollbackFor = Exception.class)
public void successHandler(RefundOrder refundOrder, PayOrder payOrder) {
@@ -182,7 +209,7 @@ public class RefundService {
}
payOrderService.updateById(payOrder);
// 更新退款订单和相关通道订单
// 更新退款订单
refundAssistService.updateOrder(refundOrder);
// 发送通知

View File

@@ -4,14 +4,17 @@ import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PaySignTypeEnum;
import cn.bootx.platform.daxpay.code.PayStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.payment.pay.PaySyncParam;
import cn.bootx.platform.daxpay.result.CommonResult;
import cn.bootx.platform.daxpay.result.pay.SyncResult;
import cn.bootx.platform.daxpay.service.code.PayRepairSourceEnum;
import cn.bootx.platform.daxpay.service.code.PayRepairWayEnum;
import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum;
import cn.bootx.platform.daxpay.service.common.context.PlatformLocal;
import cn.bootx.platform.daxpay.service.common.context.RepairLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
@@ -24,6 +27,7 @@ import cn.bootx.platform.daxpay.service.core.payment.sync.result.PaySyncResult;
import cn.bootx.platform.daxpay.service.core.record.sync.entity.PaySyncRecord;
import cn.bootx.platform.daxpay.service.core.record.sync.service.PaySyncRecordService;
import cn.bootx.platform.daxpay.service.func.AbsPaySyncStrategy;
import cn.bootx.platform.daxpay.util.PaySignUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import lombok.RequiredArgsConstructor;
@@ -64,21 +68,16 @@ public class PaySyncService {
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public SyncResult sync(PaySyncParam param) {
PayOrder payOrder = null;
if (Objects.nonNull(param.getOrderNo())){
payOrder = payOrderQueryService.findByOrderNo(param.getOrderNo())
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
if (Objects.isNull(payOrder)){
payOrder = payOrderQueryService.findByBizOrderNo(param.getBizOrderNo())
.orElseThrow(() -> new PayFailureException("未查询到支付订单"));
}
SyncResult syncResult = new SyncResult();
PayOrder payOrder = payOrderQueryService.findByBizOrOrderNo(param.getBizOrderNo(), param.getOrderNo())
.orElseThrow(() -> new BizException("支付订单不存在"));
// 钱包支付直接返回结果
if (PayChannelEnum.WALLET.getCode().equals(payOrder.getChannel())){
throw new PayFailureException("订单没有异步支付方式,不需要同步");
}
// 执行订单同步逻辑
return this.syncPayOrder(payOrder);
syncResult = this.syncPayOrder(payOrder);
return syncResult;
}
/**
* 同步支付状态, 开启一个新的事务, 不受外部抛出异常的影响
@@ -138,7 +137,7 @@ public class PaySyncService {
return new SyncResult()
.setGatewayStatus(syncResult.getSyncStatus().getCode())
.setRepair(!statusSync)
.setRepairOrderNo(repairResult.getRepairNo());
.setRepairNo(repairResult.getRepairNo());
} finally {
lockTemplate.releaseLock(lock);
}
@@ -260,4 +259,20 @@ public class PaySyncService {
.setClientIp(PaymentContextLocal.get().getRequestInfo().getClientIp());
paySyncRecordService.saveRecord(paySyncRecord);
}
/**
* 签名
*/
private void sign(CommonResult result){
PlatformLocal platformInfo = PaymentContextLocal.get()
.getPlatformInfo();
String signType = platformInfo.getSignType();
if (Objects.equals(PaySignTypeEnum.HMAC_SHA256.getCode(), signType)){
result.setSign(PaySignUtil.hmacSha256Sign(result, platformInfo.getSignSecret()));
} else if (Objects.equals(PaySignTypeEnum.MD5.getCode(), signType)){
result.setSign(PaySignUtil.md5Sign(result, platformInfo.getSignSecret()));
} else {
throw new PayFailureException("未获取到签名方式,请检查");
}
}
}

View File

@@ -121,7 +121,7 @@ public class RefundSyncService {
return new SyncResult()
.setGatewayStatus(syncResult.getSyncStatus().getCode())
.setRepair(!statusSync)
.setRepairOrderNo(repairResult.getRepairNo());
.setRepairNo(repairResult.getRepairNo());
} finally {
lockTemplate.releaseLock(lock);
}

View File

@@ -4,10 +4,13 @@ import cn.bootx.platform.common.core.exception.DataNotExistException;
import cn.bootx.platform.common.core.rest.PageResult;
import cn.bootx.platform.common.core.rest.param.PageParam;
import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.daxpay.service.common.context.CallbackLocal;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.record.callback.dao.PayCallbackRecordManager;
import cn.bootx.platform.daxpay.service.core.record.callback.entity.PayCallbackRecord;
import cn.bootx.platform.daxpay.service.dto.record.callback.PayCallbackRecordDto;
import cn.bootx.platform.daxpay.service.param.record.PayCallbackRecordQuery;
import cn.hutool.json.JSONUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -43,7 +46,17 @@ public class PayCallbackRecordService {
* 保存回调记录
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void save(PayCallbackRecord record) {
callbackRecordManager.save(record);
public void saveCallbackRecord() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
PayCallbackRecord payNotifyRecord = new PayCallbackRecord()
.setTradeNo(callbackInfo.getTradeNo())
.setOutTradeNo(callbackInfo.getOutTradeNo())
.setChannel(callbackInfo.getChannel())
.setNotifyInfo(JSONUtil.toJsonStr(callbackInfo.getCallbackParam()))
.setCallbackType(callbackInfo.getCallbackType().getCode())
.setRepairOrderNo(callbackInfo.getRepairNo())
.setStatus(callbackInfo.getCallbackStatus().getCode())
.setMsg(callbackInfo.getMsg());
callbackRecordManager.save(payNotifyRecord);
}
}

View File

@@ -1,11 +1,20 @@
package cn.bootx.platform.daxpay.service.dto.order.pay;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 支付订单和扩展信息
* @author xxm
* @since 2024/4/26
*/
@Data
@Schema(title = "支付订单和扩展信息")
public class PayOrderDetailDto {
@Schema(description = "支付订单")
private PayOrderDto payOrder;
@Schema(description = "支付订单扩展信息")
private PayOrderExtraDto payOrderExtra;
}

View File

@@ -47,7 +47,7 @@ public class PayOrderDto extends BaseDto {
/**
* 异步支付通道
* @see PayChannelEnum#ASYNC_TYPE_CODE
* @see PayChannelEnum
*/
@Schema(description = "异步支付通道")
private String asyncChannel;

View File

@@ -1,48 +0,0 @@
package cn.bootx.platform.daxpay.service.dto.order.refund;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.code.RefundStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
/**
* 支付退款通道订单
* @author xxm
* @since 2024/1/17
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "支付退款通道订单")
public class RefundChannelOrderDto extends BaseDto {
@Schema(description = "关联退款id")
private Long refundId;
@Schema(description = "通道")
private String channel;
@Schema(description = "通道支付单id")
private Long payChannelId;
@Schema(description = "异步支付方式")
private boolean async;
@Schema(description = "订单金额")
private Integer orderAmount;
@Schema(description = "退款金额")
private Integer amount;
@Schema(description = "剩余可退余额")
private Integer refundableAmount;
/**
* 退款状态
* @see RefundStatusEnum
*/
@Schema(description = "退款状态")
private String status;
}

View File

@@ -41,7 +41,7 @@ public class RefundOrderDto extends BaseDto {
/**
* 异步通道
* @see PayChannelEnum#ASYNC_TYPE_CODE
* @see PayChannelEnum
*/
@Schema(description = "异步通道")
private String asyncChannel;

View File

@@ -0,0 +1,47 @@
package cn.bootx.platform.daxpay.service.dto.order.refund;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.param.channel.AliPayParam;
import cn.bootx.platform.daxpay.param.channel.WalletPayParam;
import cn.bootx.platform.daxpay.param.channel.WeChatPayParam;
import cn.bootx.table.modify.annotation.DbColumn;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 退款订单扩展信息
* @author xxm
* @since 2024/4/26
*/
@EqualsAndHashCode(callSuper = true)
@Data
@Accessors(chain = true)
@Schema(title = "退款订单扩展信息")
public class RefundOrderExtraDto extends BaseDto {
/** 异步通知地址 */
private String notifyUrl;
/** 商户扩展参数,回调时会原样返回, 以最后一次为准 */
private String attach;
/**
* 附加参数 以最后一次为准
* @see AliPayParam
* @see WeChatPayParam
* @see WalletPayParam
*/
private String extraParam;
/** 请求时间,时间戳转时间 */
private LocalDateTime reqTime;
/** 终端ip */
private String clientIp;
}

View File

@@ -22,96 +22,6 @@ import java.util.Map;
* @since 2021/6/21
*/
@Slf4j
@Deprecated
public abstract class AbsCallbackStrategy implements PayStrategy {
@Resource
private PayCallbackRecordService callbackRecordService;
@Resource
private PayCallbackService payCallbackService;
@Resource
private RefundCallbackService refundCallbackService;
/**
* 回调处理入口
*/
public String callback(Map<String, String> params) {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
try {
// 将参数写入到上下文中
callbackInfo.getCallbackParam().putAll(params);
// 判断并保存回调类型
PaymentTypeEnum callbackType = this.getCallbackType();
callbackInfo.setCallbackType(callbackType);
// 验证消息
if (!this.verifyNotify()) {
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("验证信息格式不通过");
// 消息有问题, 保存记录并返回
this.saveCallbackRecord();
return null;
}
// 提前设置订单修复的来源
PaymentContextLocal.get().getRepairInfo().setSource(PayRepairSourceEnum.CALLBACK);
if (callbackType == PaymentTypeEnum.PAY){
// 解析支付数据并放处理
this.resolvePayData();
payCallbackService.payCallback();
} else {
// 解析退款数据并放处理
this.resolveRefundData();
refundCallbackService.refundCallback();
}
this.saveCallbackRecord();
return this.getReturnMsg();
} catch (Exception e) {
log.error("回调处理失败", e);
callbackInfo.setCallbackStatus(PayCallbackStatusEnum.FAIL).setMsg("回调处理失败: "+e.getMessage());
this.saveCallbackRecord();
throw e;
}
}
/**
* 验证信息格式
*/
public abstract boolean verifyNotify();
/**
* 判断类型 支付回调/退款回调
* @see PaymentTypeEnum
*/
public abstract PaymentTypeEnum getCallbackType();
/**
* 解析支付回调数据并放到上下文中
*/
public abstract void resolvePayData();
/**
* 解析退款回调数据并放到上下文中
*/
public abstract void resolveRefundData();
/**
* 返回响应结果
*/
public abstract String getReturnMsg();
/**
* 保存回调记录
*/
public void saveCallbackRecord() {
CallbackLocal callbackInfo = PaymentContextLocal.get().getCallbackInfo();
PayCallbackRecord payNotifyRecord = new PayCallbackRecord()
.setTradeNo(callbackInfo.getTradeNo())
.setOutTradeNo(callbackInfo.getOutTradeNo())
.setChannel(this.getChannel().getCode())
.setNotifyInfo(JSONUtil.toJsonStr(callbackInfo.getCallbackParam()))
.setCallbackType(callbackInfo.getCallbackType().getCode())
.setRepairOrderNo(callbackInfo.getRepairNo())
.setStatus(callbackInfo.getCallbackStatus().getCode())
.setMsg(callbackInfo.getMsg());
callbackRecordService.save(payNotifyRecord);
}
}

View File

@@ -0,0 +1,36 @@
package cn.bootx.platform.daxpay.service.handler.exception;
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.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.result.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 过滤SaTokenException,需要运行在 RestExceptionHandler 之前
*
* @author xxm
* @since 2021/8/5
*/
@Order(Ordered.LOWEST_PRECEDENCE - 2)
@Slf4j
@RestControllerAdvice
public class PaymentExceptionHandler {
/**
* 支付异常
*/
@ExceptionHandler({PayFailureException.class})
public ResResult<CommonResult> handleBusinessException(PayFailureException ex) {
log.info(ex.getMessage(), ex);
CommonResult commonResult = new CommonResult();
commonResult.setMsg(ex.getMessage());
return Res.ok(commonResult);
}
}

View File

@@ -47,7 +47,7 @@ public class PayOrderQuery extends QueryOrder {
/**
* 异步支付通道
* @see PayChannelEnum#ASYNC_TYPE_CODE
* @see PayChannelEnum
*/
@QueryParam(type = QueryParam.CompareTypeEnum.EQ)
@Schema(description = "异步支付通道")

View File

@@ -1,9 +1,11 @@
package cn.bootx.platform.daxpay.service.param.reconcile;
import cn.bootx.platform.common.core.annotation.QueryParam;
import cn.hutool.core.date.DatePattern;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
@@ -22,7 +24,7 @@ public class ReconcileOrderQuery {
private String batchNo;
@Schema(description = "日期")
@DateTimeFormat(pattern = "yyyy-MM-dd")
@DateTimeFormat(pattern = DatePattern.NORM_DATE_PATTERN)
private LocalDate date;
@Schema(description = "通道")