Commit dfde260ebbd9c48a7839c9951fbb5432b74cafda

Authored by jason
1 parent 444ba798

支付宝退款申请通知

Showing 13 changed files with 234 additions and 22 deletions
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundMapper.java
1 package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order; 1 package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
2 2
3 -import java.util.*;  
4 -  
5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; 3 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
6 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
7 -import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;  
8 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
9 -  
10 import org.apache.ibatis.annotations.Mapper; 5 import org.apache.ibatis.annotations.Mapper;
11 6
12 7
@@ -17,5 +12,7 @@ import org.apache.ibatis.annotations.Mapper; @@ -17,5 +12,7 @@ import org.apache.ibatis.annotations.Mapper;
17 @Mapper 12 @Mapper
18 public interface PayRefundMapper extends BaseMapperX<PayRefundDO> { 13 public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
19 14
20 - 15 + default PayRefundDO selectByReqNo(String reqNo) {
  16 + return selectOne("req_no", reqNo);
  17 + }
21 } 18 }
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
@@ -47,8 +47,8 @@ public interface PayErrorCodeCoreConstants { @@ -47,8 +47,8 @@ public interface PayErrorCodeCoreConstants {
47 ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额"); 47 ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
48 ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款"); 48 ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
49 ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空"); 49 ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空");
50 - ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006002, "未找到对应的退款后置处理类");  
51 - 50 + ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006003, "未找到对应的退款后置处理类");
  51 + ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
52 // TODO @aquan:下面还两个要合并上去哈。另外一般中英文之间要有空格。例如说, 新建一个 order 数据;这样可读性更好。 52 // TODO @aquan:下面还两个要合并上去哈。另外一般中英文之间要有空格。例如说, 新建一个 order 数据;这样可读性更好。
53 53
54 /** 54 /**
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order;
2 2
3 import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO; 3 import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO;
4 import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO; 4 import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO;
  5 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
5 6
6 /** 7 /**
7 * 退款单 Core Service 8 * 退款单 Core Service
@@ -17,4 +18,17 @@ public interface PayRefundCoreService { @@ -17,4 +18,17 @@ public interface PayRefundCoreService {
17 * @return 退款申请返回信息 18 * @return 退款申请返回信息
18 */ 19 */
19 PayRefundRespBO refund(PayRefundReqBO reqDTO); 20 PayRefundRespBO refund(PayRefundReqBO reqDTO);
  21 +
  22 +
  23 + /**
  24 + * 渠道的退款通知
  25 + * @param channelId 渠道编号
  26 + * @param channelCode 渠道编码
  27 + * @param notifyData 通知数据
  28 + * @throws Exception 退款通知异常
  29 + */
  30 + void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception;
  31 +
  32 +
  33 +
20 } 34 }
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java
@@ -10,7 +10,11 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO @@ -10,7 +10,11 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO
10 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; 10 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
11 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper; 11 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper;
12 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; 12 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  13 +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
13 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; 14 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
  15 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
  16 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
  17 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
14 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; 18 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
15 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; 19 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
16 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; 20 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
@@ -24,8 +28,10 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO @@ -24,8 +28,10 @@ import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO
24 import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils; 28 import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
25 import cn.iocoder.yudao.framework.pay.core.client.PayClient; 29 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
26 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; 30 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
  31 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
27 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; 32 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
28 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; 33 import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
  34 +import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
29 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; 35 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
30 import lombok.extern.slf4j.Slf4j; 36 import lombok.extern.slf4j.Slf4j;
31 import org.springframework.stereotype.Service; 37 import org.springframework.stereotype.Service;
@@ -66,6 +72,9 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -66,6 +72,9 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
66 @Resource 72 @Resource
67 private List<PayRefundChannelPostHandler> handlerList; 73 private List<PayRefundChannelPostHandler> handlerList;
68 74
  75 + @Resource
  76 + private PayNotifyCoreService payNotifyCoreService;
  77 +
