Commit 7d6f205dc0178108c4bf4b5043456620c5d7ac78

Authored by jason
1 parent 19f55417

支付退款申请,支付宝手机wap 相应实现

Showing 41 changed files with 1515 additions and 57 deletions
sql/pay_refund.sql 0 → 100644
  1 +DROP TABLE IF EXISTS `pay_refund`;
  2 +CREATE TABLE `pay_refund` (
  3 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编号',
  4 + `req_no` varchar(64) NOT NULL COMMENT '退款单请求号',
  5 + `merchant_id` bigint NOT NULL COMMENT '商户编号',
  6 + `app_id` bigint NOT NULL COMMENT '应用编号',
  7 + `channel_id` bigint NOT NULL COMMENT '渠道编号',
  8 + `channel_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠道编码',
  9 + `order_id` bigint NOT NULL COMMENT '支付订单编号 pay_order 表id',
  10 + `trade_no` varchar(64) NOT NULL COMMENT '交易订单号 pay_extension 表no 字段',
  11 + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号(商户系统生成)',
  12 + `merchant_refund_no` varchar(64) NOT NULL COMMENT '商户退款订单号(商户系统生成)',
  13 + `notify_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '异步通知商户地址',
  14 + `notify_status` tinyint NOT NULL COMMENT '通知商户退款结果的回调状态',
  15 + `status` tinyint NOT NULL COMMENT '退款状态',
  16 + `type` tinyint NOT NULL COMMENT '退款类型(部分退款,全部退款)',
  17 + `pay_amount` bigint NOT NULL COMMENT '支付金额,单位分',
  18 + `refund_amount` bigint NOT NULL COMMENT '退款金额,单位分',
  19 + `reason` VARCHAR(256) NOT NULL COMMENT '退款原因',
  20 + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户 IP',
  21 + `channel_order_no` varchar(64) NOT NULL COMMENT '渠道订单号,pay_order 中的channel_order_no 对应',
  22 + `channel_refund_no` varchar(64) DEFAULT NULL COMMENT '渠道退款单号,渠道返回',
  23 + `channel_error_code` varchar(128) DEFAULT NULL COMMENT '渠道调用报错时,错误码',
  24 + `channel_error_msg` varchar(256) DEFAULT NULL COMMENT '渠道调用报错时,错误信息',
  25 + `channel_extras` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付渠道的额外参数',
  26 + `expire_time` datetime DEFAULT NULL COMMENT '退款失效时间',
  27 + `success_time` datetime DEFAULT NULL COMMENT '退款成功时间',
  28 + `notify_time` datetime DEFAULT NULL COMMENT '退款通知时间',
  29 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  30 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  31 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  32 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  33 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  34 + PRIMARY KEY (`id`) USING BTREE
  35 +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='退款订单';
... ...
sql/ruoyi-vue-pro.sql
... ... @@ -953,7 +953,7 @@ CREATE TABLE `pay_app` (
953 953 -- Records of pay_app
954 954 -- ----------------------------
955 955 BEGIN;
956   -INSERT INTO `pay_app` VALUES (6, '芋道', 0, '我是一个公众号', 'http://127.0.0.1:28080/api/shop/order/pay-notify', 'http://127.0.0.1', 1, '', '2021-10-23 08:49:25', '', '2021-10-27 00:26:35', b'0');
  956 +INSERT INTO `pay_app` VALUES (6, '芋道', 0, '我是一个公众号', 'http://127.0.0.1:28080/api/shop/order/pay-notify', 'http://127.0.0.1:28080/api/shop/order/refund-notify', 1, '', '2021-10-23 08:49:25', '', '2021-10-27 00:26:35', b'0');
957 957 COMMIT;
958 958  
959 959 -- ----------------------------
... ... @@ -1240,6 +1240,43 @@ INSERT INTO `pay_order` VALUES (120, 1, 6, 9, 'wx_pub', '1635311949168', '标题
1240 1240 INSERT INTO `pay_order` VALUES (121, 1, 6, 9, 'wx_pub', '1635312124657', '标题:1635312124656', '内容:1635312124656', 'http://127.0.0.1:28080/api/shop/order/pay-notify', 0, 1, 0, 0, 10, '101.82.233.75', '2021-10-28 13:22:05', '2021-10-27 13:22:15', '2021-10-27 13:22:16', 100, 0, 0, 0, 'ockUAwIZ-0OeMZl9ogcZ4ILrGba0', '4200001174202110278060590766', NULL, '2021-10-27 13:22:05', NULL, '2021-10-27 13:22:16', b'0');
1241 1241 COMMIT;
1242 1242  
  1243 +DROP TABLE IF EXISTS `pay_refund`;
  1244 +CREATE TABLE `pay_refund` (
  1245 + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编号',
  1246 + `req_no` varchar(64) NOT NULL COMMENT '退款单请求号',
  1247 + `merchant_id` bigint NOT NULL COMMENT '商户编号',
  1248 + `app_id` bigint NOT NULL COMMENT '应用编号',
  1249 + `channel_id` bigint NOT NULL COMMENT '渠道编号',
  1250 + `channel_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠道编码',
  1251 + `order_id` bigint NOT NULL COMMENT '支付订单编号 pay_order 表id',
  1252 + `trade_no` varchar(64) NOT NULL COMMENT '交易订单号 pay_extension 表no 字段',
  1253 + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订单编号(商户系统生成)',
  1254 + `merchant_refund_no` varchar(64) NOT NULL COMMENT '商户退款订单号(商户系统生成)',
  1255 + `notify_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '异步通知商户地址',
  1256 + `notify_status` tinyint NOT NULL COMMENT '通知商户退款结果的回调状态',
  1257 + `status` tinyint NOT NULL COMMENT '退款状态',
  1258 + `type` tinyint NOT NULL COMMENT '退款类型(部分退款,全部退款)',
  1259 + `pay_amount` bigint NOT NULL COMMENT '支付金额,单位分',
  1260 + `refund_amount` bigint NOT NULL COMMENT '退款金额,单位分',
  1261 + `reason` VARCHAR(256) NOT NULL COMMENT '退款原因',
  1262 + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户 IP',
  1263 + `channel_order_no` varchar(64) NOT NULL COMMENT '渠道订单号,pay_order 中的channel_order_no 对应',
  1264 + `channel_refund_no` varchar(64) DEFAULT NULL COMMENT '渠道退款单号,渠道返回',
  1265 + `channel_error_code` varchar(128) DEFAULT NULL COMMENT '渠道调用报错时,错误码',
  1266 + `channel_error_msg` varchar(256) DEFAULT NULL COMMENT '渠道调用报错时,错误信息',
  1267 + `channel_extras` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付渠道的额外参数',
  1268 + `expire_time` datetime DEFAULT NULL COMMENT '退款失效时间',
  1269 + `success_time` datetime DEFAULT NULL COMMENT '退款成功时间',
  1270 + `notify_time` datetime DEFAULT NULL COMMENT '退款通知时间',
  1271 + `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  1272 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  1273 + `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  1274 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  1275 + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  1276 + PRIMARY KEY (`id`) USING BTREE
  1277 +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='退款订单';
  1278 +
  1279 +
1243 1280 -- ----------------------------
1244 1281 -- Table structure for pay_order_extension
1245 1282 -- ----------------------------
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.convert.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  6 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +import org.mapstruct.Mapping;
  10 +import org.mapstruct.Mappings;
  11 +
  12 +@Mapper
  13 +public interface PayRefundCoreConvert {
  14 +
  15 + PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class);
  16 +
  17 + PayRefundPostReqBO convert(PayRefundUnifiedRespDTO respDTO);
  18 +
  19 + //TODO 太多需要处理了, 暂时不用
  20 + @Mappings(value = {
  21 + @Mapping(source = "amount", target = "payAmount"),
  22 + @Mapping(source = "id", target = "orderId"),
  23 + @Mapping(target = "status",ignore = true)
  24 + })
  25 + PayRefundDO convert(PayOrderDO orderDO);
  26 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java
... ... @@ -4,7 +4,7 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO
4 4 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
5 5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
6 6 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
7   -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderRefundStatusEnum;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
8 8 import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
9 9 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
10 10 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
... ... @@ -135,7 +135,7 @@ public class PayOrderDO extends BaseDO {
135 135 /**
136 136 * 退款状态
137 137 *
138   - * 枚举 {@link PayOrderRefundStatusEnum}
  138 + * 枚举 {@link PayRefundTypeEnum}
139 139 */
140 140 private Integer refundStatus;
141 141 /**
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java
... ... @@ -5,7 +5,9 @@ import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChann
5 5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
6 6 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
7 7 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
8   -import lombok.Data;
  8 +import com.baomidou.mybatisplus.annotation.TableId;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import lombok.*;
9 11  
10 12 import java.util.Date;
11 13  
... ... @@ -17,19 +19,34 @@ import java.util.Date;
17 19 *
18 20 * @author 芋道源码
19 21 */
  22 +@TableName("pay_refund")
20 23 @Data
  24 +@EqualsAndHashCode(callSuper = true)
  25 +@ToString(callSuper = true)
  26 +@Builder
  27 +@NoArgsConstructor
  28 +@AllArgsConstructor
21 29 public class PayRefundDO extends BaseDO {
22 30  
23 31 /**
24 32 * 退款单编号,数据库自增
25 33 */
  34 + @TableId
26 35 private Long id;
27   - /**
28   - * 退款单号,根据规则生成
29   - *
  36 +
  37 + /**
  38 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
  39 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
  40 + * 退款请求号。
  41 + * 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
  42 + * 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
  43 + * 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
  44 + * 退款单请求号,根据规则生成
  45 + *
30 46 * 例如说,R202109181134287570000
31 47 */
32   - private String no;
  48 + private String reqNo;
  49 +
33 50 /**
34 51 * 商户编号
35 52 *
... ... @@ -61,23 +78,39 @@ public class PayRefundDO extends BaseDO {
61 78 */
62 79 private Long orderId;
63 80  
  81 +
  82 + /**
  83 + * 交易订单号,根据规则生成
  84 + * 调用支付渠道时,使用该字段作为对接的订单号。
  85 + * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
  86 + * 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
  87 + * 这里对应 pay_extension 里面的 no
  88 + * 例如说,P202110132239124200055
  89 + */
  90 + private String tradeNo;
  91 +
  92 +
64 93 // ========== 商户相关字段 ==========
65 94 /**
66   - * 商户退款订单号
67   - * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一 TODO 芋艿:需要在测试下
  95 + * 商户订单编号
  96 + */
  97 + private String merchantOrderId;
  98 + /**
  99 + * 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。发送channel 使用 reqNo
  100 + * 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
  101 + * TODO 芋艿:我理解 一个商户退款订单,可以对应多条退款记录, 因为有可能失败。但是 退款请求号 reqNo 必须唯一
  102 + *
68 103 */
69 104 private String merchantRefundNo;
70   -// /**
71   -// * 商户拓展参数
72   -// */
73   -// private String merchantExtra;
  105 +
74 106 /**
75 107 * 异步通知地址
76 108 */
77 109 private String notifyUrl;
  110 +
78 111 /**
79 112 * 通知商户退款结果的回调状态
80   - * TODO 芋艿:0 未发送 1 已发送
  113 + * TODO 0 未发送 1 已发送
81 114 */
82 115 private Integer notifyStatus;
83 116  
... ... @@ -85,44 +118,76 @@ public class PayRefundDO extends BaseDO {
85 118 /**
86 119 * 退款状态
87 120 *
88   - * TODO 芋艿:状态枚举
89 121 */
90 122 private Integer status;
  123 +
91 124 /**
92   - * 用户 IP
  125 + * 退款类型(部分退款,全部退款)
93 126 */
94   - private String userIp;
  127 + private Integer type;
95 128 /**
96   - * 退款金额,单位:分
  129 + * 支付金额,单位分
97 130 */
98   - private Long amount;
  131 + private Long payAmount;
  132 + /**
  133 + * 退款金额,单位分
  134 + */
  135 + private Long refundAmount;
  136 +
99 137 /**
100 138 * 退款原因
101 139 */
102 140 private String reason;
  141 +
  142 +
103 143 /**
104   - * 订单退款成功时间
  144 + * 用户 IP
105 145 */
106   - private Date successTime;
  146 + private String userIp;
  147 +
  148 + // ========== 渠道相关字段 ==========
107 149 /**
108   - * 退款失效时间
  150 + * 渠道订单号,pay_order 中的channel_order_no 对应
109 151 */
110   - private Date expireTime;
  152 + private String channelOrderNo;
  153 + /**
  154 + * 渠道退款单号,渠道返回
  155 + */
  156 + private String channelRefundNo;
  157 +
  158 + /**
  159 + * 调用渠道的错误码
  160 + */
  161 + private String channelErrorCode;
  162 +
  163 + /**
  164 + * 调用渠道报错时,错误信息
  165 + */
  166 + private String channelErrorMsg;
  167 +
  168 +
111 169 /**
112 170 * 支付渠道的额外参数
113   - *
114 171 * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
115 172 */
116   - private String channelExtra;
  173 + private String channelExtras;
  174 +
117 175  
118   - // ========== 渠道相关字段 ==========
119 176 /**
120   - * 渠道订单号
  177 + * TODO
  178 + * 退款失效时间
121 179 */
122   - private String channelOrderNo;
  180 + private Date expireTime;
123 181 /**
124   - * 渠道退款号
  182 + * 退款成功时间
125 183 */
126   - private String channelRefundNo;
  184 + private Date successTime;
  185 + /**
  186 + * 退款通知时间
  187 + */
  188 + private Date notifyTime;
  189 +
  190 +
  191 +
127 192  
128 193 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
  2 +
  3 +import java.util.*;
  4 +
  5 +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;
  9 +
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +
  12 +
  13 +/**
  14 + * 退款订单 Mapper
  15 + *
  16 + */
  17 +@Mapper
  18 +public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
  19 +
  20 +
  21 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
... ... @@ -28,6 +28,12 @@ public interface PayErrorCodeCoreConstants {
28 28 ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展单不处于待支付");
29 29 ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007003002, "支付订单不处于已支付");
30 30  
  31 + // ========== 支付模块(退款) 1-007-006-000 ==========
  32 + ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金额超过订单可退款金额");
  33 + ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订单已经全额退款");
  34 + ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订单的渠道订单为空");
  35 + ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006002, "未找到对应的退款后置处理类");
  36 +
31 37 /**
32 38 * ========== 支付商户信息 1-007-004-000 ==========
33 39 */
... ... @@ -41,11 +47,11 @@ public interface PayErrorCodeCoreConstants {
41 47  
42 48  
43 49 /**
44   - * ========== 支付渠道 1-007-006-000 ==========
  50 + * ========== 支付渠道 1-007-001-000 ==========
45 51 */
46   - ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007006000, "支付渠道不存在");
47   - ErrorCode CHANNEL_KEY_READ_ERROR = new ErrorCode(1007006002, "支付渠道秘钥文件读取失败");
  52 + ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在");
  53 + ErrorCode CHANNEL_KEY_READ_ERROR = new ErrorCode(1007001004, "支付渠道秘钥文件读取失败");
48 54 // TODO @aquan:下面这个错误码,缺了 CHANNEL 前缀。另外,错误码的分段,上面有啦,合并下进去哈
49   - ErrorCode EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007006003, "已存在相同的渠道");
  55 + ErrorCode EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道");
50 56  
51 57 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +@Getter
  7 +@AllArgsConstructor
  8 +public enum PayRefundStatusEnum {
  9 + CREATE(0, "退款订单生成"),
  10 + SUCCESS(1, "退款成功"),
  11 + FAILURE(2, "退款失败"),
  12 + PROCESSING_NOTIFY(3,"退款中, 渠道通知结果"),
  13 + PROCESSING_QUERY(4,"退款中, 系统查询结果"),
  14 + UNKNOWN_RETRY(5,"状态未知,需要重试"),
  15 + UNKNOWN_QUERY(6,"状态未知,系统查询结果"),
  16 + CLOSE(99, "退款关闭");
  17 +
  18 + private final Integer status;
  19 + private final String name;
  20 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java renamed to yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java
... ... @@ -11,7 +11,7 @@ import lombok.Getter;
11 11 */
12 12 @Getter
13 13 @AllArgsConstructor
14   -public enum PayOrderRefundStatusEnum implements IntArrayValuable {
  14 +public enum PayRefundTypeEnum implements IntArrayValuable {
15 15  
16 16 NO(0, "未退款"),
17 17 SOME(10, "部分退款"),
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java
... ... @@ -5,8 +5,10 @@ import cn.hutool.http.HttpUtil;
5 5 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyLogDO;
6 6 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
7 7 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
8 9 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyLogCoreMapper;
9 10 import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
  11 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
10 12 import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO;
11 13 import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
12 14 import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
... ... @@ -73,6 +75,9 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
73 75 private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO;
74 76  
75 77 @Resource
  78 + private PayRefundMapper payRefundMapper;
  79 +
  80 + @Resource
76 81 @Lazy // 循环依赖(自己依赖自己),避免报错
77 82 private PayNotifyCoreServiceImpl self;
78 83  
... ... @@ -89,7 +94,9 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
89 94 setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
90 95 } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
91 96 // TODO 芋艿,需要实现下哈
92   - throw new UnsupportedOperationException("需要实现");
  97 + PayRefundDO refundDO = payRefundMapper.selectById(task.getDataId());
  98 + task.setMerchantId(refundDO.getMerchantId()).setAppId(refundDO.getAppId())
  99 + .setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
93 100 }
94 101  
95 102 // 执行插入
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java
... ... @@ -17,12 +17,15 @@ import javax.validation.constraints.NotNull;
17 17 @AllArgsConstructor
18 18 public class PayRefundOrderReqVO {
19 19  
20   - @ApiModelProperty(value = "商户订单编号", required = true, example = "10")
21   - @NotEmpty(message = "商户订单号不能为空")
  20 + @ApiModelProperty(value = "商户退款单编号", required = true, example = "10")
  21 + @NotEmpty(message = "商户退款单编号不能为空")
22 22 private String merchantOrderId;
23 23  
24 24 @ApiModelProperty(value = "支付退款编号", required = true, example = "20")
25 25 @NotNull(message = "支付退款编号不能为空")
26 26 private Long payRefundId;
27 27  
  28 + @ApiModelProperty(value = "退款状态(成功,失败)", required = true, example = "10")
  29 + private Integer status;
  30 +
28 31 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java
... ... @@ -2,9 +2,7 @@ package cn.iocoder.yudao.coreservice.modules.pay.service.order;
2 2  
3 3 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
4 4  
5   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO;
6   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO;
7   -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.*;
8 6 import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
9 7  
10 8 import javax.validation.Valid;
... ... @@ -50,4 +48,6 @@ public interface PayOrderCoreService {
50 48 */
51 49 void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception;
52 50  
  51 +
  52 +
53 53 }
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  7 +
  8 +/**
  9 + * 支付退款订单渠道返回后 , 后置处理抽象类, 处理公用的逻辑
  10 + * @author jason
  11 + */
  12 +public abstract class PayRefundAbstractChannelPostHandler implements PayRefundChannelPostHandler {
  13 +
  14 + private final PayOrderCoreMapper payOrderCoreMapper;
  15 + private final PayRefundMapper payRefundMapper;
  16 +
  17 + public PayRefundAbstractChannelPostHandler(PayOrderCoreMapper payOrderCoreMapper,
  18 + PayRefundMapper payRefundMapper){
  19 + this.payOrderCoreMapper = payOrderCoreMapper;
  20 + this.payRefundMapper = payRefundMapper;
  21 + }
  22 +
  23 +
  24 + /**
  25 + * 更新退款单
  26 + * @param refundDO 需要更新的退款单信息
  27 + */
  28 + protected void updatePayRefund(PayRefundDO refundDO){
  29 + payRefundMapper.updateById(refundDO);
  30 + }
  31 +
  32 +
  33 + /**
  34 + * 更新原始支付订单
  35 + * @param payOrderDO 支付订单信息
  36 + */
  37 + protected void updatePayOrder(PayOrderDO payOrderDO){
  38 + payOrderCoreMapper.updateById(payOrderDO);
  39 + }
  40 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  4 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  5 +
  6 +/**
  7 + * 支付退款订单 ,渠道返回后 后置处理
  8 + * @author jason
  9 + */
  10 +public interface PayRefundChannelPostHandler {
  11 +
  12 + /**
  13 + * 支持的渠道返回值
  14 + * @return 支持的渠道返回值数组
  15 + */
  16 + PayChannelRespEnum[] supportHandleResp();
  17 +
  18 +
  19 + /**
  20 + * 根据渠道返回, 处理支付退款单
  21 + * @param respBO
  22 + */
  23 + void handleRefundChannelResp(PayRefundPostReqBO respBO);
  24 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO;
  5 +
  6 +/**
  7 + * 退款单 Core Service
  8 + *
  9 + * @author jason
  10 + */
  11 +public interface PayRefundCoreService {
  12 +
  13 +
  14 + /**
  15 + * 提交退款申请
  16 + * @param reqDTO 退款申请信息
  17 + * @return 退款申请返回信息
  18 + */
  19 + PayRefundRespBO refund(PayRefundReqBO reqDTO);
  20 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundPostReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.bo;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
  4 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Builder;
  7 +import lombok.Data;
  8 +import lombok.NoArgsConstructor;
  9 +import lombok.experimental.Accessors;
  10 +
  11 +@Data
  12 +@Accessors(chain = true)
  13 +@Builder
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class PayRefundPostReqBO {
  17 +
  18 +
  19 + /**
  20 + * 渠道的通用返回结果
  21 + */
  22 + private PayChannelRespEnum respEnum;
  23 +
  24 +
  25 +
  26 + private PayRefundTypeEnum refundTypeEnum;
  27 +
  28 + /**
  29 + * 已退款的总金额
  30 + */
  31 + private Long refundedAmount;
  32 +
  33 + /**
  34 + * 本次退款金额
  35 + */
  36 + private Long refundAmount;
  37 +
  38 + /**
  39 + * 已退款次数
  40 + */
  41 + private Integer refundedTimes;
  42 +
  43 +
  44 + /**
  45 + * 订单编号
  46 + */
  47 + private Long orderId;
  48 +
  49 + /**
  50 + * 退款单编号
  51 + */
  52 + private Long refundId;
  53 +
  54 +
  55 + /**
  56 + * 渠道退款单号
  57 + */
  58 + private String channelRefundNo;
  59 +
  60 +
  61 + /**
  62 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
  63 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
  64 + * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
  65 + */
  66 + private String payTradeNo;
  67 +
  68 +
  69 + /**
  70 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
  71 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
  72 + * 退款请求单号 同一退款请求单号多次请求只退一笔。
  73 + */
  74 + private String refundReqNo;
  75 +
  76 +
  77 +
  78 + /**
  79 + * 调用异常错误信息
  80 + */
  81 + private String exceptionMsg;
  82 +
  83 +
  84 + /**
  85 + * 渠道的错误码
  86 + */
  87 + private String channelErrCode;
  88 +
  89 +
  90 + /**
  91 + * 渠道的错误描述
  92 + */
  93 + private String channelErrMsg;
  94 +
  95 +
  96 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.bo;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +import lombok.experimental.Accessors;
  8 +/**
  9 + * 退款申请单 Request DTO
  10 + */
  11 +@Data
  12 +@Accessors(chain = true)
  13 +@Builder
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class PayRefundReqBO {
  17 +
  18 + /**
  19 + * 支付订单编号自增
  20 + */
  21 + private Long payOrderId;
  22 +
  23 + /**
  24 + * 退款金额
  25 + */
  26 + private Long amount;
  27 +
  28 + /**
  29 + * 退款原因
  30 + */
  31 + private String reason;
  32 +
  33 +
  34 + /**
  35 + * 商户退款订单号
  36 + */
  37 + private String merchantRefundNo;
  38 +
  39 + /**
  40 + * 用户 IP
  41 + */
  42 + private String userIp;
  43 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundRespBO.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.bo;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +import lombok.experimental.Accessors;
  8 +
  9 +/**
  10 + * 退款申请单 Response DTO
  11 + */
  12 +@Data
  13 +@Accessors(chain = true)
  14 +@Builder
  15 +@NoArgsConstructor
  16 +@AllArgsConstructor
  17 +public class PayRefundRespBO {
  18 +
  19 + /**
  20 + * 支付退款单编号, 自增
  21 + */
  22 + private Long refundId;
  23 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelFailedHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
  9 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
  10 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
  11 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
  12 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  13 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  14 +import org.springframework.stereotype.Service;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.util.Optional;
  18 +
  19 +/**
  20 + * 支付退款订单渠道返回失败的后置处理类
  21 + * {@link PayChannelRespEnum#CALL_EXCEPTION}
  22 + * {@link PayChannelRespEnum#CAN_NOT_RETRY_FAILURE}
  23 + */
  24 +@Service
  25 +public class PayRefundChannelFailedHandler extends PayRefundAbstractChannelPostHandler {
  26 +
  27 + @Resource
  28 + private PayNotifyCoreService payNotifyCoreService;
  29 +
  30 + public PayRefundChannelFailedHandler(PayOrderCoreMapper payOrderCoreMapper, PayRefundMapper payRefundMapper) {
  31 + super(payOrderCoreMapper, payRefundMapper);
  32 + }
  33 +
  34 + @Override
  35 + public PayChannelRespEnum[] supportHandleResp() {
  36 + return new PayChannelRespEnum[] {PayChannelRespEnum.CALL_EXCEPTION, PayChannelRespEnum.CAN_NOT_RETRY_FAILURE};
  37 + }
  38 +
  39 +
  40 + @Override
  41 + public void handleRefundChannelResp(PayRefundPostReqBO respBO) {
  42 + //退款失败
  43 + //更新退款单表
  44 + PayRefundDO updateRefundDO = new PayRefundDO();
  45 +
  46 + updateRefundDO.setId(respBO.getRefundId())
  47 + .setStatus(PayRefundStatusEnum.FAILURE.getStatus())
  48 + .setChannelErrorCode(respBO.getChannelErrCode())
  49 + .setChannelErrorMsg(Optional.ofNullable(respBO.getChannelErrMsg())
  50 + .orElse(respBO.getExceptionMsg()));
  51 + updatePayRefund(updateRefundDO);
  52 + PayOrderDO updateOrderDO = new PayOrderDO();
  53 + //更新订单表
  54 + updateOrderDO.setId(respBO.getOrderId())
  55 + .setRefundTimes(respBO.getRefundedTimes() + 1);
  56 + updatePayOrder(updateOrderDO);
  57 + // 立刻插入退款通知记录
  58 + // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
  59 + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
  60 + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(respBO.getRefundId()).build());
  61 + }
  62 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelNotifyHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
  9 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  10 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +/**
  14 + * 支付退款订单渠道返回通知 {@link PayChannelRespEnum#PROCESSING_NOTIFY},后置处理类
  15 + * 支付宝退款单好像没有回调, 微信会触发回调
  16 + */
  17 +@Service
  18 +public class PayRefundChannelNotifyHandler extends PayRefundAbstractChannelPostHandler {
  19 +
  20 + public PayRefundChannelNotifyHandler(PayOrderCoreMapper payOrderCoreMapper,
  21 + PayRefundMapper payRefundMapper) {
  22 + super(payOrderCoreMapper, payRefundMapper);
  23 + }
  24 +
  25 + @Override
  26 + public PayChannelRespEnum[] supportHandleResp() {
  27 + return new PayChannelRespEnum[] {PayChannelRespEnum.PROCESSING_NOTIFY};
  28 + }
  29 +
  30 + @Override
  31 + public void handleRefundChannelResp(PayRefundPostReqBO respBO) {
  32 + PayRefundDO updateRefundDO = new PayRefundDO();
  33 + //更新退款单表
  34 + updateRefundDO.setId(respBO.getRefundId())
  35 + .setStatus(PayRefundStatusEnum.PROCESSING_NOTIFY.getStatus());
  36 + updatePayRefund(updateRefundDO);
  37 +
  38 + PayOrderDO updateOrderDO = new PayOrderDO();
  39 + //更新订单表
  40 + updateOrderDO.setId(respBO.getOrderId())
  41 + .setRefundTimes(respBO.getRefundedTimes() + 1);
  42 + updatePayOrder(updateOrderDO);
  43 +
  44 + }
  45 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
  9 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  10 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +import java.util.Objects;
  14 +
  15 +/**
  16 + * 支付退款订单渠道返回需调用查询接口的后置处理类
  17 + * {@link PayChannelRespEnum#PROCESSING_QUERY} //TODO 芋道源码 是不是微信有这样的情况
  18 + * {@link PayChannelRespEnum#READ_TIME_OUT_EXCEPTION}
  19 + */
  20 +@Service
  21 +public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHandler {
  22 +
  23 +
  24 + public PayRefundChannelQueryHandler(PayOrderCoreMapper payOrderCoreMapper,
  25 + PayRefundMapper payRefundMapper) {
  26 + super(payOrderCoreMapper, payRefundMapper);
  27 + }
  28 +
  29 + @Override
  30 + public PayChannelRespEnum[] supportHandleResp() {
  31 + return new PayChannelRespEnum[]{PayChannelRespEnum.PROCESSING_QUERY, PayChannelRespEnum.READ_TIME_OUT_EXCEPTION};
  32 + }
  33 +
  34 + @Override
  35 + public void handleRefundChannelResp(PayRefundPostReqBO respBO) {
  36 + final PayChannelRespEnum respEnum = respBO.getRespEnum();
  37 + PayRefundStatusEnum refundStatus =
  38 + Objects.equals(PayChannelRespEnum.PROCESSING_QUERY, respEnum) ? PayRefundStatusEnum.PROCESSING_QUERY
  39 + : PayRefundStatusEnum.UNKNOWN_QUERY;
  40 + //更新退款单表
  41 + PayRefundDO updateRefundDO = new PayRefundDO();
  42 + updateRefundDO.setId(respBO.getRefundId())
  43 + .setStatus(refundStatus.getStatus());
  44 + updatePayRefund(updateRefundDO);
  45 +
  46 + PayOrderDO updateOrderDO = new PayOrderDO();
  47 + //更新订单表
  48 + updateOrderDO.setId(respBO.getOrderId())
  49 + .setRefundTimes(respBO.getRefundedTimes() + 1);
  50 + updatePayOrder(updateOrderDO);
  51 +
  52 + //TODO 发起查询任务
  53 + }
  54 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelRetryHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
  9 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  10 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +import java.util.Optional;
  14 +
  15 +/**
  16 + * 支付退款订单渠道返回重试的后置处理类
  17 + * {@link PayChannelRespEnum#RETRY_FAILURE}
  18 + */
  19 +@Service
  20 +public class PayRefundChannelRetryHandler extends PayRefundAbstractChannelPostHandler {
  21 +
  22 +
  23 + public PayRefundChannelRetryHandler(PayOrderCoreMapper payOrderCoreMapper,
  24 + PayRefundMapper payRefundMapper) {
  25 + super(payOrderCoreMapper, payRefundMapper);
  26 + }
  27 +
  28 + @Override
  29 + public PayChannelRespEnum[] supportHandleResp() {
  30 + return new PayChannelRespEnum[] {PayChannelRespEnum.RETRY_FAILURE};
  31 + }
  32 +
  33 + @Override
  34 + public void handleRefundChannelResp(PayRefundPostReqBO respBO) {
  35 +
  36 + PayRefundDO updateRefundDO = new PayRefundDO();
  37 + //更新退款单表
  38 + updateRefundDO.setId(respBO.getRefundId())
  39 + .setStatus(PayRefundStatusEnum.UNKNOWN_RETRY.getStatus())
  40 + .setChannelErrorCode(respBO.getChannelErrCode())
  41 + .setChannelErrorMsg(respBO.getChannelErrMsg());
  42 + updatePayRefund(updateRefundDO);
  43 +
  44 + PayOrderDO updateOrderDO = new PayOrderDO();
  45 + //更新订单表
  46 + updateOrderDO.setId(respBO.getOrderId())
  47 + .setRefundTimes(respBO.getRefundedTimes() + 1);
  48 + updatePayOrder(updateOrderDO);
  49 +
  50 + //TODO 发起重试任务
  51 + }
  52 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelSuccessHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
  9 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService;
  10 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
  11 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler;
  12 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  13 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  14 +import org.springframework.stereotype.Service;
  15 +
  16 +import javax.annotation.Resource;
  17 +import java.util.Date;
  18 +/**
  19 + * 支付退款订单渠道返回退款成功的后置处理类
  20 + * {@link PayChannelRespEnum#SYNC_SUCCESS}
  21 + */
  22 +@Service
  23 +public class PayRefundChannelSuccessHandler extends PayRefundAbstractChannelPostHandler {
  24 +
  25 +
  26 + @Resource
  27 + private PayNotifyCoreService payNotifyCoreService;
  28 +
  29 +
  30 + public PayRefundChannelSuccessHandler(PayOrderCoreMapper payOrderCoreMapper,
  31 + PayRefundMapper payRefundMapper) {
  32 + super(payOrderCoreMapper, payRefundMapper);
  33 + }
  34 +
  35 + @Override
  36 + public PayChannelRespEnum[] supportHandleResp() {
  37 + return new PayChannelRespEnum[]{PayChannelRespEnum.SYNC_SUCCESS};
  38 + }
  39 +
  40 + @Override
  41 + public void handleRefundChannelResp(PayRefundPostReqBO respBO) {
  42 + //退款成功
  43 + PayRefundDO updateRefundDO = new PayRefundDO();
  44 + //更新退款单表
  45 + updateRefundDO.setId(respBO.getRefundId())
  46 + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus())
  47 + .setChannelRefundNo(respBO.getChannelRefundNo())
  48 + .setSuccessTime(new Date());
  49 + updatePayRefund(updateRefundDO);
  50 +
  51 + PayOrderDO updateOrderDO = new PayOrderDO();
  52 + //更新订单表
  53 + updateOrderDO.setId(respBO.getOrderId())
  54 + .setRefundTimes(respBO.getRefundedTimes() + 1)
  55 + .setRefundStatus(respBO.getRefundTypeEnum().getStatus())
  56 + .setRefundAmount(respBO.getRefundedAmount()+respBO.getRefundAmount());
  57 + updatePayOrder(updateOrderDO);
  58 +
  59 + // 立刻插入退款通知记录
  60 + // TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
  61 + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
  62 + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(respBO.getRefundId()).build());
  63 + }
  64 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayRefundCoreConvert;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
  6 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
  8 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
  9 +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
  10 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper;
  11 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper;
  12 +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper;
  13 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
  14 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
  15 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
  16 +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum;
  17 +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
  18 +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
  19 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
  20 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundChannelPostHandler;
  21 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO;
  22 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO;
  23 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO;
  24 +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
  25 +import cn.iocoder.yudao.framework.pay.core.client.PayClient;
  26 +import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
  27 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
  28 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
  29 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  30 +import lombok.extern.slf4j.Slf4j;
  31 +import org.springframework.stereotype.Service;
  32 +import org.springframework.transaction.annotation.Transactional;
  33 +
  34 +import javax.annotation.PostConstruct;
  35 +import javax.annotation.Resource;
  36 +import java.util.*;
  37 +
  38 +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
  39 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  40 +
  41 +@Service
  42 +@Slf4j
  43 +public class PayRefundCoreServiceImpl implements PayRefundCoreService {
  44 +
  45 + @Resource
  46 + private PayOrderCoreMapper payOrderCoreMapper;
  47 +
  48 + @Resource
  49 + private PayRefundMapper payRefundMapper;
  50 +
  51 + @Resource
  52 + private PayOrderExtensionCoreMapper payOrderExtensionCoreMapper;
  53 +
  54 + @Resource
  55 + private PayAppCoreService payAppCoreService;
  56 +
  57 + @Resource
  58 + private PayChannelCoreService payChannelCoreService;
  59 +
  60 + @Resource
  61 + private PayClientFactory payClientFactory;
  62 +
  63 + /**
  64 + * 处理渠道返回结果的后置处理器 集合
  65 + */
  66 + @Resource
  67 + private List<PayRefundChannelPostHandler> handlerList;
  68 +
  69 +
  70 + private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> mapHandler = new EnumMap<>(PayChannelRespEnum.class);
  71 +
  72 +
  73 +
  74 + @PostConstruct
  75 + public void init(){
  76 +
  77 + if (Objects.nonNull(handlerList)) {
  78 + handlerList.forEach(t->{
  79 + for (PayChannelRespEnum item : t.supportHandleResp()) {
  80 + mapHandler.put(item, t);
  81 + }
  82 + });
  83 + }
  84 +
  85 + }
  86 +
  87 +
  88 + @Override
  89 + @Transactional(rollbackFor = Exception.class)
  90 + public PayRefundRespBO refund(PayRefundReqBO reqBO) {
  91 + // 获得 PayOrderDO
  92 + PayOrderDO order = payOrderCoreMapper.selectById(reqBO.getPayOrderId());
  93 + // 校验订单是否存在
  94 + if (Objects.isNull(order) ) {
  95 + throw exception(PAY_ORDER_NOT_FOUND);
  96 + }
  97 + // 校验 App
  98 + PayAppDO app = payAppCoreService.validPayApp(order.getAppId());
  99 + // 校验支付渠道是否有效
  100 + PayChannelDO channel = payChannelCoreService.validPayChannel(order.getChannelId());
  101 + // 校验支付客户端是否正确初始化
  102 + PayClient client = payClientFactory.getPayClient(channel.getId());
  103 + if (client == null) {
  104 + log.error("[refund][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
  105 + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
  106 + }
  107 +
  108 + //校验退款的条件
  109 + validatePayRefund(reqBO, order);
  110 +
  111 + //退款类型
  112 + PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
  113 + if (Objects.equals(reqBO.getAmount(), order.getAmount())) {
  114 + refundType = PayRefundTypeEnum.ALL;
  115 + }
  116 +
  117 + //退款单入库 退款单状态:生成, 没有和渠道产生交互
  118 + PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
  119 + PayRefundDO refundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
  120 + .appId(order.getAppId())
  121 + .channelOrderNo(order.getChannelOrderNo())
  122 + .channelCode(order.getChannelCode())
  123 + .channelId(order.getChannelId())
  124 + .merchantId(order.getMerchantId())
  125 + .orderId(order.getId())
  126 + .merchantRefundNo(reqBO.getMerchantRefundNo())
  127 + .notifyUrl(app.getRefundNotifyUrl())
  128 + .payAmount(order.getAmount())
  129 + .refundAmount(reqBO.getAmount())
  130 + .userIp(reqBO.getUserIp())
  131 + .merchantOrderId(order.getMerchantOrderId())
  132 + .tradeNo(orderExtensionDO.getNo())
  133 + .status(PayRefundStatusEnum.CREATE.getStatus())
  134 + .reason(reqBO.getReason())
  135 + .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
  136 + .reqNo(PaySeqUtils.genRefundReqNo())
  137 + .type(refundType.getStatus())
  138 + .build();
  139 +
  140 + payRefundMapper.insert(refundDO);
  141 +
  142 + PayRefundUnifiedReqDTO unifiedReqDTO = PayRefundUnifiedReqDTO.builder()
  143 + .userIp(reqBO.getUserIp())
  144 + .channelOrderNo(refundDO.getChannelOrderNo())
  145 + .payTradeNo(refundDO.getTradeNo())
  146 + .refundReqNo(refundDO.getReqNo())
  147 + .amount(reqBO.getAmount())
  148 + .reason(refundDO.getReason())
  149 + .build();
  150 +
  151 + //调用渠道进行退款
  152 + PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO);
  153 +
  154 + //根据渠道返回,获取退款后置处理,由postHandler 进行处理
  155 + PayRefundChannelPostHandler payRefundChannelPostHandler = mapHandler.get(refundUnifiedRespDTO.getRespEnum());
  156 +
  157 + if(Objects.isNull(payRefundChannelPostHandler)){
  158 + throw exception(PAY_REFUND_POST_HANDLER_NOT_FOUND);
  159 + }
  160 +
  161 + PayRefundPostReqBO bo = PayRefundCoreConvert.INSTANCE.convert(refundUnifiedRespDTO);
  162 + bo.setRefundAmount(reqBO.getAmount())
  163 + .setRefundedAmount(order.getRefundAmount())
  164 + .setRefundedTimes(order.getRefundTimes())
  165 + .setRefundId(refundDO.getId())
  166 + .setOrderId(order.getId())
  167 + .setRefundTypeEnum(refundType);
  168 +
  169 + //调用退款的后置处理
  170 + payRefundChannelPostHandler.handleRefundChannelResp(bo);
  171 +
  172 + return PayRefundRespBO.builder().refundId(refundDO.getId()).build();
  173 + }
  174 +
  175 +
  176 + /**
  177 + * 校验是否进行退款
  178 + * @param reqBO 退款申请信息
  179 + * @param order 原始支付订单信息
  180 + */
  181 + private void validatePayRefund(PayRefundReqBO reqBO, PayOrderDO order) {
  182 +
  183 + // 校验状态,必须是支付状态
  184 + if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
  185 + throw exception(PAY_ORDER_STATUS_IS_NOT_SUCCESS);
  186 + }
  187 + //是否已经全额退款
  188 + if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) {
  189 + throw exception(PAY_REFUND_ALL_REFUNDED);
  190 + }
  191 + // 校验金额 退款金额不能大于 原定的金额
  192 + if(reqBO.getAmount() + order.getRefundAmount() > order.getAmount()){
  193 + throw exception(PAY_REFUND_AMOUNT_EXCEED);
  194 + }
  195 + //校验渠道订单号
  196 + if (StrUtil.isEmpty(order.getChannelOrderNo())) {
  197 + throw exception(PAY_REFUND_CHN_ORDER_NO_IS_NULL);
  198 + }
  199 + //TODO 退款的期限 退款次数的控制
  200 +
  201 +
  202 + }
  203 +}
... ...
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.coreservice.modules.pay.util;
  2 +
  3 +import cn.hutool.core.date.DatePattern;
  4 +import cn.hutool.core.date.DateUtil;
  5 +import cn.hutool.core.util.RandomUtil;
  6 +
  7 +import java.util.Date;
  8 +import java.util.concurrent.atomic.AtomicLong;
  9 +
  10 +/**
  11 + * 支付相关编号的生产
  12 + */
  13 +public class PaySeqUtils {
  14 +
  15 + private static final AtomicLong REFUND_REQ_NO_SEQ = new AtomicLong(0L);
  16 +
  17 + private static final AtomicLong MER_REFUND_NO_SEQ = new AtomicLong(0L);
  18 +
  19 + private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L);
  20 +
  21 + /**
  22 + * 生成商户退款单号,用于测试,应该由商户系统生成
  23 + * @return 商户退款单
  24 + */
  25 + public static String genMerchantRefundNo() {
  26 + return String.format("%s%s%04d", "MR",
  27 + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN),
  28 + (int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000);
  29 + }
  30 +
  31 + /**
  32 + * 生成退款请求号
  33 + * @return 退款请求号
  34 + */
  35 + public static String genRefundReqNo() {
  36 + return String.format("%s%s%04d", "RR",
  37 + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN),
  38 + (int) REFUND_REQ_NO_SEQ.getAndIncrement() % 10000);
  39 + }
  40 +
  41 + /**
  42 + * 生成商户订单编号号 用于测试,应该由商户系统生成
  43 + * @return 商户订单编号
  44 + */
  45 + public static String genMerchantOrderNo() {
  46 + return String.format("%s%s%04d", "MO",
  47 + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN),
  48 + (int) MER_ORDER_NO_SEQ.getAndIncrement() % 10000);
  49 + }
  50 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client;
2 2  
3 3  
4   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
5   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
6   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
  4 +import cn.iocoder.yudao.framework.pay.core.client.dto.*;
7 5  
8 6 /**
9 7 * 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能
... ... @@ -36,4 +34,12 @@ public interface PayClient {
36 34 */
37 35 PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception;
38 36  
  37 +
  38 + /**
  39 + * 调用支付渠道,进行退款
  40 + * @param reqDTO 统一退款请求信息
  41 + * @return 各支付渠道的统一返回结果
  42 + */
  43 + PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
  44 +
39 45 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java
... ... @@ -6,7 +6,10 @@ import lombok.ToString;
6 6  
7 7 import java.util.Map;
8 8  
9   -// TODO @jason:注释要写下哈。字段不要使用 // 注释,非标准
  9 +
  10 +/**
  11 + * 支付订单回调,渠道的统一通知请求数据
  12 + */
10 13 @Data
11 14 @ToString
12 15 @Builder
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.dto;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +import lombok.experimental.Accessors;
  8 +import org.hibernate.validator.constraints.URL;
  9 +
  10 +import javax.validation.constraints.DecimalMin;
  11 +import javax.validation.constraints.NotEmpty;
  12 +import javax.validation.constraints.NotNull;
  13 +
  14 +/**
  15 + * 统一 退款 Request DTO
  16 + *
  17 + * @author jason
  18 + */
  19 +@Accessors(chain = true)
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +@Data
  24 +public class PayRefundUnifiedReqDTO {
  25 +
  26 + /**
  27 + * 用户 IP
  28 + */
  29 + private String userIp;
  30 +
  31 + /**
  32 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 transaction_id
  33 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 trade_no
  34 + * 渠道订单号
  35 + */
  36 + private String channelOrderNo;
  37 +
  38 +
  39 + /**
  40 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
  41 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
  42 + * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
  43 + */
  44 + private String payTradeNo;
  45 +
  46 +
  47 + /**
  48 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
  49 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
  50 + * 退款请求单号 同一退款请求单号多次请求只退一笔。
  51 + */
  52 + @NotEmpty(message = "退款请求单号")
  53 + private String refundReqNo;
  54 +
  55 +
  56 + /**
  57 + * 退款原因
  58 + */
  59 + @NotEmpty(message = "退款原因不能为空")
  60 + private String reason;
  61 +
  62 +
  63 + /**
  64 + * 退款金额,单位:分
  65 + */
  66 + @NotNull(message = "退款金额不能为空")
  67 + @DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
  68 + private Long amount;
  69 +
  70 +
  71 +
  72 +
  73 + /**
  74 + * 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要
  75 + */
  76 + @URL(message = "支付结果的 notify 回调地址必须是 URL 格式")
  77 + private String notifyUrl;
  78 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.client.dto;
  2 +
  3 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +import lombok.experimental.Accessors;
  9 +
  10 +import javax.validation.constraints.NotEmpty;
  11 +/**
  12 + * 统一 退款 Response DTO
  13 + *
  14 + * @author jason
  15 + */
  16 +@Accessors(chain = true)
  17 +@Builder
  18 +@NoArgsConstructor
  19 +@AllArgsConstructor
  20 +@Data
  21 +public class PayRefundUnifiedRespDTO {
  22 +
  23 +
  24 +
  25 + /**
  26 + * 渠道的通用返回结果
  27 + */
  28 + private PayChannelRespEnum respEnum;
  29 +
  30 +
  31 +
  32 + /**
  33 + * 渠道退款单号
  34 + */
  35 + private String channelRefundNo;
  36 +
  37 +
  38 + /**
  39 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no
  40 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
  41 + * 支付交易号 {PayOrderExtensionDO no字段} 和 渠道订单号 不能同时为空
  42 + */
  43 + private String payTradeNo;
  44 +
  45 +
  46 + /**
  47 + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
  48 + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
  49 + * 退款请求单号 同一退款请求单号多次请求只退一笔。
  50 + */
  51 + private String refundReqNo;
  52 +
  53 +
  54 +
  55 + /**
  56 + * 调用异常错误信息
  57 + */
  58 + private String exceptionMsg;
  59 +
  60 +
  61 + /**
  62 + * 渠道的错误码
  63 + */
  64 + private String channelErrCode;
  65 +
  66 +
  67 + /**
  68 + * 渠道的错误描述
  69 + */
  70 + private String channelErrMsg;
  71 +
  72 + //TODO 退款资金渠 ???
  73 +}
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
... ... @@ -6,8 +6,13 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClient;
6 6 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
7 7 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
8 8 import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
  9 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
  10 +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
  11 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
9 12 import lombok.extern.slf4j.Slf4j;
10 13  
  14 +import java.net.SocketTimeoutException;
  15 +
11 16 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
12 17  
13 18 /**
... ... @@ -22,6 +27,7 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
22 27 * 渠道编号
23 28 */
24 29 private final Long channelId;
  30 +
25 31 /**
26 32 * 渠道编码
27 33 */
... ... @@ -91,7 +97,37 @@ public abstract class AbstractPayClient&lt;Config extends PayClientConfig&gt; implemen
91 97 return result;
92 98 }
93 99  
  100 +
  101 +
94 102 protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
95 103 throws Throwable;
96 104  
  105 +
  106 + @Override
  107 + public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  108 +
  109 + PayRefundUnifiedRespDTO resp;
  110 + try {
  111 + resp = doUnifiedRefund(reqDTO);
  112 + }catch (SocketTimeoutException ex){
  113 + //网络 read time out 异常
  114 + log.error("[unifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), ex);
  115 + return PayRefundUnifiedRespDTO.builder()
  116 + .exceptionMsg(ex.getMessage())
  117 + .respEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION)
  118 + .build();
  119 + } catch (Throwable ex) {
  120 + // 打印异常日志
  121 + log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
  122 + return PayRefundUnifiedRespDTO.builder()
  123 + .exceptionMsg(ex.getMessage())
  124 + .respEnum(PayChannelRespEnum.CALL_EXCEPTION)
  125 + .build();
  126 + }
  127 + return resp;
  128 + }
  129 +
  130 +
  131 + protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
  132 +
97 133 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
... ... @@ -3,9 +3,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
3 3 import cn.hutool.core.bean.BeanUtil;
4 4 import cn.hutool.core.date.DateUtil;
5 5 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
6   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
7   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
8   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
  6 +import cn.iocoder.yudao.framework.pay.core.client.dto.*;
9 7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
10 8 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
11 9 import com.alipay.api.AlipayApiException;
... ... @@ -71,6 +69,8 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
71 69 return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
72 70 }
73 71  
  72 +
  73 +
74 74 @Override
75 75 public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
76 76 //结果转换
... ... @@ -82,4 +82,10 @@ public class AlipayQrPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
82 82 .data(data.getBody()).build();
83 83  
84 84 }
  85 +
  86 + @Override
  87 + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
  88 + //TODO 需要实现
  89 + throw new UnsupportedOperationException();
  90 + }
85 91 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
... ... @@ -3,28 +3,35 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
3 3 import cn.hutool.core.bean.BeanUtil;
4 4 import cn.hutool.core.date.DateUtil;
5 5 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
6   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
7   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
8   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
  6 +import cn.iocoder.yudao.framework.pay.core.client.dto.*;
9 7 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
10 8 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
  9 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
11 10 import com.alipay.api.AlipayApiException;
12 11 import com.alipay.api.AlipayConfig;
13 12 import com.alipay.api.DefaultAlipayClient;
  13 +import com.alipay.api.domain.AlipayTradeRefundModel;
14 14 import com.alipay.api.domain.AlipayTradeWapPayModel;
  15 +import com.alipay.api.request.AlipayTradeRefundRequest;
15 16 import com.alipay.api.request.AlipayTradeWapPayRequest;
  17 +import com.alipay.api.response.AlipayTradeRefundResponse;
16 18 import com.alipay.api.response.AlipayTradeWapPayResponse;
17 19 import lombok.SneakyThrows;
  20 +import lombok.extern.slf4j.Slf4j;
18 21  
  22 +import java.net.SocketTimeoutException;
19 23 import java.util.Map;
20 24 import java.util.Objects;
21 25  
  26 +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
  27 +
22 28 /**
23 29 * 支付宝【手机网站】的 PayClient 实现类
24 30 * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
25 31 *
26 32 * @author 芋道源码
27 33 */
  34 +@Slf4j
28 35 public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> {
29 36  
30 37 private DefaultAlipayClient client;
... ... @@ -96,4 +103,62 @@ public class AlipayWapPayClient extends AbstractPayClient&lt;AlipayPayClientConfig&gt;
96 103 .data(data.getBody()).build();
97 104 }
98 105  
  106 +
  107 + @Override
  108 + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
  109 + AlipayTradeRefundModel model=new AlipayTradeRefundModel();
  110 + model.setTradeNo(reqDTO.getChannelOrderNo());
  111 + model.setOutTradeNo(reqDTO.getPayTradeNo());
  112 + model.setOutRequestNo(reqDTO.getRefundReqNo());
  113 + model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
  114 + model.setRefundReason(reqDTO.getReason());
  115 + AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
  116 + refundRequest.setBizModel(model);
  117 + PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
  118 + try {
  119 + AlipayTradeRefundResponse response = client.execute(refundRequest);
  120 + log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
  121 + if (response.isSuccess()) {
  122 + //退款成功
  123 + //TODO 沙箱环境 返回 的tradeNo(渠道退款单号) 和 订单的tradNo 是一个值,是不是理解不对?
  124 + respDTO.setRespEnum(PayChannelRespEnum.SYNC_SUCCESS)
  125 + .setChannelRefundNo(response.getTradeNo())
  126 + .setPayTradeNo(response.getOutTradeNo());
  127 + }else{
  128 + //特殊处理 sub_code ACQ.SYSTEM_ERROR(系统错误), 需要调用重试任务
  129 + //沙箱环境返回的貌似是”aop.ACQ.SYSTEM_ERROR“, 用contain
  130 + if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) {
  131 + respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE)
  132 + .setChannelErrMsg(response.getSubMsg())
  133 + .setChannelErrCode(response.getSubCode());
  134 + }else{
  135 + //其他当做不可以重试的错误
  136 + respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE)
  137 + .setChannelErrCode(response.getSubCode())
  138 + .setChannelErrMsg(response.getSubMsg());
  139 + }
  140 + }
  141 + return respDTO;
  142 + } catch (AlipayApiException e) {
  143 + //TODO 记录异常日志
  144 + log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
  145 + Throwable cause = e.getCause();
  146 + //网络 read time out 异常, 退款状态未知
  147 + if (cause instanceof SocketTimeoutException) {
  148 + respDTO.setExceptionMsg(e.getMessage())
  149 + .setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION);
  150 + }else{
  151 + respDTO.setExceptionMsg(e.getMessage())
  152 + .setChannelErrCode(e.getErrCode())
  153 + .setChannelErrMsg(e.getErrMsg())
  154 + .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
  155 + }
  156 +
  157 + return respDTO;
  158 +
  159 + }
  160 +
  161 +
  162 + }
  163 +
99 164 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
... ... @@ -8,9 +8,7 @@ import cn.hutool.core.util.StrUtil;
8 8 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
9 9 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
10 10 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
11   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
12   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
13   -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
  11 +import cn.iocoder.yudao.framework.pay.core.client.dto.*;
14 12 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
15 13 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
16 14 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
... ... @@ -92,6 +90,7 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
92 90 return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
93 91 }
94 92  
  93 +
95 94 private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
96 95 // 构建 WxPayUnifiedOrderRequest 对象
97 96 WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
... ... @@ -142,4 +141,11 @@ public class WXPubPayClient extends AbstractPayClient&lt;WXPayClientConfig&gt; {
142 141 .data(data.getBody()).build();
143 142 }
144 143  
  144 +
  145 + @Override
  146 + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
  147 + //TODO 需要实现
  148 + throw new UnsupportedOperationException();
  149 + }
  150 +
