mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 20:28:44 +08:00 
			
		
		
		
	mall + pay:
1. 重构支付回调的逻辑,将回调解析改成 PayOrderRespDTO,为后续轮询做铺垫 2. 调整退款单的表结构 3. 调整退款调用的实现
This commit is contained in:
		@@ -3,9 +3,8 @@ 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.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.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 | 
			
		||||
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;
 | 
			
		||||
@@ -61,18 +60,17 @@ public class PayNotifyController {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 2. 解析通知数据
 | 
			
		||||
        PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
 | 
			
		||||
        Object notify = payClient.parseNotify(rawNotify);
 | 
			
		||||
        Object notify = payClient.parseNotify(params, body);
 | 
			
		||||
 | 
			
		||||
        // 3. 处理通知
 | 
			
		||||
        // 3.1:退款通知
 | 
			
		||||
        if (notify instanceof PayRefundNotifyRespDTO) {
 | 
			
		||||
            refundService.notifyPayRefund(channelId, (PayRefundNotifyRespDTO) notify, rawNotify);
 | 
			
		||||
        if (notify instanceof PayRefundRespDTO) {
 | 
			
		||||
            refundService.notifyPayRefund(channelId, (PayRefundRespDTO) notify);
 | 
			
		||||
            return "success";
 | 
			
		||||
        }
 | 
			
		||||
        // 3.2:支付通知
 | 
			
		||||
        if (notify instanceof PayOrderNotifyRespDTO) {
 | 
			
		||||
            orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
 | 
			
		||||
        if (notify instanceof PayOrderRespDTO) {
 | 
			
		||||
            orderService.notifyPayOrder(channelId, (PayOrderRespDTO) notify);
 | 
			
		||||
            return "success";
 | 
			
		||||
        }
 | 
			
		||||
        throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
 | 
			
		||||
 
 | 
			
		||||
@@ -76,9 +76,6 @@ public class PayRefundExcelVO {
 | 
			
		||||
    @ExcelProperty("退款成功时间")
 | 
			
		||||
    private LocalDateTime successTime;
 | 
			
		||||
 | 
			
		||||
    @ExcelProperty("退款通知时间")
 | 
			
		||||
    private LocalDateTime notifyTime;
 | 
			
		||||
 | 
			
		||||
    @ExcelProperty("退款失效时间")
 | 
			
		||||
    private LocalDateTime expireTime;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,9 +60,10 @@ public interface PayRefundConvert {
 | 
			
		||||
        PayRefundExcelVO payRefundExcelVO = new PayRefundExcelVO();
 | 
			
		||||
 | 
			
		||||
        payRefundExcelVO.setId(bean.getId());
 | 
			
		||||
        payRefundExcelVO.setTradeNo(bean.getTradeNo());
 | 
			
		||||
        payRefundExcelVO.setTradeNo(bean.getNo());
 | 
			
		||||
        payRefundExcelVO.setMerchantOrderId(bean.getMerchantOrderId());
 | 
			
		||||
        payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo());
 | 
			
		||||
        // TODO 芋艿:晚点在改
 | 
			
		||||
//        payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo());
 | 
			
		||||
        payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl());
 | 
			
		||||
        payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus());
 | 
			
		||||
        payRefundExcelVO.setStatus(bean.getStatus());
 | 
			
		||||
@@ -71,9 +72,7 @@ public interface PayRefundConvert {
 | 
			
		||||
        payRefundExcelVO.setUserIp(bean.getUserIp());
 | 
			
		||||
        payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
 | 
			
		||||
        payRefundExcelVO.setChannelRefundNo(bean.getChannelRefundNo());
 | 
			
		||||
        payRefundExcelVO.setExpireTime(bean.getExpireTime());
 | 
			
		||||
        payRefundExcelVO.setSuccessTime(bean.getSuccessTime());
 | 
			
		||||
        payRefundExcelVO.setNotifyTime(bean.getNotifyTime());
 | 
			
		||||
        payRefundExcelVO.setCreateTime(bean.getCreateTime());
 | 
			
		||||
 | 
			
		||||
        BigDecimal multiple = new BigDecimal(100);
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,8 @@ public class PayOrderDO extends BaseDO {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 商户订单编号
 | 
			
		||||
     * 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
 | 
			
		||||
     *
 | 
			
		||||
     * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
 | 
			
		||||
     */
 | 
			
		||||
    private String merchantOrderId;
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -32,10 +32,11 @@ public class PayOrderExtensionDO extends BaseDO {
 | 
			
		||||
     */
 | 
			
		||||
    private Long id;
 | 
			
		||||
    /**
 | 
			
		||||
     * 支付订单号,根据规则生成
 | 
			
		||||
     * 调用支付渠道时,使用该字段作为对接的订单号。
 | 
			
		||||
     * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
 | 
			
		||||
     * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
 | 
			
		||||
     * 外部订单号,根据规则生成
 | 
			
		||||
     *
 | 
			
		||||
     * 调用支付渠道时,使用该字段作为对接的订单号:
 | 
			
		||||
     * 1. 微信支付:对应 <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml">JSAPI 支付</a> 的 out_trade_no 字段
 | 
			
		||||
     * 2. 支付宝支付:对应 <a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a> 的 out_trade_no 字段
 | 
			
		||||
     *
 | 
			
		||||
     * 例如说,P202110132239124200055
 | 
			
		||||
     */
 | 
			
		||||
@@ -70,7 +71,7 @@ public class PayOrderExtensionDO extends BaseDO {
 | 
			
		||||
    /**
 | 
			
		||||
     * 支付渠道的额外参数
 | 
			
		||||
     *
 | 
			
		||||
     * 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
 | 
			
		||||
     * 参见 <a href="https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html">参数说明</>
 | 
			
		||||
     */
 | 
			
		||||
    @TableField(typeHandler = JacksonTypeHandler.class)
 | 
			
		||||
    private Map<String, String> channelExtras;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.KeySequence;
 | 
			
		||||
@@ -37,6 +38,14 @@ public class PayRefundDO extends BaseDO {
 | 
			
		||||
     */
 | 
			
		||||
    @TableId
 | 
			
		||||
    private Long id;
 | 
			
		||||
    /**
 | 
			
		||||
     * 外部退款号,根据规则生成
 | 
			
		||||
     *
 | 
			
		||||
     * 调用支付渠道时,使用该字段作为对接的退款号:
 | 
			
		||||
     * 1. 微信退款:对应 <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_4">申请退款</a> 的 out_refund_no 字段
 | 
			
		||||
     * 2. 支付宝退款:对应 <a href="https://opendocs.alipay.com/open/02e7go"统一收单交易退款接口></a> 的 out_request_no 字段
 | 
			
		||||
     */
 | 
			
		||||
    private String no;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 应用编号
 | 
			
		||||
@@ -63,47 +72,27 @@ public class PayRefundDO extends BaseDO {
 | 
			
		||||
     */
 | 
			
		||||
    private Long orderId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 交易订单号,根据规则生成
 | 
			
		||||
     * 调用支付渠道时,使用该字段作为对接的订单号。
 | 
			
		||||
     * 1. 调用微信支付 https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 时,使用该字段作为 out_trade_no
 | 
			
		||||
     * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
 | 
			
		||||
     *  这里对应 pay_extension 里面的 no
 | 
			
		||||
     * 例如说,P202110132239124200055
 | 
			
		||||
     */
 | 
			
		||||
    private String tradeNo;
 | 
			
		||||
 | 
			
		||||
    // ========== 商户相关字段 ==========
 | 
			
		||||
    /**
 | 
			
		||||
     * 商户订单编号
 | 
			
		||||
     *
 | 
			
		||||
     * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
 | 
			
		||||
     */
 | 
			
		||||
    private String merchantOrderId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。
 | 
			
		||||
     * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
 | 
			
		||||
     * 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等
 | 
			
		||||
     * 使用商户退款单,作为退款请求号
 | 
			
		||||
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 中的 out_refund_no
 | 
			
		||||
     * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
 | 
			
		||||
     * 退款请求号。
 | 
			
		||||
     * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
 | 
			
		||||
     * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
 | 
			
		||||
     * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
 | 
			
		||||
     * 退款单请求号,根据规则生成
 | 
			
		||||
     * 例如说,R202109181134287570000
 | 
			
		||||
     * 商户退款订单号
 | 
			
		||||
     *
 | 
			
		||||
     * 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
 | 
			
		||||
     */
 | 
			
		||||
    // TODO @jason:merchantRefundNo =》merchantRefundOId
 | 
			
		||||
    private String merchantRefundNo;
 | 
			
		||||
 | 
			
		||||
    private String merchantRefundId;
 | 
			
		||||
    /**
 | 
			
		||||
     * 异步通知地址
 | 
			
		||||
     */
 | 
			
		||||
    private String notifyUrl;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 通知商户退款结果的回调状态
 | 
			
		||||
     * TODO 0 未发送 1 已发送
 | 
			
		||||
     *
 | 
			
		||||
     * 枚举 {@link PayNotifyStatusEnum}
 | 
			
		||||
     */
 | 
			
		||||
    private Integer notifyStatus;
 | 
			
		||||
 | 
			
		||||
@@ -142,22 +131,27 @@ public class PayRefundDO extends BaseDO {
 | 
			
		||||
 | 
			
		||||
    // ========== 渠道相关字段 ==========
 | 
			
		||||
    /**
 | 
			
		||||
     * 渠道订单号,pay_order 中的channel_order_no 对应
 | 
			
		||||
     * 渠道订单号
 | 
			
		||||
     *
 | 
			
		||||
     * 冗余 {@link PayOrderDO#getChannelOrderNo()}
 | 
			
		||||
     */
 | 
			
		||||
    private String channelOrderNo;
 | 
			
		||||
    /**
 | 
			
		||||
     * 微信中的 refund_id
 | 
			
		||||
     * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
 | 
			
		||||
     * 支付宝没有
 | 
			
		||||
     * 渠道退款单号,渠道返回
 | 
			
		||||
     * 渠道退款单号
 | 
			
		||||
     *
 | 
			
		||||
     * 1. 微信退款:对应 <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_4">申请退款</a> 的 refund_id 字段
 | 
			
		||||
     * 2. 支付宝退款:没有字段
 | 
			
		||||
     */
 | 
			
		||||
    private String channelRefundNo;
 | 
			
		||||
    /**
 | 
			
		||||
     * 退款成功时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime successTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 调用渠道的错误码
 | 
			
		||||
     */
 | 
			
		||||
    private String channelErrorCode;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 调用渠道报错时,错误信息
 | 
			
		||||
     */
 | 
			
		||||
@@ -165,22 +159,15 @@ public class PayRefundDO extends BaseDO {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 支付渠道的额外参数
 | 
			
		||||
     * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
 | 
			
		||||
     *
 | 
			
		||||
     * 参见 <a href="https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html">参数说明</>
 | 
			
		||||
     */
 | 
			
		||||
    private String channelExtras;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * TODO
 | 
			
		||||
     * 退款失效时间
 | 
			
		||||
     * 支付渠道异步通知的内容
 | 
			
		||||
     *
 | 
			
		||||
     * 在退款成功后,会记录回调的数据
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime expireTime;
 | 
			
		||||
    /**
 | 
			
		||||
     * 退款成功时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime successTime;
 | 
			
		||||
    /**
 | 
			
		||||
     * 退款通知时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime notifyTime;
 | 
			
		||||
    private String channelNotifyData;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,8 +45,10 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
 | 
			
		||||
        return selectOne("req_no", reqNo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO 芋艿:要重构
 | 
			
		||||
    default  PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
 | 
			
		||||
        return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
 | 
			
		||||
//        return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -83,17 +83,20 @@ public class PayChannelServiceImpl implements PayChannelService {
 | 
			
		||||
     */
 | 
			
		||||
    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
 | 
			
		||||
    public void refreshLocalCache() {
 | 
			
		||||
        // 情况一:如果缓存里没有数据,则直接刷新缓存
 | 
			
		||||
        if (CollUtil.isEmpty(channelCache)) {
 | 
			
		||||
            initLocalCache();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 注意:忽略自动多租户,因为要全局初始化缓存
 | 
			
		||||
        TenantUtils.executeIgnore(() -> {
 | 
			
		||||
            // 情况一:如果缓存里没有数据,则直接刷新缓存
 | 
			
		||||
            if (CollUtil.isEmpty(channelCache)) {
 | 
			
		||||
                initLocalCache();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
 | 
			
		||||
        LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannelDO::getUpdateTime);
 | 
			
		||||
        if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
 | 
			
		||||
            initLocalCache();
 | 
			
		||||
        }
 | 
			
		||||
            // 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
 | 
			
		||||
            LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannelDO::getUpdateTime);
 | 
			
		||||
            if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
 | 
			
		||||
                initLocalCache();
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,7 @@ 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.PayNotifyReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 | 
			
		||||
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;
 | 
			
		||||
@@ -103,8 +102,7 @@ public interface PayOrderService {
 | 
			
		||||
     *
 | 
			
		||||
     * @param channelId 渠道编号
 | 
			
		||||
     * @param notify    通知
 | 
			
		||||
     * @param rawNotify 通知数据
 | 
			
		||||
     */
 | 
			
		||||
    void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
 | 
			
		||||
    void notifyPayOrder(Long channelId, PayOrderRespDTO notify);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 | 
			
		||||
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.PayNotifyReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
 | 
			
		||||
@@ -148,17 +148,17 @@ public class PayOrderServiceImpl implements PayOrderService {
 | 
			
		||||
        // 3. 调用三方接口
 | 
			
		||||
        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp)
 | 
			
		||||
                // 商户相关的字段
 | 
			
		||||
                .setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
 | 
			
		||||
                .setOutTradeNo(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
 | 
			
		||||
                .setSubject(order.getSubject()).setBody(order.getBody())
 | 
			
		||||
                .setNotifyUrl(genChannelPayNotifyUrl(channel))
 | 
			
		||||
                .setReturnUrl(reqVO.getReturnUrl())
 | 
			
		||||
                // 订单相关字段
 | 
			
		||||
                .setAmount(order.getPrice()).setExpireTime(order.getExpireTime());
 | 
			
		||||
                .setPrice(order.getPrice()).setExpireTime(order.getExpireTime());
 | 
			
		||||
        PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
 | 
			
		||||
 | 
			
		||||
        // 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
 | 
			
		||||
        if (unifiedOrderRespDTO.getNotify() != null) {
 | 
			
		||||
            notifyPayOrderSuccess(channel, unifiedOrderRespDTO.getNotify(), null);
 | 
			
		||||
        if (unifiedOrderRespDTO.getOrder() != null) {
 | 
			
		||||
            notifyPayOrder(channel, unifiedOrderRespDTO.getOrder());
 | 
			
		||||
            // 此处需要读取最新的状态
 | 
			
		||||
            order = orderMapper.selectById(order.getId());
 | 
			
		||||
        }
 | 
			
		||||
@@ -226,16 +226,26 @@ public class PayOrderServiceImpl implements PayOrderService {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(rollbackFor = Exception.class)
 | 
			
		||||
    public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
 | 
			
		||||
    public void notifyPayOrder(Long channelId, PayOrderRespDTO notify) {
 | 
			
		||||
        // 校验支付渠道是否有效
 | 
			
		||||
        PayChannelDO channel = channelService.validPayChannel(channelId);
 | 
			
		||||
        // 更新支付订单为已支付
 | 
			
		||||
        TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrderSuccess(channel, notify, rawNotify));
 | 
			
		||||
        TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrder(channel, notify));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void notifyPayOrderSuccess(PayChannelDO channel, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
 | 
			
		||||
    private void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) {
 | 
			
		||||
        // 情况一:支付成功的回调
 | 
			
		||||
        if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
 | 
			
		||||
            notifyPayOrderSuccess(channel, notify);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        // 情况二:非支付成功的回调,进行忽略
 | 
			
		||||
        log.info("[notifyPayOrder][非支付成功的回调({}),直接忽略]", toJsonString(notify));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void notifyPayOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
 | 
			
		||||
        // 1. 更新 PayOrderExtensionDO 支付成功
 | 
			
		||||
        PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(), rawNotify);
 | 
			
		||||
        PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify);
 | 
			
		||||
        // 2. 更新 PayOrderDO 支付成功
 | 
			
		||||
        Pair<Boolean, PayOrderDO> order = updatePayOrderSuccess(channel, orderExtension, notify);
 | 
			
		||||
        if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
 | 
			
		||||
@@ -250,13 +260,12 @@ public class PayOrderServiceImpl implements PayOrderService {
 | 
			
		||||
    /**
 | 
			
		||||
     * 更新 PayOrderExtensionDO 支付成功
 | 
			
		||||
     *
 | 
			
		||||
     * @param no 支付订单号(支付模块)
 | 
			
		||||
     * @param rawNotify 通知数据
 | 
			
		||||
     * @param notify 通知
 | 
			
		||||
     * @return PayOrderExtensionDO 对象
 | 
			
		||||
     */
 | 
			
		||||
    private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
 | 
			
		||||
    private PayOrderExtensionDO updatePayOrderExtensionSuccess(PayOrderRespDTO notify) {
 | 
			
		||||
        // 1. 查询 PayOrderExtensionDO
 | 
			
		||||
        PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
 | 
			
		||||
        PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
 | 
			
		||||
        if (orderExtension == null) {
 | 
			
		||||
            throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
@@ -269,10 +278,8 @@ public class PayOrderServiceImpl implements PayOrderService {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 2. 更新 PayOrderExtensionDO
 | 
			
		||||
        int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
 | 
			
		||||
                PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
 | 
			
		||||
                        .status(PayOrderStatusEnum.SUCCESS.getStatus())
 | 
			
		||||
                        .channelNotifyData(toJsonString(rawNotify)).build());
 | 
			
		||||
        int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(),
 | 
			
		||||
                PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build());
 | 
			
		||||
        if (updateCounts == 0) { // 校验状态,必须是待支付
 | 
			
		||||
            throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
 | 
			
		||||
        }
 | 
			
		||||
@@ -290,7 +297,7 @@ public class PayOrderServiceImpl implements PayOrderService {
 | 
			
		||||
     *         value:PayOrderDO 对象
 | 
			
		||||
     */
 | 
			
		||||
    private Pair<Boolean, PayOrderDO> updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
 | 
			
		||||
                                                            PayOrderNotifyRespDTO notify) {
 | 
			
		||||
                                                            PayOrderRespDTO notify) {
 | 
			
		||||
        // 1. 判断 PayOrderDO 是否处于待支付
 | 
			
		||||
        PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
 | 
			
		||||
        if (order == null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,10 @@
 | 
			
		||||
package cn.iocoder.yudao.module.pay.service.refund;
 | 
			
		||||
 | 
			
		||||
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.common.pojo.PageResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 | 
			
		||||
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;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
@@ -62,8 +61,7 @@ public interface PayRefundService {
 | 
			
		||||
     *
 | 
			
		||||
     * @param channelId  渠道编号
 | 
			
		||||
     * @param notify     通知
 | 
			
		||||
     * @param rawNotify  通知数据
 | 
			
		||||
     */
 | 
			
		||||
    void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
 | 
			
		||||
    void notifyPayRefund(Long channelId, PayRefundRespDTO notify);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,9 @@ 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.PayNotifyReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayNotifyRefundStatusEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
 | 
			
		||||
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;
 | 
			
		||||
@@ -39,14 +38,13 @@ import org.springframework.transaction.annotation.Transactional;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 退款订单 Service 实现类
 | 
			
		||||
 *
 | 
			
		||||
 * @author aquan
 | 
			
		||||
 * @author jason
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
@Slf4j
 | 
			
		||||
@@ -116,7 +114,7 @@ public class PayRefundServiceImpl implements PayRefundService {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // TODO 芋艿:待实现
 | 
			
		||||
        String merchantRefundId = RandomUtil.randomNumbers(16);
 | 
			
		||||
        String merchantRefundId = "rrr" + RandomUtil.randomNumbers(16);
 | 
			
		||||
 | 
			
		||||
        // 校验退款的条件
 | 
			
		||||
        validatePayRefund(reqDTO, order);
 | 
			
		||||
@@ -128,12 +126,11 @@ public class PayRefundServiceImpl implements PayRefundService {
 | 
			
		||||
        PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
 | 
			
		||||
        PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
 | 
			
		||||
                merchantRefundId);  // TODO 芋艿:需要优化
 | 
			
		||||
        if(Objects.nonNull(payRefundDO)){
 | 
			
		||||
        if (Objects.nonNull(payRefundDO)) {
 | 
			
		||||
            // 退款订单已经提交过。
 | 
			
		||||
            //TODO 校验相同退款单的金额
 | 
			
		||||
            // TODO @jason:咱要不封装一个 ObjectUtils.equalsAny
 | 
			
		||||
            if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
 | 
			
		||||
                    || Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) {
 | 
			
		||||
            if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())) {
 | 
			
		||||
                //已成功退款
 | 
			
		||||
                throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_SUCCEED);
 | 
			
		||||
            }
 | 
			
		||||
@@ -147,14 +144,14 @@ public class PayRefundServiceImpl implements PayRefundService {
 | 
			
		||||
                    .channelCode(order.getChannelCode())
 | 
			
		||||
                    .channelId(order.getChannelId())
 | 
			
		||||
                    .orderId(order.getId())
 | 
			
		||||
                    .merchantRefundNo(merchantRefundId) // TODO 芋艿:需要优化
 | 
			
		||||
                    .merchantRefundId(merchantRefundId)
 | 
			
		||||
                    .notifyUrl(app.getRefundNotifyUrl())
 | 
			
		||||
                    .payPrice(order.getPrice())
 | 
			
		||||
                    .refundPrice(reqDTO.getPrice())
 | 
			
		||||
                    .userIp(reqDTO.getUserIp())
 | 
			
		||||
                    .merchantOrderId(order.getMerchantOrderId())
 | 
			
		||||
                    .tradeNo(orderExtensionDO.getNo())
 | 
			
		||||
                    .status(PayRefundStatusEnum.CREATE.getStatus())
 | 
			
		||||
                    .no(orderExtensionDO.getNo())
 | 
			
		||||
                    .status(PayRefundStatusEnum.WAITING.getStatus())
 | 
			
		||||
                    .reason(reqDTO.getReason())
 | 
			
		||||
                    .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
 | 
			
		||||
                    .type(refundType.getStatus())
 | 
			
		||||
@@ -163,11 +160,9 @@ public class PayRefundServiceImpl implements PayRefundService {
 | 
			
		||||
        }
 | 
			
		||||
        // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
 | 
			
		||||
        PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
 | 
			
		||||
        unifiedReqDTO.setUserIp(reqDTO.getUserIp())
 | 
			
		||||
                .setAmount(reqDTO.getPrice())
 | 
			
		||||
                .setChannelOrderNo(order.getChannelOrderNo())
 | 
			
		||||
                .setPayTradeNo(orderExtensionDO.getNo())
 | 
			
		||||
                .setMerchantRefundId(merchantRefundId)  // TODO 芋艿:需要优化
 | 
			
		||||
        unifiedReqDTO.setPrice(reqDTO.getPrice())
 | 
			
		||||
                .setOutTradeNo(orderExtensionDO.getNo())
 | 
			
		||||
                .setOutRefundNo(merchantRefundId)  // TODO 芋艿:需要优化
 | 
			
		||||
                .setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
 | 
			
		||||
                .setReason(reqDTO.getReason());
 | 
			
		||||
        // 向渠道发起退款申请
 | 
			
		||||
@@ -191,24 +186,25 @@ public class PayRefundServiceImpl implements PayRefundService {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(rollbackFor = Exception.class)
 | 
			
		||||
    public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
 | 
			
		||||
    public void notifyPayRefund(Long channelId, PayRefundRespDTO notify) {
 | 
			
		||||
        // 校验支付渠道是否有效
 | 
			
		||||
        // TODO 芋艿:需要重构下这块的逻辑
 | 
			
		||||
        PayChannelDO channel = channelService.validPayChannel(channelId);
 | 
			
		||||
        if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
 | 
			
		||||
        if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
 | 
			
		||||
            payRefundSuccess(notify);
 | 
			
		||||
        } else {
 | 
			
		||||
            //TODO 支付异常, 支付宝似乎没有支付异常的通知。
 | 
			
		||||
            // TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
 | 
			
		||||
    private void payRefundSuccess(PayRefundRespDTO refundNotify) {
 | 
			
		||||
        // 校验退款单存在
 | 
			
		||||
        PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
 | 
			
		||||
                refundNotify.getReqNo());
 | 
			
		||||
        PayRefundDO refundDO = null; // TODO 芋艿:临时注释
 | 
			
		||||
//        PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
 | 
			
		||||
//                refundNotify.getReqNo());
 | 
			
		||||
        if (refundDO == null) {
 | 
			
		||||
            log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
 | 
			
		||||
            // TODO 芋艿:临时注释
 | 
			
		||||
//            log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
 | 
			
		||||
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -233,10 +229,10 @@ public class PayRefundServiceImpl implements PayRefundService {
 | 
			
		||||
        // 更新退款订单
 | 
			
		||||
        PayRefundDO updateRefundDO = new PayRefundDO();
 | 
			
		||||
        updateRefundDO.setId(refundDO.getId())
 | 
			
		||||
                .setSuccessTime(refundNotify.getRefundSuccessTime())
 | 
			
		||||
                .setChannelRefundNo(refundNotify.getChannelOrderNo())
 | 
			
		||||
                .setTradeNo(refundNotify.getTradeNo())
 | 
			
		||||
                .setNotifyTime(LocalDateTime.now())
 | 
			
		||||
                .setSuccessTime(refundNotify.getSuccessTime())
 | 
			
		||||
                // TODO 芋艿:如下两行,临时注释
 | 
			
		||||
//                .setChannelRefundNo(refundNotify.getChannelOrderNo())
 | 
			
		||||
//                .setNo(refundNotify.getTradeNo())
 | 
			
		||||
                .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
 | 
			
		||||
        refundMapper.updateById(updateRefundDO);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.pay.service.app;
 | 
			
		||||
import cn.hutool.core.util.RandomUtil;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
 | 
			
		||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
 | 
			
		||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 | 
			
		||||
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO;
 | 
			
		||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO;
 | 
			
		||||
@@ -18,8 +16,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
 | 
			
		||||
import org.springframework.context.annotation.Import;
 | 
			
		||||
 | 
			
		||||
import javax.annotation.Resource;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
 | 
			
		||||
            o.setChannelId(1L);
 | 
			
		||||
            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
 | 
			
		||||
            o.setOrderId(1L);
 | 
			
		||||
            o.setTradeNo("OT0000001");
 | 
			
		||||
            o.setNo("OT0000001");
 | 
			
		||||
            o.setMerchantOrderId("MOT0000001");
 | 
			
		||||
            o.setMerchantRefundNo("MRF0000001");
 | 
			
		||||
            o.setNotifyUrl("https://www.cancanzi.com");
 | 
			
		||||
@@ -127,7 +127,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
 | 
			
		||||
            o.setChannelId(1L);
 | 
			
		||||
            o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
 | 
			
		||||
            o.setOrderId(1L);
 | 
			
		||||
            o.setTradeNo("OT0000001");
 | 
			
		||||
            o.setNo("OT0000001");
 | 
			
		||||
            o.setMerchantOrderId("MOT0000001");
 | 
			
		||||
            o.setMerchantRefundNo("MRF0000001");
 | 
			
		||||
            o.setNotifyUrl("https://www.cancanzi.com");
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user