69 78
70 private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> mapHandler = new EnumMap<>(PayChannelRespEnum.class); 79 private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> mapHandler = new EnumMap<>(PayChannelRespEnum.class);
71 80
@@ -84,7 +93,6 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -84,7 +93,6 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
84 93
85 } 94 }
86 95
87 -  
88 @Override 96 @Override
89 @Transactional(rollbackFor = Exception.class) 97 @Transactional(rollbackFor = Exception.class)
90 public PayRefundRespBO refund(PayRefundReqBO reqBO) { 98 public PayRefundRespBO refund(PayRefundReqBO reqBO) {
@@ -173,6 +181,64 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -173,6 +181,64 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
173 } 181 }
174 182
175 183
  184 + @Override
  185 + public void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) {
  186 + log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
  187 + // 校验支付渠道是否有效
  188 + PayChannelDO channel = payChannelCoreService.validPayChannel(channelId);
  189 + // 校验支付客户端是否正确初始化
  190 + PayClient client = payClientFactory.getPayClient(channel.getId());
  191 + if (client == null) {
  192 + log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
  193 + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
  194 + }
  195 + //解析渠道退款通知数据, 统一处理
  196 + PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
  197 +
  198 + if(Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
  199 + //退款成功。 支付宝只有退款成功才会发通知
  200 + PayRefundDO refundDO = payRefundMapper.selectByReqNo(refundNotify.getReqNo());
  201 + if (refundDO == null) {
  202 + log.error("不存在 seqNo 为{} 的支付退款单",refundNotify.getReqNo());
  203 + throw exception(PAY_REFUND_NOT_FOUND);
  204 + }
  205 + Long refundAmount = refundDO.getRefundAmount();
  206 + Integer type = refundDO.getType();
  207 + PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
  208 + if(PayRefundTypeEnum.ALL.getStatus().equals(type)){
  209 + orderStatus = PayOrderStatusEnum.CLOSED;
  210 + }
  211 + //更新支付订单
  212 + PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
  213 + //需更新已退金额
  214 + Long refundedAmount = payOrderDO.getRefundAmount();
  215 + PayOrderDO updateOrderDO = new PayOrderDO();
  216 + updateOrderDO.setId(refundDO.getOrderId())
  217 + .setRefundAmount(refundedAmount + refundAmount)
  218 + .setStatus(orderStatus.getStatus())
  219 + .setRefundStatus(type);
  220 + payOrderCoreMapper.updateById(updateOrderDO);
  221 +
  222 + //跟新退款订单
  223 + PayRefundDO updateRefundDO = new PayRefundDO();
  224 + updateRefundDO.setId(refundDO.getId())
  225 + .setSuccessTime(refundNotify.getRefundSuccessTime())
  226 + .setChannelRefundNo(refundNotify.getChannelOrderNo())
  227 + .setTradeNo(refundNotify.getTradeNo())
  228 + .setNotifyTime(new Date())
  229 + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
  230 + payRefundMapper.updateById(updateRefundDO);
  231 +
  232 + //插入退款通知记录
  233 + // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
  234 + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
  235 + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
  236 + }else{
  237 + //TODO 退款失败
  238 + }
  239 +
  240 + }
  241 +
176 /** 242 /**
177 * 校验是否进行退款 243 * 校验是否进行退款
178 * @param reqBO 退款申请信息 244 * @param reqBO 退款申请信息
@@ -197,7 +263,5 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { @@ -197,7 +263,5 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
197 throw exception(PAY_REFUND_CHN_ORDER_NO_IS_NULL); 263 throw exception(PAY_REFUND_CHN_ORDER_NO_IS_NULL);
198 } 264 }
199 //TODO 退款的期限 退款次数的控制 265 //TODO 退款的期限 退款次数的控制
200 -  
201 -  
202 } 266 }
203 } 267 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
@@ -42,4 +42,11 @@ public interface PayClient { @@ -42,4 +42,11 @@ public interface PayClient {
42 */ 42 */
43 PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); 43 PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
44 44
  45 + /**
  46 + * 解析支付退款通知数据
  47 + * @param notifyData 支付退款通知请求数据
  48 + * @return 支付退款通知的Notify DTO
  49 + */
  50 + PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
  51 +