145 151 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.enums;
  2 +
  3 +/**
  4 + * 统一的渠道返回结果
  5 + * @author jason
  6 + */
  7 +public enum PayChannelRespEnum {
  8 +
  9 + /**
  10 + * 接口通讯正常返回, 并明确处理成功 ,不需要通过查询或者回调接口 进行下一步处理
  11 + */
  12 + SYNC_SUCCESS,
  13 +
  14 + /**
  15 + * 接口通讯正常返回, 但返回错误,并且不能通过重试解决的错误,
  16 + * 如提交失败, 业务错误(余额不足), 或者参数错误, 签名错误, 需要干预后才能处理
  17 + */
  18 + CAN_NOT_RETRY_FAILURE,
  19 +
  20 +
  21 + /**
  22 + * 接口通讯正常返回,
  23 + * 可以通过重试解决的错误. 如系统超时, 系统繁忙。状态未知 不能改变请求参数,如退款单请求号,重发请求
  24 + */
  25 + RETRY_FAILURE,
  26 +
  27 +
  28 + /**
  29 + * 接口通讯正常返回,但是处理结果 需要渠道回调进行下一步处理
  30 + */
  31 + PROCESSING_NOTIFY,
  32 +
  33 +
  34 + /**
  35 + * 接口通讯正常返回, 但是处理结果,需要调用查询接口 进行查询
  36 + */
  37 + PROCESSING_QUERY,
  38 +
  39 +
  40 + /**
  41 + * 本系统调用渠道接口异常, 渠道接口请求未正常发送, 本系统不可预知的异常,较少发生, 可认为失败。 不用重试.
  42 + */
  43 + CALL_EXCEPTION,
  44 +
  45 +
  46 + /**
  47 + * 本系统调用渠道接口成功, 但是未接受到请求结果,较少发生(需合理设置read time out ) 结果未知。 需要调用查询接口进行查询
  48 + */
  49 + READ_TIME_OUT_EXCEPTION;
  50 +}
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.pay.controller.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO;
  5 +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundReqVO;
  8 +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundRespVO;
  9 +import cn.iocoder.yudao.userserver.modules.pay.convert.order.PayRefundConvert;
  10 +import io.swagger.annotations.Api;
  11 +import io.swagger.annotations.ApiOperation;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.PostMapping;
  15 +import org.springframework.web.bind.annotation.RequestBody;
  16 +import org.springframework.web.bind.annotation.RequestMapping;
  17 +import org.springframework.web.bind.annotation.RestController;
  18 +
  19 +import javax.annotation.Resource;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
  22 +
  23 +@Api(tags = "退款订单")
  24 +@RestController
  25 +@RequestMapping("/pay/order")
  26 +@Validated
  27 +@Slf4j
  28 +public class PayRefundController {
  29 +
  30 + @Resource
  31 + private PayRefundCoreService payRefundCoreService;
  32 +
  33 + @PostMapping("/refund")
  34 + @ApiOperation("提交退款订单")
  35 + public CommonResult<PayRefundRespVO> refund(@RequestBody PayRefundReqVO reqVO){
  36 + PayRefundReqBO reqBO = PayRefundConvert.INSTANCE.convert(reqVO);
  37 + reqBO.setUserIp(getClientIP());
  38 + //TODO 测试暂时模拟生成商户退款订单
  39 + reqBO.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo());
  40 + return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.refund(reqBO)));
  41 + }
  42 +}
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.pay.controller.order.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +
  9 +import javax.validation.constraints.NotEmpty;
  10 +
  11 +@ApiModel("退款订单 Req VO")
  12 +@Data
  13 +@NoArgsConstructor
  14 +@AllArgsConstructor
  15 +public class PayRefundReqVO {
  16 +
  17 + @ApiModelProperty(value = "支付订单编号自增", required = true, example = "10")
  18 + @NotEmpty(message = "支付订单编号自增")
  19 + private Long payOrderId;
  20 +
  21 + @ApiModelProperty(value = "退款金额", required = true, example = "1")
  22 + @NotEmpty(message = "退款金额")
  23 + private Long amount;
  24 +
  25 +
  26 + @ApiModelProperty(value = "退款原因", required = true, example = "不喜欢")
  27 + @NotEmpty(message = "退款原因")
  28 + private String reason;
  29 +
  30 + @ApiModelProperty(value = "商户退款订单号", required = true, example = "MR202111180000000001")
  31 + //TODO 测试暂时模拟生成
  32 + //@NotEmpty(message = "商户退款订单号")
  33 + private String merchantRefundNo;
  34 +
  35 +}
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.pay.controller.order.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Builder;
  6 +import lombok.Data;
  7 +import lombok.NoArgsConstructor;
  8 +import lombok.experimental.Accessors;
  9 +
  10 +@ApiModel("提交退款订单 Response VO")
  11 +@Data
  12 +@Accessors(chain = true)
  13 +@Builder
  14 +@NoArgsConstructor
  15 +@AllArgsConstructor
  16 +public class PayRefundRespVO {
  17 +
  18 + /**
  19 + * 支付退款单编号, 自增
  20 + */
  21 + private Long refundId;
  22 +}
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.userserver.modules.pay.convert.order;
  2 +
  3 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO;
  5 +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundReqVO;
  6 +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundRespVO;
  7 +import org.mapstruct.Mapper;
  8 +import org.mapstruct.factory.Mappers;
  9 +
  10 +/**
  11 + * 支付退款 Convert
  12 + * @author jason
  13 + */
  14 +@Mapper
  15 +public interface PayRefundConvert {
  16 +
  17 + PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class);
  18 +
  19 + PayRefundReqBO convert(PayRefundReqVO reqVO);
  20 +
  21 + PayRefundRespVO convert(PayRefundRespBO respBO);
  22 +}
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java 0 → 100644
  1 +/**
  2 + * 提供 POJO 类的实体转换
  3 + *
  4 + * 目前使用 MapStruct 框架
  5 + */
  6 +package cn.iocoder.yudao.userserver.modules.pay.convert;
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/《芋道 Spring Boot 对象转换 MapStruct 入门》.md 0 → 100644
  1 +<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>
