feat 微信支付退款同步和一些订单记录的数据填充

This commit is contained in:
xxm1995
2024-01-31 13:47:47 +08:00
parent 56d7236011
commit c088d21d2a
24 changed files with 231 additions and 113 deletions

View File

@@ -34,8 +34,8 @@ public class PayRepairRecordController {
@Operation(summary = "查询单条")
@GetMapping("/findById")
public ResResult<PayRepairRecordDto> findById(Long paymentId){
return Res.ok(service.findById(paymentId));
public ResResult<PayRepairRecordDto> findById(Long id){
return Res.ok(service.findById(id));
}
}

View File

@@ -79,6 +79,9 @@ public interface AliPayCode {
/** 退款成功 */
String REFUND_SUCCESS = "REFUND_SUCCESS";
// 参数
/** 返回退款时间 */
String GMT_REFUND_PAY = "gmt_refund_pay";
// 错误提示
/** 交易不存在 */

View File

@@ -66,23 +66,32 @@ public interface WeChatPayCode {
/** 交易状态 */
String TRADE_STATE = "trade_state";
// 回调参数
/** 支付金额 */
String TOTAL_FEE = "total_fee";
/** 微信退款单号 */
String REFUND_ID = "refund_id";
String CALLBACK_REFUND_ID = "refund_id";
/** 商户退款单号 */
String OUT_REFUND_NO = "out_refund_no";
String CALLBACK_OUT_REFUND_NO = "out_refund_no";
/** 退款状态 */
String REFUND_STATUS = "refund_status";
String CALLBACK_REFUND_STATUS = "refund_status";
/** 申请退款金额 */
String REFUND_FEE = "refund_fee";
String CALLBACK_REFUND_FEE = "refund_fee";
/** 退款成功时间 yyyyMMddHHmmss */
String SUCCESS_TIME = "success_time";
String CALLBACK_SUCCESS_TIME = "success_time";
// 退款信息查询参数
/** 退款成功时间 yyyy-MM-dd HH:mm:ss */
String REFUND_SUCCESS_TIME = "refund_success_time_0";
/** 网关退款订单号 */
String REFUND_ID = "refund_id_0";
/** 退款成状态 */
String REFUND_STATUS = "refund_status_0";
/** 当前返回退款笔数 */
String REFUND_COUNT = "refund_count";
@@ -116,7 +125,7 @@ public interface WeChatPayCode {
String PAY_USERPAYING = "USERPAYING";
/** 退款成功 */
String REFUND_USERPAYING = "SUCCESS";
String REFUND_SUCCESS = "SUCCESS";
/** 退款异常 */
String REFUND_CHANGE = "CHANGE";
@@ -124,6 +133,9 @@ public interface WeChatPayCode {
/** 退款关闭 */
String REFUND_REFUNDCLOSE = "REFUNDCLOSE";
/** 退款处理中 */
String REFUND_PROCESSING = "PROCESSING";
/** 支付失败(刷卡支付) */
String TRADE_PAYERROR = "PAYERROR";

View File

@@ -14,13 +14,5 @@ import java.time.LocalDateTime;
@Accessors(chain = true)
public class PaySyncLocal {
/**
* 第三方支付网关生成的订单号, 用与将记录关联起来
* 1. 如付款码支付直接成功时会出现
*/
private String gatewayOrderNo;
/** 支付完成时间(通常用于接收异步支付返回的时间) */
private LocalDateTime payTime;
}

View File

@@ -49,9 +49,8 @@ public class AliPayRefundService {
}
// 接口返回fund_change=Y为退款成功fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态
if (response.getFundChange().equals("Y")){
// TODO 测试退款同步
// refundInfo.setStatus(PayRefundStatusEnum.SUCCESS)
// .setGatewayOrderNo(response.getTradeNo());
refundInfo.setStatus(PayRefundStatusEnum.SUCCESS)
.setGatewayOrderNo(response.getTradeNo());
}
refundInfo.setStatus(PayRefundStatusEnum.PROGRESS)
.setGatewayOrderNo(response.getTradeNo());

View File

@@ -4,6 +4,7 @@ import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.service.code.AliPayCode;
import cn.bootx.platform.daxpay.service.common.context.PaySyncLocal;
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.refund.entity.PayRefundOrder;
@@ -21,8 +22,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Objects;
import static cn.bootx.platform.daxpay.service.code.AliPayCode.GMT_REFUND_PAY;
/**
* 支付宝同步
*
@@ -34,7 +38,7 @@ import java.util.Objects;
@RequiredArgsConstructor
public class AliPaySyncService {
/**
* 与支付宝网关同步状态, 退款状态
* 与支付宝网关同步状态, 退款状态
* 1 远程支付成功
* 2 交易创建,等待买家付款
* 3 超时关闭
@@ -42,6 +46,7 @@ public class AliPaySyncService {
* 5 查询失败
*/
public PayGatewaySyncResult syncPayStatus(PayOrder payOrder) {
PaySyncLocal paySyncLocal = PaymentContextLocal.get().getPaySyncInfo();
PayGatewaySyncResult syncResult = new PayGatewaySyncResult().setSyncStatus(PaySyncStatusEnum.FAIL);
// 查询
try {
@@ -58,13 +63,13 @@ public class AliPaySyncService {
syncResult.setErrorMsg(response.getSubMsg());
return syncResult;
}
// 设置网关订单号
syncResult.setGatewayOrderNo(response.getTradeNo());
// 支付完成 TODO 部分退款也在这个地方, 但无法进行区分, 需要借助对账进行处理
if (Objects.equals(tradeStatus, AliPayCode.NOTIFY_TRADE_SUCCESS) || Objects.equals(tradeStatus, AliPayCode.NOTIFY_TRADE_FINISHED)) {
PaymentContextLocal.get().getPaySyncInfo().setGatewayOrderNo(response.getTradeNo());
// 支付完成时间
LocalDateTime payTime = LocalDateTimeUtil.of(response.getSendPayDate());
PaymentContextLocal.get().getPaySyncInfo().setPayTime(payTime);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS).setPayTime(payTime);
}
// 待支付
if (Objects.equals(tradeStatus, AliPayCode.NOTIFY_WAIT_BUYER_PAY)) {
@@ -78,7 +83,6 @@ public class AliPaySyncService {
} else {
return syncResult.setSyncStatus(PaySyncStatusEnum.CLOSED);
}
}
// 支付宝支付后, 客户未进行操作将不会创建出订单, 所以交易不存在约等于未查询订单
if (Objects.equals(response.getSubCode(), AliPayCode.ACQ_TRADE_NOT_EXIST)) {
@@ -103,6 +107,8 @@ public class AliPaySyncService {
queryModel.setOutRequestNo(String.valueOf(refundOrder.getId()));
// 商户订单号
queryModel.setOutTradeNo(String.valueOf(refundOrder.getPaymentId()));
// 设置返回退款完成时间
queryModel.setQueryOptions(Collections.singletonList(GMT_REFUND_PAY));
AlipayTradeFastpayRefundQueryResponse response = AliPayApi.tradeRefundQueryToResponse(queryModel);
syncResult.setSyncInfo(JSONUtil.toJsonStr(response));
// 失败
@@ -113,9 +119,12 @@ public class AliPaySyncService {
return syncResult;
}
String tradeStatus = response.getRefundStatus();
// 设置网关订单号
syncResult.setGatewayOrderNo(response.getTradeNo());
// 成功
if (Objects.equals(tradeStatus, AliPayCode.REFUND_SUCCESS)){
return syncResult.setSyncStatus(PayRefundSyncStatusEnum.SUCCESS);
LocalDateTime localDateTime = LocalDateTimeUtil.of(response.getGmtRefundPay());
return syncResult.setRefundTime(localDateTime).setSyncStatus(PayRefundSyncStatusEnum.SUCCESS);
} else {
return syncResult.setSyncStatus(PayRefundSyncStatusEnum.FAIL).setErrorMsg("支付宝网关反正退款未成功");
}

View File

@@ -118,18 +118,19 @@ public class WeChatPayCallbackService extends AbsCallbackStrategy {
callbackParam = WxPayKit.xmlToMap(decryptData);
callbackInfo.setCallbackParam(callbackParam);
// 网关订单号
callbackInfo.setGatewayOrderNo(callbackParam.get(REFUND_ID));
callbackInfo.setGatewayOrderNo(callbackParam.get(CALLBACK_REFUND_ID));
// 退款订单Id
callbackInfo.setOrderId(Long.valueOf(callbackParam.get(OUT_REFUND_NO)));
callbackInfo.setOrderId(Long.valueOf(callbackParam.get(CALLBACK_OUT_REFUND_NO)));
// 退款金额
callbackInfo.setAmount(callbackParam.get(REFUND_FEE));
callbackInfo.setAmount(callbackParam.get(CALLBACK_REFUND_FEE));
// 交易状态
PayStatusEnum payStatus = Objects.equals(callbackParam.get(REFUND_STATUS), REFUND_USERPAYING) ? PayStatusEnum.SUCCESS : PayStatusEnum.FAIL;
PayStatusEnum payStatus = Objects.equals(callbackParam.get(CALLBACK_REFUND_STATUS), REFUND_SUCCESS)
? PayStatusEnum.SUCCESS : PayStatusEnum.FAIL;
callbackInfo.setGatewayStatus(payStatus.getCode());
// 退款时间
String timeEnd = callbackParam.get(SUCCESS_TIME);
String timeEnd = callbackParam.get(CALLBACK_SUCCESS_TIME);
if (StrUtil.isNotBlank(timeEnd)) {
LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.NORM_DATETIME_PATTERN);
callbackInfo.setFinishTime(time);

View File

@@ -1,17 +1,11 @@
package cn.bootx.platform.daxpay.service.core.channel.wechat.service;
import cn.bootx.platform.common.core.util.LocalDateTimeUtil;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.service.code.WeChatPayCode;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.channel.wechat.entity.WeChatPayConfig;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundOrder;
import cn.bootx.platform.daxpay.service.core.payment.sync.result.PayGatewaySyncResult;
import cn.bootx.platform.daxpay.service.core.payment.sync.result.RefundGatewaySyncResult;
@@ -30,6 +24,8 @@ import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import static cn.bootx.platform.daxpay.service.code.WeChatPayCode.TRANSACTION_ID;
/**
* 微信支付同步服务
*
@@ -40,11 +36,9 @@ import java.util.Objects;
@Service
@RequiredArgsConstructor
public class WeChatPaySyncService {
private final PayChannelOrderManager payChannelOrderManager;
private final PayRefundChannelOrderManager refundChannelOrderManager;
/**
* 同步查询
* 支付信息查询
*/
public PayGatewaySyncResult syncPayStatus(PayOrder order, WeChatPayConfig weChatPayConfig) {
PayGatewaySyncResult syncResult = new PayGatewaySyncResult().setSyncStatus(PaySyncStatusEnum.FAIL);
@@ -70,14 +64,16 @@ public class WeChatPaySyncService {
log.warn("疑似未查询到订单:{}", result);
return syncResult.setSyncStatus(PaySyncStatusEnum.NOT_FOUND);
}
// 设置微信支付网关订单号
syncResult.setGatewayOrderNo(result.get(TRANSACTION_ID));
// 查询到订单的状态
String tradeStatus = result.get(WeChatPayCode.TRADE_STATE);
// 支付完成
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_SUCCESS) || Objects.equals(tradeStatus, WeChatPayCode.PAY_ACCEPT)) {
String timeEnd = result.get(WeChatPayCode.TIME_END);
LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.PURE_DATETIME_PATTERN);
PaymentContextLocal.get().getPaySyncInfo().setPayTime(time);
return syncResult.setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS);
return syncResult.setPayTime(time).setSyncStatus(PaySyncStatusEnum.PAY_SUCCESS);
}
// 待支付
if (Objects.equals(tradeStatus, WeChatPayCode.PAY_NOTPAY)
@@ -97,7 +93,7 @@ public class WeChatPaySyncService {
}
}
catch (RuntimeException e) {
log.error("查询订单失败:", e);
log.error("查询支付订单失败:", e);
syncResult.setErrorMsg(e.getMessage());
}
return syncResult;
@@ -107,26 +103,41 @@ public class WeChatPaySyncService {
* 退款信息查询
*/
public RefundGatewaySyncResult syncRefundStatus(PayRefundOrder refundOrder, WeChatPayConfig weChatPayConfig){
PayRefundChannelOrder orderChannel = refundChannelOrderManager.findByRefundIdAndChannel(refundOrder.getId(), PayChannelEnum.WECHAT.getCode())
.orElseThrow(() -> new PayFailureException("支付订单通道信息不存在"));
RefundGatewaySyncResult syncResult = new RefundGatewaySyncResult();
Map<String, String> params = RefundQueryModel.builder()
.appid(weChatPayConfig.getWxAppId())
.mch_id(weChatPayConfig.getWxMchId())
.nonce_str(WxPayKit.generateStr())
// 使用退款单号查询, 只返回当前这条, 如果使用支付订单号查询,
.out_refund_no(String.valueOf(refundOrder.getId()))
.build()
.createSign(weChatPayConfig.getApiKeyV2(), SignType.HMACSHA256);
String xmlResult = WxPayApi.orderRefundQuery(false, params);
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
// TODO 处理退款同步的情况
try {
String xmlResult = WxPayApi.orderRefundQuery(false, params);
Map<String, String> result = WxPayKit.xmlToMap(xmlResult);
syncResult.setSyncInfo(JSONUtil.toJsonStr(result));
Integer refundFee = Integer.valueOf(result.get(WeChatPayCode.REFUND_FEE));
if (Objects.equals(refundFee, orderChannel.getAmount())){
return new RefundGatewaySyncResult().setSyncStatus(PayRefundSyncStatusEnum.REFUNDING);
// 设置微信支付网关订单号
syncResult.setGatewayOrderNo(result.get(WeChatPayCode.REFUND_ID));
// 状态
String tradeStatus = result.get(WeChatPayCode.REFUND_STATUS);
// 退款成功
if (Objects.equals(tradeStatus, WeChatPayCode.REFUND_SUCCESS)) {
String timeEnd = result.get(WeChatPayCode.REFUND_SUCCESS_TIME);
LocalDateTime time = LocalDateTimeUtil.parse(timeEnd, DatePattern.NORM_DATETIME_PATTERN);
return syncResult.setRefundTime(time).setSyncStatus(PayRefundSyncStatusEnum.SUCCESS);
}
// 退款中
if (Objects.equals(tradeStatus, WeChatPayCode.REFUND_PROCESSING)) {
return syncResult.setSyncStatus(PayRefundSyncStatusEnum.REFUNDING);
}
return syncResult.setSyncStatus(PayRefundSyncStatusEnum.FAIL);
} catch (Exception e) {
log.error("查询退款订单失败:", e);
syncResult.setSyncStatus(PayRefundSyncStatusEnum.REFUNDING).setErrorMsg(e.getMessage());
}
return new RefundGatewaySyncResult().setSyncInfo(JSONUtil.toJsonStr(result));
return syncResult;
}
}

View File

@@ -9,9 +9,9 @@ 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.order.pay.dao.PayChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderExtraManager;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrderExtra;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
import cn.bootx.platform.daxpay.util.PaySignUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.ContentType;
@@ -35,7 +35,7 @@ import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class PayNoticeService {
private final PayOrderManager payOrderManager;
private final PayOrderQueryService payOrderQueryService;
private final PayOrderExtraManager payOrderExtraManager;
private final PayChannelOrderManager payChannelOrderManager;
@@ -49,7 +49,7 @@ public class PayNoticeService {
PlatformLocal platform = PaymentContextLocal.get().getPlatformInfo();
// 首先判断接口是开启了通知回调功能
if (apiInfo.isNotice()){
PayOrder payOrder = payOrderManager.findById(paymentId).orElseThrow(DataNotExistException::new);
PayOrder payOrder = payOrderQueryService.findById(paymentId).orElseThrow(DataNotExistException::new);
// 判断是否是同步支付, 并且配置不进行消息通知
if (!payOrder.isAsyncPay() && apiInfo.isOnlyAsyncNotice()){
return;

View File

@@ -6,18 +6,18 @@ import cn.bootx.platform.common.mybatisplus.util.MpUtil;
import cn.bootx.platform.common.query.generator.QueryGenerator;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.param.order.PayOrderQuery;
import cn.hutool.core.text.NamingCase;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import java.util.Objects;
import java.util.Optional;
/**
* 支付订单
* 注意: 增删改需要使用 PayOrderQueryService 服务类, 不可以直接使用此dao, 因为订单超时任务需要处理
*
* @author xxm
* @since 2023/12/18
*/

View File

@@ -7,6 +7,8 @@ import cn.bootx.platform.daxpay.service.core.order.refund.convert.RefundOrderCha
import cn.bootx.platform.daxpay.service.dto.order.refund.RefundChannelOrderDto;
import cn.bootx.table.modify.annotation.DbColumn;
import cn.bootx.table.modify.annotation.DbTable;
import com.baomidou.mybatisplus.annotation.FieldStrategy;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -44,6 +46,10 @@ public class PayRefundChannelOrder extends MpCreateEntity implements EntityBaseF
@DbColumn(comment = "退款金额")
private Integer amount;
@DbColumn(comment = "剩余可退余额")
@TableField(updateStrategy = FieldStrategy.NEVER)
private Integer refundableAmount;
/**
* 退款状态
* @see PayRefundStatusEnum
@@ -51,7 +57,7 @@ public class PayRefundChannelOrder extends MpCreateEntity implements EntityBaseF
@DbColumn(comment = "退款状态")
private String status;
@DbColumn(comment = "退款时间")
@DbColumn(comment = "退款完成时间")
private LocalDateTime refundTime;
/**

View File

@@ -90,7 +90,7 @@ public class PayRefundOrder extends MpBaseEntity implements EntityBaseFunction<P
private String reason;
/** 退款时间 */
@DbColumn(comment = "退款时间")
@DbColumn(comment = "退款完成时间")
private LocalDateTime refundTime;
/**

View File

@@ -115,7 +115,6 @@ public class PayRepairService {
.getFinishTime();
// 执行个通道的成功处理方法
strategies.forEach(AbsPayRepairStrategy::doPaySuccessHandler);
// 修改订单支付状态为成功
order.setStatus(PayStatusEnum.SUCCESS.getCode());
// 读取支付网关中的时间

View File

@@ -7,9 +7,10 @@ import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum;
import cn.bootx.platform.daxpay.service.code.RefundRepairWayEnum;
import cn.bootx.platform.daxpay.service.common.local.PaymentContextLocal;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.dao.PayOrderManager;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayChannelOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.entity.PayOrder;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderQueryService;
import cn.bootx.platform.daxpay.service.core.order.pay.service.PayOrderService;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundChannelOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.dao.PayRefundOrderManager;
import cn.bootx.platform.daxpay.service.core.order.refund.entity.PayRefundChannelOrder;
@@ -39,7 +40,9 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor
public class RefundRepairService {
private final PayOrderManager payOrderManager;
private final PayOrderService payOrderService;
private final PayOrderQueryService payOrderQueryService;
private final PayChannelOrderManager payChannelOrderManager;
@@ -56,7 +59,7 @@ public class RefundRepairService {
public RefundRepairResult repair(PayRefundOrder refundOrder, RefundRepairWayEnum repairType){
// 获取关联支付单
PayOrder payOrder = payOrderManager.findById(refundOrder.getPaymentId())
PayOrder payOrder = payOrderQueryService.findById(refundOrder.getPaymentId())
.orElseThrow(() -> new RuntimeException("支付单不存在"));
// 关联支付通道支付单
Map<String, PayChannelOrder> payChannelOrderMap = payChannelOrderManager.findAllByPaymentId(refundOrder.getPaymentId())
@@ -127,7 +130,7 @@ public class RefundRepairService {
.collect(Collectors.toList());
// 更新订单和退款相关订单
payOrderManager.updateById(payOrder);
payOrderService.updateById(payOrder);
refundOrderManager.updateById(refundOrder);
payChannelOrderManager.updateAllById(payChannelOrders);
refundChannelOrderManager.updateAllById(refundChannelOrders);
@@ -183,7 +186,7 @@ public class RefundRepairService {
// 更新订单和退款相关订单
payChannelOrderManager.updateAllById(payChannelOrders);
payOrderManager.updateById(payOrder);
payOrderService.updateById(payOrder);
refundOrderManager.updateById(refundOrder);
refundChannelOrderManager.updateAllById(refundChannelOrders);
return repairResult;

View File

@@ -4,6 +4,8 @@ import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.FAIL;
/**
@@ -22,6 +24,14 @@ public class PayGatewaySyncResult {
*/
private PaySyncStatusEnum syncStatus = FAIL;
/**
* 第三方支付网关生成的订单号, 用与将记录关联起来
*/
private String gatewayOrderNo;
/** 支付完成时间(通常用于接收异步支付返回的时间) */
private LocalDateTime payTime;
/** 同步时网关返回的对象, 序列化为json字符串 */
private String syncInfo;

View File

@@ -4,7 +4,9 @@ import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
import lombok.Data;
import lombok.experimental.Accessors;
import static cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum.FAIL;
import java.time.LocalDateTime;
import static cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum.REFUNDING;
/**
* 支付退款同步结果
@@ -16,14 +18,22 @@ import static cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum.FAIL;
public class RefundGatewaySyncResult {
/**
* 支付网关订单状态
* 支付网关订单状态, 默认为退款中
* @see PayRefundSyncStatusEnum
*/
private PayRefundSyncStatusEnum syncStatus = FAIL;
private PayRefundSyncStatusEnum syncStatus = REFUNDING;
/** 同步时网关返回的对象, 序列化为json字符串 */
private String syncInfo;
/**
* 第三方支付网关生成的订单号, 用与将记录关联起来
*/
private String gatewayOrderNo;
/** 退款完成时间(通常用于接收网关返回的时间) */
private LocalDateTime refundTime;
/** 错误提示码 */
private String errorCode;

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.service.core.payment.sync.service;
import cn.bootx.platform.common.core.exception.BizException;
import cn.bootx.platform.common.core.exception.RepetitiveOperationException;
import cn.bootx.platform.daxpay.code.PayRefundStatusEnum;
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
import cn.bootx.platform.daxpay.exception.pay.PayFailureException;
import cn.bootx.platform.daxpay.param.pay.RefundSyncParam;
@@ -53,19 +54,23 @@ public class PayRefundSyncService {
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public SyncResult sync(RefundSyncParam param){
// 先获取退款单
PayRefundOrder requestOrder;
PayRefundOrder refundOrder;
if (Objects.nonNull(param.getRefundId())){
requestOrder = refundOrderManager.findById(param.getRefundId())
refundOrder = refundOrderManager.findById(param.getRefundId())
.orElseThrow(() -> new PayFailureException("未查询到退款订单"));
} else {
requestOrder = refundOrderManager.findByRefundNo(param.getRefundNo())
refundOrder = refundOrderManager.findByRefundNo(param.getRefundNo())
.orElseThrow(() -> new PayFailureException("未查询到退款订单"));
}
// 如果不是异步支付, 直接返回返回
if (!requestOrder.isAsyncPay()){
if (!refundOrder.isAsyncPay()){
return new SyncResult().setSuccess(false).setRepair(false).setErrorMsg("订单没有异步通道的退款,不需要同步");
}
return this.syncRefundOrder(requestOrder);
// 如果订单已经关闭, 直接返回失败
if (Objects.equals(refundOrder.getStatus(), PayRefundStatusEnum.CLOSE.getCode())){
return new SyncResult().setSuccess(false).setRepair(false).setErrorMsg("订单已经关闭,不需要同步");
}
return this.syncRefundOrder(refundOrder);
}
/**
@@ -82,6 +87,8 @@ public class PayRefundSyncService {
// 获取支付同步策略类
AbsRefundSyncStrategy syncPayStrategy = RefundSyncStrategyFactory.create(refundOrder.getAsyncChannel());
syncPayStrategy.initRefundParam(refundOrder);
// 同步前处理, 主要预防请求过于迅速
syncPayStrategy.doBeforeHandler();
// 执行操作, 获取支付网关同步的结果
RefundGatewaySyncResult syncResult = syncPayStrategy.doSyncStatus();
@@ -90,12 +97,23 @@ public class PayRefundSyncService {
// 同步失败, 返回失败响应, 同时记录失败的日志
return new SyncResult().setErrorMsg(syncResult.getErrorMsg());
}
// 支付订单的网关订单号是否一致, 不一致进行更新
if (!Objects.equals(syncResult.getGatewayOrderNo(), refundOrder.getGatewayOrderNo())){
refundOrder.setGatewayOrderNo(syncResult.getGatewayOrderNo());
refundOrderManager.updateById(refundOrder);
}
// 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态
boolean statusSync = this.checkSyncStatus(syncResult, refundOrder);
RefundRepairResult repairResult = new RefundRepairResult();
try {
// 状态不一致,执行退款单修复逻辑
if (!statusSync) {
// 如果没有支付来源, 设置支付来源为同步
RepairLocal repairInfo = PaymentContextLocal.get().getRepairInfo();
if (Objects.isNull(repairInfo.getSource())){
repairInfo.setSource(PayRepairSourceEnum.SYNC);
}
repairInfo.setFinishTime(syncResult.getRefundTime());
repairResult = this.repairHandler(syncResult, refundOrder);
}
} catch (PayFailureException e) {
@@ -119,11 +137,29 @@ public class PayRefundSyncService {
/**
* 检查状态是否一致
* @see PayRefundSyncStatusEnum 同步返回类型
* @see PayRefundStatusEnum 退款单状态
*/
private boolean checkSyncStatus(RefundGatewaySyncResult syncResult, PayRefundOrder order){
PayRefundSyncStatusEnum syncStatus = syncResult.getSyncStatus();
String orderStatus = order.getStatus();
return Objects.equals(orderStatus, syncStatus.getCode());
// 退款完成
if (Objects.equals(syncStatus, PayRefundSyncStatusEnum.SUCCESS)&&
Objects.equals(orderStatus, PayRefundStatusEnum.SUCCESS.getCode())) {
return true;
}
// 退款失败
if (Objects.equals(syncStatus, PayRefundSyncStatusEnum.FAIL)&&
Objects.equals(orderStatus, PayRefundStatusEnum.FAIL.getCode())) {
return true;
}
// 退款中
if (Objects.equals(syncStatus, PayRefundSyncStatusEnum.REFUNDING)&&
Objects.equals(orderStatus, PayRefundStatusEnum.PROGRESS.getCode())) {
return true;
}
return false;
}
/**
@@ -131,11 +167,6 @@ public class PayRefundSyncService {
*/
private RefundRepairResult repairHandler(RefundGatewaySyncResult syncResult, PayRefundOrder order){
PayRefundSyncStatusEnum syncStatusEnum = syncResult.getSyncStatus();
// 如果没有支付来源, 设置支付来源为同步
RepairLocal repairInfo = PaymentContextLocal.get().getRepairInfo();
if (Objects.isNull(repairInfo.getSource())){
repairInfo.setSource(PayRepairSourceEnum.SYNC);
}
RefundRepairResult repair = new RefundRepairResult();
// 对支付网关同步的结果进行处理
switch (syncStatusEnum) {
@@ -158,21 +189,20 @@ public class PayRefundSyncService {
return repair;
}
/**
* 保存同步记录
* @param payOrder 支付单
* @param refundOrder 支付单
* @param syncResult 同步结果
* @param repair 是否修复
* @param errorMsg 错误信息
*/
private void saveRecord(PayRefundOrder payOrder, RefundGatewaySyncResult syncResult, boolean repair, Long repairOrderId, String errorMsg){
private void saveRecord(PayRefundOrder refundOrder, RefundGatewaySyncResult syncResult, boolean repair, Long repairOrderId, String errorMsg){
PaySyncRecord paySyncRecord = new PaySyncRecord()
.setOrderId(payOrder.getId())
.setOrderNo(payOrder.getBusinessNo())
.setOrderId(refundOrder.getId())
.setOrderNo(refundOrder.getRefundNo())
.setSyncType(PaymentTypeEnum.REFUND.getCode())
.setAsyncChannel(payOrder.getAsyncChannel())
.setAsyncChannel(refundOrder.getAsyncChannel())
.setGatewayOrderNo(syncResult.getGatewayOrderNo())
.setSyncInfo(syncResult.getSyncInfo())
.setGatewayStatus(syncResult.getSyncStatus().getCode())
.setRepairOrder(repair)

View File

@@ -15,6 +15,7 @@ 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;
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.payment.repair.result.PayRepairResult;
import cn.bootx.platform.daxpay.service.core.payment.repair.service.PayRepairService;
import cn.bootx.platform.daxpay.service.core.payment.sync.factory.PaySyncStrategyFactory;
@@ -49,6 +50,8 @@ import static cn.bootx.platform.daxpay.code.PaySyncStatusEnum.*;
public class PaySyncService {
private final PayOrderQueryService payOrderQueryService;
private final PayOrderService payOrderService;
private final PaySyncRecordService paySyncRecordService;
private final PayRepairService repairService;
@@ -79,8 +82,8 @@ public class PaySyncService {
/**
* 同步支付状态, 开启一个新的事务, 不受外部抛出异常的影响
* 1. 如果状态一致, 不进行处理
* 2. 如果状态不一致, 调用修复逻辑进行修复
* todo 需要进行异常处理, 现在会有 Transaction rolled back because it has been marked as rollback-only 问题
* 2. 如果状态不一致, 调用修复逻辑进行修复, 更新状态和完成时间
* 3. 会更新关联网关订单号
*/
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public SyncResult syncPayOrder(PayOrder payOrder) {
@@ -102,13 +105,24 @@ public class PaySyncService {
this.saveRecord(payOrder, syncResult, false, null, syncResult.getErrorMsg());
return new SyncResult().setErrorMsg(syncResult.getErrorMsg());
}
// 支付订单的网关订单号是否一致, 不一致进行更新
if (!Objects.equals(syncResult.getGatewayOrderNo(), payOrder.getGatewayOrderNo())){
payOrder.setGatewayOrderNo(syncResult.getGatewayOrderNo());
payOrderService.updateById(payOrder);
}
// 判断网关状态是否和支付单一致, 同时特定情况下更新网关同步状态
boolean statusSync = this.checkAndAdjustSyncStatus(syncResult,payOrder);
PayRepairResult repairResult = new PayRepairResult();
try {
// 状态不一致,执行支付单修复逻辑
if (!statusSync){
// 如果没有修复触发来源, 设置修复触发来源为同步
RepairLocal repairInfo = PaymentContextLocal.get().getRepairInfo();
if (Objects.isNull(repairInfo.getSource())){
repairInfo.setSource(PayRepairSourceEnum.SYNC);
}
// 设置支付单完成时间
repairInfo.setFinishTime(syncResult.getPayTime());
repairResult = this.repairHandler(syncResult, payOrder);
}
} catch (PayFailureException e) {
@@ -178,11 +192,6 @@ public class PaySyncService {
*/
private PayRepairResult repairHandler(PayGatewaySyncResult syncResult, PayOrder payOrder){
PaySyncStatusEnum syncStatusEnum = syncResult.getSyncStatus();
// 如果没有支付来源, 设置支付来源为同步
RepairLocal repairInfo = PaymentContextLocal.get().getRepairInfo();
if (Objects.isNull(repairInfo.getSource())){
repairInfo.setSource(PayRepairSourceEnum.SYNC);
}
PayRepairResult repair = new PayRepairResult();
// 对支付网关同步的结果进行处理
switch (syncStatusEnum) {

View File

@@ -3,6 +3,7 @@ package cn.bootx.platform.daxpay.service.core.record.sync.entity;
import cn.bootx.platform.common.core.function.EntityBaseFunction;
import cn.bootx.platform.common.mybatisplus.base.MpCreateEntity;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.platform.daxpay.service.code.PaymentTypeEnum;
import cn.bootx.platform.daxpay.service.core.record.sync.convert.PaySyncRecordConvert;
@@ -36,6 +37,10 @@ public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction<
@DbColumn(comment = "本地业务号")
private String orderNo;
/** 网关订单号 */
@DbColumn(comment = "网关订单号")
private String gatewayOrderNo;
/**
* 同步类型 支付/退款
* @see PaymentTypeEnum
@@ -43,14 +48,11 @@ public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction<
@DbColumn(comment = "同步类型")
private String syncType;
@DbColumn(comment = "同步的异步通道")
private String syncChannel;
/**
* 同步通道
* 同步的异步通道
* @see PayChannelEnum#getCode()
*/
@DbColumn(comment = "同步通道")
@DbColumn(comment = "同步的异步通道")
private String asyncChannel;
/** 网关返回的同步消息 */
@@ -61,6 +63,7 @@ public class PaySyncRecord extends MpCreateEntity implements EntityBaseFunction<
/**
* 网关返回状态
* @see PaySyncStatusEnum
* @see PayRefundSyncStatusEnum
*/
@DbColumn(comment = "网关返回状态")
private String gatewayStatus;

View File

@@ -38,6 +38,9 @@ public class RefundChannelOrderDto extends BaseDto {
@Schema(description = "退款金额")
private Integer amount;
@Schema(description = "剩余可退余额")
private Integer refundableAmount;
/**
* 退款状态
* @see PayRefundStatusEnum

View File

@@ -2,6 +2,7 @@ package cn.bootx.platform.daxpay.service.dto.record.sync;
import cn.bootx.platform.common.core.rest.dto.BaseDto;
import cn.bootx.platform.daxpay.code.PayChannelEnum;
import cn.bootx.platform.daxpay.code.PayRefundSyncStatusEnum;
import cn.bootx.platform.daxpay.code.PaySyncStatusEnum;
import cn.bootx.table.modify.mysql.annotation.DbMySqlFieldType;
import cn.bootx.table.modify.mysql.constants.MySqlFieldTypeEnum;
@@ -21,19 +22,27 @@ import lombok.experimental.Accessors;
@Schema(title = "支付同步订单")
public class PaySyncRecordDto extends BaseDto {
/** 支付记录id */
@Schema(description = "支付记录id")
private Long paymentId;
/** 本地订单ID */
@Schema(description = "本地订单ID")
private Long orderId;
/** 业务号 */
@Schema(description = "业务号")
private String businessNo;
/** 本地业务号 */
@Schema(description = "本地业务号")
private String orderNo;
/** 网关订单号 */
@Schema(description = "网关订单号")
private String gatewayOrderNo;
/** 同步类型 */
@Schema(description = "同步类型")
private String syncType;
/**
* 同步通道
* 同步的异步通道
* @see PayChannelEnum#getCode()
*/
@Schema(description = "同步通道")
@Schema(description = "同步的异步通道")
private String asyncChannel;
/** 通知消息 */
@@ -44,6 +53,7 @@ public class PaySyncRecordDto extends BaseDto {
/**
* 网关返回状态
* @see PaySyncStatusEnum
* @see PayRefundSyncStatusEnum
*/
@Schema(description = "网关返回状态")
private String gatewayStatus;

View File

@@ -72,6 +72,8 @@ public abstract class AbsRefundRepairStrategy implements PayStrategy{
payChannelOrder.setStatus(PayStatusEnum.PARTIAL_REFUND.getCode());
}
// 如果失败, 可退余额设置为null
refundChannelOrder.setRefundableAmount(null);
refundChannelOrder.setStatus(PayRefundStatusEnum.CLOSE.getCode());
}

View File

@@ -76,8 +76,7 @@ public abstract class AbsRefundStrategy implements PayStrategy{
*/
public void doSuccessHandler() {
// 更新退款订单数据状态
this.refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode())
.setRefundTime(LocalDateTime.now());
this.refundChannelOrder.setStatus(PayRefundStatusEnum.SUCCESS.getCode()).setRefundTime(LocalDateTime.now());
// 支付通道订单可退余额
int refundableBalance = this.getPayChannelOrder().getRefundableBalance() - this.refundChannelOrder.getAmount();
@@ -91,11 +90,14 @@ public abstract class AbsRefundStrategy implements PayStrategy{
* 生成通道退款订单对象
*/
public void generateChannelOrder() {
int refundableAmount = this.getPayChannelOrder().getRefundableBalance() - this.getRefundChannelParam().getAmount();
this.refundChannelOrder = new PayRefundChannelOrder()
.setPayChannelId(this.getPayChannelOrder().getId())
.setAsync(this.getPayChannelOrder().isAsync())
.setChannel(this.getPayChannelOrder().getChannel())
.setOrderAmount(this.getPayChannelOrder().getAmount())
.setRefundableAmount(refundableAmount)
.setAmount(this.getRefundChannelParam().getAmount());
}