45 } 52 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java
@@ -8,7 +8,7 @@ import java.util.Map; @@ -8,7 +8,7 @@ import java.util.Map;
8 8
9 9
10 /** 10 /**
11 - * 支付订单回调,渠道的统一通知请求数据 11 + * 支付订单,退款订单回调,渠道的统一通知请求数据
12 */ 12 */
13 @Data 13 @Data
14 @ToString 14 @ToString
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.ToString;
  7 +
  8 +import java.util.Date;
  9 +
  10 +/**
  11 + * 从渠道返回数据中解析得到的支付退款通知的Notify DTO
  12 + *
  13 + * @author jason
  14 + */
  15 +@Data
  16 +@ToString
  17 +@Builder
  18 +public class PayRefundNotifyDTO {
  19 +
  20 + /**
  21 + * 支付渠道编号
  22 + */
  23 + private String channelOrderNo;
  24 +
  25 +
  26 + /**
  27 + * 交易订单号,根据规则生成
  28 + * 调用支付渠道时,使用该字段作为对接的订单号。
  29 + * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
  30 + * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
  31 + * 这里对应 pay_extension 里面的 no
  32 + * 例如说,P202110132239124200055
  33 + */
  34 + private String tradeNo;
  35 +
  36 + /**
  37 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
  38 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
  39 + * 退款请求号。
  40 + * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
  41 + * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
  42 + * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
  43 + * 退款单请求号,根据规则生成
  44 + *
  45 + * 例如说,RR202109181134287570000
  46 + */
  47 + private String reqNo;
  48 +
  49 +
  50 + /**
  51 + * 退款是否成功
  52 + */
  53 + private PayNotifyRefundStatusEnum status;
  54 +
  55 +
  56 +
  57 + /**
  58 + * 退款成功时间
  59 + */
  60 + private Date refundSuccessTime;
  61 +
  62 +
  63 +}
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
@@ -84,6 +84,12 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt; @@ -84,6 +84,12 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
84 } 84 }
85 85
86 @Override 86 @Override
  87 + public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
  88 + //TODO 需要实现
  89 + throw new UnsupportedOperationException("需要实现");
  90 + }
  91 +
  92 + @Override
87 protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { 93 protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
88 //TODO 需要实现 94 //TODO 需要实现
89 throw new UnsupportedOperationException(); 95 throw new UnsupportedOperationException();
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
6 import cn.iocoder.yudao.framework.pay.core.client.dto.*; 6 import cn.iocoder.yudao.framework.pay.core.client.dto.*;
7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; 7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
8 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; 8 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
  9 +import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
9 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; 10 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
10 import com.alipay.api.AlipayApiException; 11 import com.alipay.api.AlipayApiException;
11 import com.alipay.api.AlipayConfig; 12 import com.alipay.api.AlipayConfig;
@@ -103,7 +104,6 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt; @@ -103,7 +104,6 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
103 .data(data.getBody()).build(); 104 .data(data.getBody()).build();
104 } 105 }
105 106
106 -  
107 @Override 107 @Override
108 protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { 108 protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
109 AlipayTradeRefundModel model=new AlipayTradeRefundModel(); 109 AlipayTradeRefundModel model=new AlipayTradeRefundModel();
@@ -119,11 +119,10 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt; @@ -119,11 +119,10 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
119 AlipayTradeRefundResponse response = client.execute(refundRequest); 119 AlipayTradeRefundResponse response = client.execute(refundRequest);
120 log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response)); 120 log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
121 if (response.isSuccess()) { 121 if (response.isSuccess()) {
122 - //退款成功 122 + //退款成功,更新为PROCESSING_NOTIFY, 而不是 SYNC_SUCCESS 通过支付宝回调接口处理。退款导致触发的异步通知,
  123 + //退款导致触发的异步通知是发送到支付接口中设置的notify_url
123 //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对? 124 //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对?
124 - respDTO.setRespEnum(PayChannelRespEnum.SYNC_SUCCESS)  
125 - .setChannelRefundNo(response.getTradeNo())  
126 - .setPayTradeNo(response.getOutTradeNo()); 125 + respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY);
127 }else{ 126 }else{
128 //特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务 127 //特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务
129 //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain 128 //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain
@@ -153,12 +152,20 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt; @@ -153,12 +152,20 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
153 .setChannelErrMsg(e.getErrMsg()) 152 .setChannelErrMsg(e.getErrMsg())
154 .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION); 153 .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
155 } 154 }
156 -  
157 return respDTO; 155 return respDTO;
158 -  
159 } 156 }
  157 + }
160 158
161 - 159 + @Override
  160 + public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
  161 + Map<String, String> params = notifyData.getParams();
  162 + PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
  163 + .tradeNo(params.get("out_trade_no"))
  164 + .reqNo(params.get("out_biz_no"))
  165 + .status(PayNotifyRefundStatusEnum.SUCCESS)
  166 + .refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
  167 + .build();
  168 + return notifyDTO;
162 } 169 }
163 170
164 } 171 }
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
@@ -141,6 +141,12 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; { @@ -141,6 +141,12 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
141 .data(data.getBody()).build(); 141 .data(data.getBody()).build();
142 } 142 }
143 143
  144 + @Override
  145 + public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
  146 + //TODO 需要实现
  147 + throw new UnsupportedOperationException("需要实现");
  148 + }
  149 +