... ...
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java
1 1 package cn.iocoder.yudao.userserver.modules.shop.controller;
2 2  
3 3 import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO;
  4 +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO;
4 5 import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
5 6 import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO;
  7 +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils;
6 8 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 9 import cn.iocoder.yudao.framework.common.util.date.DateUtils;
8 10 import cn.iocoder.yudao.userserver.modules.shop.controller.vo.ShopOrderCreateRespVO;
... ... @@ -43,10 +45,10 @@ public class ShopOrderController {
43 45 PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO();
44 46 reqDTO.setAppId(6L);
45 47 reqDTO.setUserIp(getClientIP());
46   - reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
  48 + reqDTO.setMerchantOrderId(PaySeqUtils.genMerchantOrderNo());
47 49 reqDTO.setSubject("标题:" + shopOrderId);
48 50 reqDTO.setBody("内容:" + shopOrderId);
49   - reqDTO.setAmount(1); // 单位:分
  51 + reqDTO.setAmount(200); // 单位:分
50 52 reqDTO.setExpireTime(DateUtils.addTime(Duration.ofDays(1)));
51 53 Long payOrderId = payOrderCoreService.createPayOrder(reqDTO);
52 54  
... ... @@ -55,6 +57,8 @@ public class ShopOrderController {
55 57 .payOrderId(payOrderId).build());
56 58 }
57 59  
  60 +
  61 +
58 62 @PostMapping("/pay-notify")
59 63 @ApiOperation("支付回调")
60 64 public CommonResult<Boolean> payNotify(@RequestBody @Valid PayNotifyOrderReqVO reqVO) {
... ... @@ -62,4 +66,11 @@ public class ShopOrderController {
62 66 return success(true);
63 67 }
64 68  
  69 + @PostMapping("/refund-notify")
  70 + @ApiOperation("退款回调")
  71 + public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundOrderReqVO reqVO) {
  72 + log.info("[refundNotify][回调成功]");
  73 + return success(true);
  74 + }
  75 +
65 76 }
... ...