pay: 优化回调的逻辑

This commit is contained in:
YunaiV
2023-02-20 23:41:03 +08:00
parent 4390e28eea
commit 14893c4cff
20 changed files with 130 additions and 515 deletions

View File

@ -3,7 +3,11 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
import io.swagger.v3.oas.annotations.Operation;
@ -17,6 +21,7 @@ import javax.annotation.security.PermitAll;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
@Tag(name = "管理后台 - 支付通知")
@ -65,26 +70,31 @@ public class PayNotifyController {
@OperateLog(enable = false) // 回调地址,无需记录操作日志
public String notifyCallback(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) throws Exception {
// 校验支付渠道是否存在
@RequestBody(required = false) String body) {
log.info("[notifyCallback][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = payClientFactory.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
}
// 校验通知数据是否合法
PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(body).build();
payClient.verifyNotifyData(notifyData);
// 情况一:如果是退款,则发起退款通知
if (payClient.isRefundNotify(notifyData)) {
refundService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
// 2. 解析通知数据
PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
Object notify = payClient.parseNotify(rawNotify);
// 3. 处理通知
// 3.1:退款通知
if (notify instanceof PayRefundNotifyRespDTO) {
refundService.notifyPayRefund(channelId, (PayRefundNotifyRespDTO) notify, rawNotify);
return "success";
}
// 情况二:如果非退款,则发起支付通知
orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
return "success";
// 3.2:支付通知
if (notify instanceof PayOrderNotifyRespDTO) {
orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
return "success";
}
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
}
}

View File

@ -2,7 +2,8 @@ package cn.iocoder.yudao.module.pay.service.order;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
@ -93,8 +94,9 @@ public interface PayOrderService {
* 通知支付单成功
*
* @param channelId 渠道编号
* @param notifyData 通知数据
* @param notify 通知
* @param rawNotify 通知数据
*/
void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
}

View File

@ -3,13 +3,12 @@ package cn.iocoder.yudao.module.pay.service.order;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.config.PayProperties;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
@ -45,6 +44,7 @@ import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
/**
* 支付订单 Service 实现类
@ -105,7 +105,7 @@ public class PayOrderServiceImpl implements PayOrderService {
reqDTO.getAppId(), reqDTO.getMerchantOrderId());
if (order != null) {
log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说,不会出现这个情况
order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
return order.getId();
}
@ -222,49 +222,30 @@ public class PayOrderServiceImpl implements PayOrderService {
@Override
@Transactional(rollbackFor = Exception.class)
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) {
// TODO 芋艿,记录回调日志
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
// 校验支付渠道是否有效
PayChannelDO channel = channelService.validPayChannel(channelId);
TenantUtils.execute(channel.getTenantId(), () -> {
try {
notifyPayOrder(channel, notifyData);
} catch (Exception e) {
throw new RuntimeException(e);
}
// 1. 更新 PayOrderExtensionDO 支付成功
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(),
rawNotify);
// 2. 更新 PayOrderDO 支付成功
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notify);
// 3. 插入支付通知记录
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
});
}
private void notifyPayOrder(PayChannelDO channel, PayNotifyDataDTO notifyData) throws Exception {
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
}
// 0. 解析支付结果
PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
// 1. 更新 PayOrderExtensionDO 支付成功
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notifyRespDTO.getOrderExtensionNo(), notifyData.getBody());
// 2. 更新 PayOrderDO 支付成功
PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notifyRespDTO);
// 3. 插入支付通知记录
notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
}
/**
* 更新 PayOrderExtensionDO 支付成功
*
* @param no 支付订单号(支付模块)
* @param body 回调内容
* @param rawNotify 通知数据
* @return PayOrderExtensionDO 对象
*/
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, String body) {
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
// 1.1 查询 PayOrderExtensionDO
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
if (orderExtension == null) {
@ -276,7 +257,8 @@ public class PayOrderServiceImpl implements PayOrderService {
// 1.2 更新 PayOrderExtensionDO
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(body).build());
.status(PayOrderStatusEnum.SUCCESS.getStatus())
.channelNotifyData(toJsonString(rawNotify)).build());
if (updateCounts == 0) { // 校验状态,必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
}
@ -289,11 +271,11 @@ public class PayOrderServiceImpl implements PayOrderService {
*
* @param channel 支付渠道
* @param orderExtension 支付拓展单
* @param notifyRespDTO 通知回调
* @param notify 通知回调
* @return PayOrderDO 对象
*/
private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
PayOrderNotifyRespDTO notifyRespDTO) {
PayOrderNotifyRespDTO notify) {
// 2.1 判断 PayOrderDO 是否处于待支付
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
if (order == null) {
@ -306,8 +288,8 @@ public class PayOrderServiceImpl implements PayOrderService {
int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
.channelId(channel.getId()).channelCode(channel.getCode())
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
.successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
.channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
.notifyTime(LocalDateTime.now()).build());
if (updateCounts == 0) { // 校验状态,必须是待支付
throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.pay.service.refund;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
@ -52,9 +53,9 @@ public interface PayRefundService {
* 渠道的退款通知
*
* @param channelId 渠道编号
* @param notifyData 通知数据
* @throws Exception 退款通知异常
* @param notify 通知
* @param rawNotify 通知数据
*/
void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
}

View File

@ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.config.PayProperties;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
@ -187,29 +187,22 @@ public class PayRefundServiceImpl implements PayRefundService {
@Override
@Transactional(rollbackFor = Exception.class)
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
// 校验支付渠道是否有效
// TODO 芋艿:需要重构下这块的逻辑
PayChannelDO channel = channelService.validPayChannel(channelId);
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
}
// 解析渠道退款通知数据, 统一处理
PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
payRefundSuccess(refundNotify);
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
payRefundSuccess(notify);
} else {
//TODO 支付异常, 支付宝似乎没有支付异常的通知。
// TODO @jason那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
}
}
private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
// 校验退款单存在
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
refundNotify.getReqNo());
if (refundDO == null) {
log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);