144 150
145 @Override 151 @Override
146 protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { 152 protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayNotifyRefundStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.enums;
  2 +
  3 +/**
  4 + * 退款通知, 统一的渠道退款状态
  5 + *
  6 + * @author jason
  7 + */
  8 +public enum PayNotifyRefundStatusEnum {
  9 + /**
  10 + * 支付宝 中 全额退款 trade_status=TRADE_CLOSED, 部分退款 trade_status=TRADE_SUCCESS
  11 + * 退款成功
  12 + */
  13 + SUCCESS,
  14 +
  15 + /**
  16 + * 支付宝退款通知没有这个状态
  17 + * 退款异常
  18 + */
  19 + ABNORMAL;
  20 +}
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order;
3 import cn.hutool.core.bean.BeanUtil; 3 import cn.hutool.core.bean.BeanUtil;
4 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; 4 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
5 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; 5 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
6 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; 7 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO;
7 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; 8 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 9 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
@@ -34,6 +35,9 @@ public class PayOrderController { @@ -34,6 +35,9 @@ public class PayOrderController {
34 @Resource 35 @Resource
35 private PayOrderCoreService payOrderCoreService; 36 private PayOrderCoreService payOrderCoreService;
36 37
  38 + @Resource
  39 + private PayRefundCoreService payRefundCoreService;
  40 +
37 @PostMapping("/submit") 41 @PostMapping("/submit")
38 @ApiOperation("提交支付订单") 42 @ApiOperation("提交支付订单")
39 // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 43 // @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
@@ -85,11 +89,21 @@ public class PayOrderController { @@ -85,11 +89,21 @@ public class PayOrderController {
85 public String notifyAliPayWapPayOrder(@PathVariable("channelId") Long channelId, 89 public String notifyAliPayWapPayOrder(@PathVariable("channelId") Long channelId,
86 @RequestParam Map<String, String> params, 90 @RequestParam Map<String, String> params,
87 @RequestBody String originData) throws Exception { 91 @RequestBody String originData) throws Exception {
88 - //TODO @jason 校验 是否支付宝调用。 payclient 中加一个校验方法  
89 - payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build()); 92 + //TODO 校验是否支付宝调用。 payclient 中加一个校验方法
  93 + //支付宝退款交易也会触发支付回调接口
  94 + //参考 https://opensupport.alipay.com/support/helpcenter/193/201602484851
  95 + //判断是否为支付宝的退款交易
  96 + if(isAliPayRefund(params)) {
  97 + //退款通知
  98 + payRefundCoreService.notifyPayRefund(channelId,PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build());
  99 + }else{
  100 + //支付通知
  101 + payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build());
  102 + }
90 return "success"; 103 return "success";
91 } 104 }
92 105
  106 +
93 /** 107 /**
94 * https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E 108 * https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
95 * @param channelId 渠道id 109 * @param channelId 渠道id
@@ -102,6 +116,19 @@ public class PayOrderController { @@ -102,6 +116,19 @@ public class PayOrderController {
102 return "支付成功"; 116 return "支付成功";
103 } 117 }
104 118
  119 + /**
  120 + * 是否是支付宝的退款交易
  121 + * @param params http content-type application/x-www-form-urlencoded 的参数
  122 + * @return
  123 + */
  124 + private boolean isAliPayRefund(Map<String, String> params) {
  125 + if (params.containsKey("refund_fee")) {
  126 + return true;
  127 + } else {
  128 + return false;
  129 + }
  130 + }
  131 +
105 @RequestMapping("/notify/test") 132 @RequestMapping("/notify/test")
106 @ApiOperation("通知的测试接口") 133 @ApiOperation("通知的测试接口")
107 public String notifyTest() { 134 public String notifyTest() {
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java
@@ -37,6 +37,7 @@ public class PayRefundController { @@ -37,6 +37,7 @@ public class PayRefundController {
37 reqBO.setUserIp(getClientIP()); 37 reqBO.setUserIp(getClientIP());
38 //TODO 测试暂时模拟生成商户退款订单 38 //TODO 测试暂时模拟生成商户退款订单
39 reqBO.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo()); 39 reqBO.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
  40 + //reqBO.setMerchantRefundNo("MO202111210814084370000");
40 return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.refund(reqBO))); 41 return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.refund(reqBO)));
41 } 42 }
42 } 43 }