From 7d6f205dc0178108c4bf4b5043456620c5d7ac78 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Sun, 21 Nov 2021 11:12:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E4=BB=98=E9=80=80=E6=AC=BE=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=EF=BC=8C=E6=94=AF=E4=BB=98=E5=AE=9D=E6=89=8B=E6=9C=BA?= =?UTF-8?q?wap=20=E7=9B=B8=E5=BA=94=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/pay_refund.sql | 35 +++ sql/ruoyi-vue-pro.sql | 39 +++- .../convert/order/PayRefundCoreConvert.java | 26 +++ .../pay/dal/dataobject/order/PayOrderDO.java | 4 +- .../pay/dal/dataobject/order/PayRefundDO.java | 129 ++++++++--- .../pay/dal/mysql/order/PayRefundMapper.java | 21 ++ .../pay/enums/PayErrorCodeCoreConstants.java | 14 +- .../pay/enums/order/PayRefundStatusEnum.java | 20 ++ ...StatusEnum.java => PayRefundTypeEnum.java} | 2 +- .../notify/impl/PayNotifyCoreServiceImpl.java | 9 +- .../notify/vo/PayRefundOrderReqVO.java | 7 +- .../service/order/PayOrderCoreService.java | 6 +- .../PayRefundAbstractChannelPostHandler.java | 40 ++++ .../order/PayRefundChannelPostHandler.java | 24 +++ .../service/order/PayRefundCoreService.java | 20 ++ .../service/order/bo/PayRefundPostReqBO.java | 96 +++++++++ .../pay/service/order/bo/PayRefundReqBO.java | 43 ++++ .../pay/service/order/bo/PayRefundRespBO.java | 23 ++ .../impl/PayRefundChannelFailedHandler.java | 62 ++++++ .../impl/PayRefundChannelNotifyHandler.java | 45 ++++ .../impl/PayRefundChannelQueryHandler.java | 54 +++++ .../impl/PayRefundChannelRetryHandler.java | 52 +++++ .../impl/PayRefundChannelSuccessHandler.java | 64 ++++++ .../order/impl/PayRefundCoreServiceImpl.java | 203 ++++++++++++++++++ .../modules/pay/util/PaySeqUtils.java | 50 +++++ .../framework/pay/core/client/PayClient.java | 12 +- .../pay/core/client/dto/PayNotifyDataDTO.java | 5 +- .../client/dto/PayRefundUnifiedReqDTO.java | 78 +++++++ .../client/dto/PayRefundUnifiedRespDTO.java | 73 +++++++ .../core/client/impl/AbstractPayClient.java | 36 ++++ .../client/impl/alipay/AlipayQrPayClient.java | 12 +- .../impl/alipay/AlipayWapPayClient.java | 71 +++++- .../core/client/impl/wx/WXPubPayClient.java | 12 +- .../pay/core/enums/PayChannelRespEnum.java | 50 +++++ .../controller/order/PayRefundController.java | 42 ++++ .../controller/order/vo/PayRefundReqVO.java | 35 +++ .../controller/order/vo/PayRefundRespVO.java | 22 ++ .../pay/convert/order/PayRefundConvert.java | 22 ++ .../modules/pay/convert/package-info.java | 6 + ...‹é“ Spring Boot å¯¹è±¡è½¬æ¢ MapStruct 入门》.md | 1 + .../shop/controller/ShopOrderController.java | 15 +- 41 files changed, 1519 insertions(+), 61 deletions(-) create mode 100644 sql/pay_refund.sql create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundMapper.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java rename yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/{PayOrderRefundStatusEnum.java => PayRefundTypeEnum.java} (88%) create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundPostReqBO.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundReqBO.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundRespBO.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelFailedHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelNotifyHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelRetryHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelSuccessHandler.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java create mode 100644 yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java create mode 100644 yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java create mode 100644 yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/ã€ŠèŠ‹é“ Spring Boot å¯¹è±¡è½¬æ¢ MapStruct 入门》.md diff --git a/sql/pay_refund.sql b/sql/pay_refund.sql new file mode 100644 index 000000000..1eb99beab --- /dev/null +++ b/sql/pay_refund.sql @@ -0,0 +1,35 @@ +DROP TABLE IF EXISTS `pay_refund`; +CREATE TABLE `pay_refund` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编å·', + `req_no` varchar(64) NOT NULL COMMENT '退款å•è¯·æ±‚å·', + `merchant_id` bigint NOT NULL COMMENT '商户编å·', + `app_id` bigint NOT NULL COMMENT '应用编å·', + `channel_id` bigint NOT NULL COMMENT '渠é“ç¼–å·', + `channel_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠é“ç¼–ç ', + `order_id` bigint NOT NULL COMMENT '支付订å•ç¼–å· pay_order 表id', + `trade_no` varchar(64) NOT NULL COMMENT '交易订å•å· pay_extension 表no 字段', + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订å•ç¼–å·ï¼ˆå•†æˆ·ç³»ç»Ÿç”Ÿæˆï¼‰', + `merchant_refund_no` varchar(64) NOT NULL COMMENT '商户退款订å•å·ï¼ˆå•†æˆ·ç³»ç»Ÿç”Ÿæˆï¼‰', + `notify_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '异步通知商户地å€', + `notify_status` tinyint NOT NULL COMMENT '通知商户退款结果的回调状æ€', + `status` tinyint NOT NULL COMMENT '退款状æ€', + `type` tinyint NOT NULL COMMENT '退款类型(部分退款,全部退款)', + `pay_amount` bigint NOT NULL COMMENT '支付金é¢,å•ä½åˆ†', + `refund_amount` bigint NOT NULL COMMENT '退款金é¢,å•ä½åˆ†', + `reason` VARCHAR(256) NOT NULL COMMENT '退款原因', + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户 IP', + `channel_order_no` varchar(64) NOT NULL COMMENT '渠é“订å•å·ï¼Œpay_order 中的channel_order_no 对应', + `channel_refund_no` varchar(64) DEFAULT NULL COMMENT '渠é“退款å•å·ï¼Œæ¸ é“返回', + `channel_error_code` varchar(128) DEFAULT NULL COMMENT '渠é“调用报错时,错误ç ', + `channel_error_msg` varchar(256) DEFAULT NULL COMMENT '渠é“调用报错时,错误信æ¯', + `channel_extras` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付渠é“çš„é¢å¤–å‚æ•°', + `expire_time` datetime DEFAULT NULL COMMENT '退款失效时间', + `success_time` datetime DEFAULT NULL COMMENT '退款æˆåŠŸæ—¶é—´', + `notify_time` datetime DEFAULT NULL COMMENT '退款通知时间', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='退款订å•'; diff --git a/sql/ruoyi-vue-pro.sql b/sql/ruoyi-vue-pro.sql index c63073a32..fcb534a44 100644 --- a/sql/ruoyi-vue-pro.sql +++ b/sql/ruoyi-vue-pro.sql @@ -953,7 +953,7 @@ CREATE TABLE `pay_app` ( -- Records of pay_app -- ---------------------------- BEGIN; -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'); +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'); COMMIT; -- ---------------------------- @@ -1240,6 +1240,43 @@ INSERT INTO `pay_order` VALUES (120, 1, 6, 9, 'wx_pub', '1635311949168', '标题 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'); COMMIT; +DROP TABLE IF EXISTS `pay_refund`; +CREATE TABLE `pay_refund` ( + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '支付退款编å·', + `req_no` varchar(64) NOT NULL COMMENT '退款å•è¯·æ±‚å·', + `merchant_id` bigint NOT NULL COMMENT '商户编å·', + `app_id` bigint NOT NULL COMMENT '应用编å·', + `channel_id` bigint NOT NULL COMMENT '渠é“ç¼–å·', + `channel_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '渠é“ç¼–ç ', + `order_id` bigint NOT NULL COMMENT '支付订å•ç¼–å· pay_order 表id', + `trade_no` varchar(64) NOT NULL COMMENT '交易订å•å· pay_extension 表no 字段', + `merchant_order_id` varchar(64) NOT NULL COMMENT '商户订å•ç¼–å·ï¼ˆå•†æˆ·ç³»ç»Ÿç”Ÿæˆï¼‰', + `merchant_refund_no` varchar(64) NOT NULL COMMENT '商户退款订å•å·ï¼ˆå•†æˆ·ç³»ç»Ÿç”Ÿæˆï¼‰', + `notify_url` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '异步通知商户地å€', + `notify_status` tinyint NOT NULL COMMENT '通知商户退款结果的回调状æ€', + `status` tinyint NOT NULL COMMENT '退款状æ€', + `type` tinyint NOT NULL COMMENT '退款类型(部分退款,全部退款)', + `pay_amount` bigint NOT NULL COMMENT '支付金é¢,å•ä½åˆ†', + `refund_amount` bigint NOT NULL COMMENT '退款金é¢,å•ä½åˆ†', + `reason` VARCHAR(256) NOT NULL COMMENT '退款原因', + `user_ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户 IP', + `channel_order_no` varchar(64) NOT NULL COMMENT '渠é“订å•å·ï¼Œpay_order 中的channel_order_no 对应', + `channel_refund_no` varchar(64) DEFAULT NULL COMMENT '渠é“退款å•å·ï¼Œæ¸ é“返回', + `channel_error_code` varchar(128) DEFAULT NULL COMMENT '渠é“调用报错时,错误ç ', + `channel_error_msg` varchar(256) DEFAULT NULL COMMENT '渠é“调用报错时,错误信æ¯', + `channel_extras` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '支付渠é“çš„é¢å¤–å‚æ•°', + `expire_time` datetime DEFAULT NULL COMMENT '退款失效时间', + `success_time` datetime DEFAULT NULL COMMENT '退款æˆåŠŸæ—¶é—´', + `notify_time` datetime DEFAULT NULL COMMENT '退款通知时间', + `creator` varchar(64) DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是å¦åˆ é™¤', + PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='退款订å•'; + + -- ---------------------------- -- Table structure for pay_order_extension -- ---------------------------- diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java new file mode 100644 index 000000000..8f5bb10d3 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/convert/order/PayRefundCoreConvert.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.coreservice.modules.pay.convert.order; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +@Mapper +public interface PayRefundCoreConvert { + + PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class); + + PayRefundPostReqBO convert(PayRefundUnifiedRespDTO respDTO); + + //TODO 太多需è¦å¤„ç†äº†ï¼Œ æš‚æ—¶ä¸ç”¨ + @Mappings(value = { + @Mapping(source = "amount", target = "payAmount"), + @Mapping(source = "id", target = "orderId"), + @Mapping(target = "status",ignore = true) + }) + PayRefundDO convert(PayOrderDO orderDO); +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java index ccaed8a2c..65cf94bf2 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayOrderDO.java +++ b/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 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; -import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; @@ -135,7 +135,7 @@ public class PayOrderDO extends BaseDO { /** * é€€æ¬¾çŠ¶æ€ * - * 枚举 {@link PayOrderRefundStatusEnum} + * 枚举 {@link PayRefundTypeEnum} */ private Integer refundStatus; /** diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java index 77e5a51e7..98e6b55f2 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/dataobject/order/PayRefundDO.java +++ b/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 import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import lombok.Data; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; import java.util.Date; @@ -17,19 +19,34 @@ import java.util.Date; * * @author 芋é“æºç  */ +@TableName("pay_refund") @Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor public class PayRefundDO extends BaseDO { /** * 退款å•ç¼–å·ï¼Œæ•°æ®åº“自增 */ + @TableId private Long id; - /** - * 退款å•å·ï¼Œæ ¹æ®è§„åˆ™ç”Ÿæˆ - * + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no + * 退款请求å·ã€‚ + * 标识一次退款请求,需è¦ä¿è¯åœ¨äº¤æ˜“å·ä¸‹å”¯ä¸€ï¼Œå¦‚需部分退款,则此å‚数必传。 + * 注:针对åŒä¸€æ¬¡é€€æ¬¾è¯·æ±‚,如果调用接å£å¤±è´¥æˆ–异常了,é‡è¯•æ—¶éœ€è¦ä¿è¯é€€æ¬¾è¯·æ±‚å·ä¸èƒ½å˜æ›´ï¼Œ + * 防止该笔交易é‡å¤é€€æ¬¾ã€‚支付å®ä¼šä¿è¯åŒæ ·çš„退款请求å·å¤šæ¬¡è¯·æ±‚åªä¼šé€€ä¸€æ¬¡ã€‚ + * 退款å•è¯·æ±‚å·ï¼Œæ ¹æ®è§„åˆ™ç”Ÿæˆ + * * 例如说,R202109181134287570000 */ - private String no; + private String reqNo; + /** * å•†æˆ·ç¼–å· * @@ -61,23 +78,39 @@ public class PayRefundDO extends BaseDO { */ private Long orderId; + + /** + * 交易订å•å·ï¼Œæ ¹æ®è§„åˆ™ç”Ÿæˆ + * 调用支付渠é“时,使用该字段作为对接的订å•å·ã€‚ + * 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no + * 2. è°ƒç”¨æ”¯ä»˜å® https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no + * 这里对应 pay_extension 里é¢çš„ no + * 例如说,P202110132239124200055 + */ + private String tradeNo; + + // ========== 商户相关字段 ========== /** - * 商户退款订å•å· - * 例如说,内部系统 A 的退款订å•å·ã€‚需è¦ä¿è¯æ¯ä¸ª PayMerchantDO 唯一 TODO 芋艿:需è¦åœ¨æµ‹è¯•ä¸‹ + * 商户订å•ç¼–å· + */ + private String merchantOrderId; + /** + * 商户退款订å•å·, 由商户系统产生, 由他们ä¿è¯å”¯ä¸€ï¼Œä¸èƒ½ä¸ºç©ºï¼Œé€šçŸ¥å•†æˆ·æ—¶ä¼šä¼ è¯¥å­—段。å‘é€channel 使用 reqNo + * 例如说,内部系统 A 的退款订å•å·ã€‚需è¦ä¿è¯æ¯ä¸ª PayMerchantDO 唯一 + * TODO 芋艿:我ç†è§£ 一个商户退款订å•ï¼Œå¯ä»¥å¯¹åº”多æ¡é€€æ¬¾è®°å½•ï¼Œ 因为有å¯èƒ½å¤±è´¥ã€‚但是 é€€æ¬¾è¯·æ±‚å· reqNo 必须唯一 + * */ private String merchantRefundNo; -// /** -// * 商户拓展å‚æ•° -// */ -// private String merchantExtra; + /** * å¼‚æ­¥é€šçŸ¥åœ°å€ */ private String notifyUrl; + /** * é€šçŸ¥å•†æˆ·é€€æ¬¾ç»“æžœçš„å›žè°ƒçŠ¶æ€ - * TODO 芋艿:0 未å‘é€ 1 å·²å‘é€ + * TODO 0 未å‘é€ 1 å·²å‘é€ */ private Integer notifyStatus; @@ -85,44 +118,76 @@ public class PayRefundDO extends BaseDO { /** * é€€æ¬¾çŠ¶æ€ * - * TODO 芋艿:状æ€æžšä¸¾ */ private Integer status; + /** - * 用户 IP + * 退款类型(部分退款,全部退款) */ - private String userIp; + private Integer type; /** - * 退款金é¢ï¼Œå•ä½ï¼šåˆ† + * 支付金é¢,å•ä½åˆ† */ - private Long amount; + private Long payAmount; + /** + * 退款金é¢,å•ä½åˆ† + */ + private Long refundAmount; + /** * 退款原因 */ private String reason; + + /** - * 订å•é€€æ¬¾æˆåŠŸæ—¶é—´ + * 用户 IP */ - private Date successTime; + private String userIp; + + // ========== 渠é“相关字段 ========== /** + * 渠é“订å•å·ï¼Œpay_order 中的channel_order_no 对应 + */ + private String channelOrderNo; + /** + * 渠é“退款å•å·ï¼Œæ¸ é“返回 + */ + private String channelRefundNo; + + /** + * 调用渠é“çš„é”™è¯¯ç  + */ + private String channelErrorCode; + + /** + * 调用渠é“æŠ¥é”™æ—¶ï¼Œé”™è¯¯ä¿¡æ¯ + */ + private String channelErrorMsg; + + + /** + * 支付渠é“çš„é¢å¤–å‚æ•° + * å‚è§ https://www.pingxx.com/api/Refunds%20退款概述.html + */ + private String channelExtras; + + + /** + * TODO * 退款失效时间 */ private Date expireTime; /** - * 支付渠é“çš„é¢å¤–å‚æ•° - * - * å‚è§ https://www.pingxx.com/api/Refunds%20退款概述.html + * 退款æˆåŠŸæ—¶é—´ */ - private String channelExtra; + private Date successTime; + /** + * 退款通知时间 + */ + private Date notifyTime; + + - // ========== 渠é“相关字段 ========== - /** - * 渠é“订å•å· - */ - private String channelOrderNo; - /** - * 渠é“é€€æ¬¾å· - */ - private String channelRefundNo; } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundMapper.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundMapper.java new file mode 100644 index 000000000..471ea33e8 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/dal/mysql/order/PayRefundMapper.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order; + +import java.util.*; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; + +import org.apache.ibatis.annotations.Mapper; + + +/** + * é€€æ¬¾è®¢å• Mapper + * + */ +@Mapper +public interface PayRefundMapper extends BaseMapperX { + + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java index 8d09e6865..f6f4cd48f 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java @@ -28,6 +28,12 @@ public interface PayErrorCodeCoreConstants { ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(1007003001, "支付交易拓展å•ä¸å¤„于待支付"); ErrorCode PAY_ORDER_EXTENSION_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007003002, "支付订å•ä¸å¤„于已支付"); + // ========== 支付模å—(退款) 1-007-006-000 ========== + ErrorCode PAY_REFUND_AMOUNT_EXCEED = new ErrorCode(1007006000, "退款金é¢è¶…过订å•å¯é€€æ¬¾é‡‘é¢"); + ErrorCode PAY_REFUND_ALL_REFUNDED = new ErrorCode(1007006001, "订å•å·²ç»å…¨é¢é€€æ¬¾"); + ErrorCode PAY_REFUND_CHN_ORDER_NO_IS_NULL = new ErrorCode(1007006002, "该订å•çš„渠é“订å•ä¸ºç©º"); + ErrorCode PAY_REFUND_POST_HANDLER_NOT_FOUND = new ErrorCode(1007006002, "未找到对应的退款åŽç½®å¤„ç†ç±»"); + /** * ========== æ”¯ä»˜å•†æˆ·ä¿¡æ¯ 1-007-004-000 ========== */ @@ -41,11 +47,11 @@ public interface PayErrorCodeCoreConstants { /** - * ========== æ”¯ä»˜æ¸ é“ 1-007-006-000 ========== + * ========== æ”¯ä»˜æ¸ é“ 1-007-001-000 ========== */ - ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007006000, "支付渠é“ä¸å­˜åœ¨"); - ErrorCode CHANNEL_KEY_READ_ERROR = new ErrorCode(1007006002, "支付渠é“秘钥文件读å–失败"); + ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠é“ä¸å­˜åœ¨"); + ErrorCode CHANNEL_KEY_READ_ERROR = new ErrorCode(1007001004, "支付渠é“秘钥文件读å–失败"); // TODO @aquan:下é¢è¿™ä¸ªé”™è¯¯ç ï¼Œç¼ºäº† CHANNEL å‰ç¼€ã€‚å¦å¤–,错误ç çš„分段,上é¢æœ‰å•¦ï¼Œåˆå¹¶ä¸‹è¿›åŽ»å“ˆ - ErrorCode EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007006003, "已存在相åŒçš„渠é“"); + ErrorCode EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相åŒçš„渠é“"); } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java new file mode 100644 index 000000000..3b95849ef --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundStatusEnum.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.coreservice.modules.pay.enums.order; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PayRefundStatusEnum { + CREATE(0, "退款订å•ç”Ÿæˆ"), + SUCCESS(1, "退款æˆåŠŸ"), + FAILURE(2, "退款失败"), + PROCESSING_NOTIFY(3,"退款中, 渠é“通知结果"), + PROCESSING_QUERY(4,"退款中, 系统查询结果"), + UNKNOWN_RETRY(5,"状æ€æœªçŸ¥ï¼Œéœ€è¦é‡è¯•"), + UNKNOWN_QUERY(6,"状æ€æœªçŸ¥ï¼Œç³»ç»ŸæŸ¥è¯¢ç»“æžœ"), + CLOSE(99, "退款关闭"); + + private final Integer status; + private final String name; +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java similarity index 88% rename from yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java rename to yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java index 3c283a9d5..ba7ee2fcc 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayOrderRefundStatusEnum.java +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/order/PayRefundTypeEnum.java @@ -11,7 +11,7 @@ import lombok.Getter; */ @Getter @AllArgsConstructor -public enum PayOrderRefundStatusEnum implements IntArrayValuable { +public enum PayRefundTypeEnum implements IntArrayValuable { NO(0, "未退款"), SOME(10, "部分退款"), diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java index 4e7b91547..15857e35b 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/impl/PayNotifyCoreServiceImpl.java +++ b/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; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyLogDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyLogCoreMapper; import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify.PayNotifyTaskCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; import cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify.PayNotifyLockCoreRedisDAO; import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum; import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum; @@ -72,6 +74,9 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { @Resource private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO; + @Resource + private PayRefundMapper payRefundMapper; + @Resource @Lazy // 循环ä¾èµ–(自己ä¾èµ–自己),é¿å…报错 private PayNotifyCoreServiceImpl self; @@ -89,7 +94,9 @@ public class PayNotifyCoreServiceImpl implements PayNotifyCoreService { setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl()); } else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) { // TODO 芋艿,需è¦å®žçŽ°ä¸‹å“ˆ - throw new UnsupportedOperationException("需è¦å®žçŽ°"); + PayRefundDO refundDO = payRefundMapper.selectById(task.getDataId()); + task.setMerchantId(refundDO.getMerchantId()).setAppId(refundDO.getAppId()) + .setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl()); } // 执行æ’å…¥ diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java index 705800892..d436d759b 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/notify/vo/PayRefundOrderReqVO.java +++ b/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; @AllArgsConstructor public class PayRefundOrderReqVO { - @ApiModelProperty(value = "商户订å•ç¼–å·", required = true, example = "10") - @NotEmpty(message = "商户订å•å·ä¸èƒ½ä¸ºç©º") + @ApiModelProperty(value = "商户退款å•ç¼–å·", required = true, example = "10") + @NotEmpty(message = "商户退款å•ç¼–å·ä¸èƒ½ä¸ºç©º") private String merchantOrderId; @ApiModelProperty(value = "支付退款编å·", required = true, example = "20") @NotNull(message = "支付退款编å·ä¸èƒ½ä¸ºç©º") private Long payRefundId; + @ApiModelProperty(value = "退款状æ€(æˆåŠŸï¼Œå¤±è´¥)", required = true, example = "10") + private Integer status; + } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java index c5a98e4b4..3a1e4774e 100644 --- a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java +++ b/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; import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO; -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; -import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.*; import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; import javax.validation.Valid; @@ -50,4 +48,6 @@ public interface PayOrderCoreService { */ void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception; + + } diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java new file mode 100644 index 000000000..db27bcadc --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundAbstractChannelPostHandler.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; + +/** + * 支付退款订å•æ¸ é“è¿”å›žåŽ , åŽç½®å¤„ç†æŠ½è±¡ç±»ï¼Œ 处ç†å…¬ç”¨çš„逻辑 + * @author jason + */ +public abstract class PayRefundAbstractChannelPostHandler implements PayRefundChannelPostHandler { + + private final PayOrderCoreMapper payOrderCoreMapper; + private final PayRefundMapper payRefundMapper; + + public PayRefundAbstractChannelPostHandler(PayOrderCoreMapper payOrderCoreMapper, + PayRefundMapper payRefundMapper){ + this.payOrderCoreMapper = payOrderCoreMapper; + this.payRefundMapper = payRefundMapper; + } + + + /** + * æ›´æ–°é€€æ¬¾å• + * @param refundDO 需è¦æ›´æ–°çš„退款å•ä¿¡æ¯ + */ + protected void updatePayRefund(PayRefundDO refundDO){ + payRefundMapper.updateById(refundDO); + } + + + /** + * æ›´æ–°åŽŸå§‹æ”¯ä»˜è®¢å• + * @param payOrderDO 支付订å•ä¿¡æ¯ + */ + protected void updatePayOrder(PayOrderDO payOrderDO){ + payOrderCoreMapper.updateById(payOrderDO); + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java new file mode 100644 index 000000000..e99d46e24 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundChannelPostHandler.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; + +/** + * æ”¯ä»˜é€€æ¬¾è®¢å• ï¼Œæ¸ é“è¿”å›žåŽ åŽç½®å¤„ç† + * @author jason + */ +public interface PayRefundChannelPostHandler { + + /** + * 支æŒçš„渠é“返回值 + * @return 支æŒçš„渠é“返回值数组 + */ + PayChannelRespEnum[] supportHandleResp(); + + + /** + * æ ¹æ®æ¸ é“返回, 处ç†æ”¯ä»˜é€€æ¬¾å• + * @param respBO + */ + void handleRefundChannelResp(PayRefundPostReqBO respBO); +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java new file mode 100644 index 000000000..f9faf70de --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO; + +/** + * é€€æ¬¾å• Core Service + * + * @author jason + */ +public interface PayRefundCoreService { + + + /** + * æ交退款申请 + * @param reqDTO é€€æ¬¾ç”³è¯·ä¿¡æ¯ + * @return é€€æ¬¾ç”³è¯·è¿”å›žä¿¡æ¯ + */ + PayRefundRespBO refund(PayRefundReqBO reqDTO); +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundPostReqBO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundPostReqBO.java new file mode 100644 index 000000000..4ff7eb115 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundPostReqBO.java @@ -0,0 +1,96 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.bo; + +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Data +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundPostReqBO { + + + /** + * 渠é“的通用返回结果 + */ + private PayChannelRespEnum respEnum; + + + + private PayRefundTypeEnum refundTypeEnum; + + /** + * å·²é€€æ¬¾çš„æ€»é‡‘é¢ + */ + private Long refundedAmount; + + /** + * æœ¬æ¬¡é€€æ¬¾é‡‘é¢ + */ + private Long refundAmount; + + /** + * 已退款次数 + */ + private Integer refundedTimes; + + + /** + * 订å•ç¼–å· + */ + private Long orderId; + + /** + * 退款å•ç¼–å· + */ + private Long refundId; + + + /** + * 渠é“退款å•å· + */ + private String channelRefundNo; + + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no + * æ”¯ä»˜äº¤æ˜“å· {PayOrderExtensionDO no字段} å’Œ 渠é“订å•å· ä¸èƒ½åŒæ—¶ä¸ºç©º + */ + private String payTradeNo; + + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no + * 退款请求å•å· åŒä¸€é€€æ¬¾è¯·æ±‚å•å·å¤šæ¬¡è¯·æ±‚åªé€€ä¸€ç¬”。 + */ + private String refundReqNo; + + + + /** + * è°ƒç”¨å¼‚å¸¸é”™è¯¯ä¿¡æ¯ + */ + private String exceptionMsg; + + + /** + * 渠é“çš„é”™è¯¯ç  + */ + private String channelErrCode; + + + /** + * 渠é“的错误æè¿° + */ + private String channelErrMsg; + + +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundReqBO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundReqBO.java new file mode 100644 index 000000000..86fc9c1eb --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundReqBO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.bo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +/** + * é€€æ¬¾ç”³è¯·å• Request DTO + */ +@Data +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundReqBO { + + /** + * 支付订å•ç¼–å·è‡ªå¢ž + */ + private Long payOrderId; + + /** + * é€€æ¬¾é‡‘é¢ + */ + private Long amount; + + /** + * 退款原因 + */ + private String reason; + + + /** + * 商户退款订å•å· + */ + private String merchantRefundNo; + + /** + * 用户 IP + */ + private String userIp; +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundRespBO.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundRespBO.java new file mode 100644 index 000000000..f66090ac8 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/bo/PayRefundRespBO.java @@ -0,0 +1,23 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.bo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +/** + * é€€æ¬¾ç”³è¯·å• Response DTO + */ +@Data +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundRespBO { + + /** + * 支付退款å•ç¼–å·ï¼Œ 自增 + */ + private Long refundId; +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelFailedHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelFailedHandler.java new file mode 100644 index 000000000..4447f4930 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelFailedHandler.java @@ -0,0 +1,62 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Optional; + +/** + * 支付退款订å•æ¸ é“返回失败的åŽç½®å¤„ç†ç±» + * {@link PayChannelRespEnum#CALL_EXCEPTION} + * {@link PayChannelRespEnum#CAN_NOT_RETRY_FAILURE} + */ +@Service +public class PayRefundChannelFailedHandler extends PayRefundAbstractChannelPostHandler { + + @Resource + private PayNotifyCoreService payNotifyCoreService; + + public PayRefundChannelFailedHandler(PayOrderCoreMapper payOrderCoreMapper, PayRefundMapper payRefundMapper) { + super(payOrderCoreMapper, payRefundMapper); + } + + @Override + public PayChannelRespEnum[] supportHandleResp() { + return new PayChannelRespEnum[] {PayChannelRespEnum.CALL_EXCEPTION, PayChannelRespEnum.CAN_NOT_RETRY_FAILURE}; + } + + + @Override + public void handleRefundChannelResp(PayRefundPostReqBO respBO) { + //退款失败 + //更新退款å•è¡¨ + PayRefundDO updateRefundDO = new PayRefundDO(); + + updateRefundDO.setId(respBO.getRefundId()) + .setStatus(PayRefundStatusEnum.FAILURE.getStatus()) + .setChannelErrorCode(respBO.getChannelErrCode()) + .setChannelErrorMsg(Optional.ofNullable(respBO.getChannelErrMsg()) + .orElse(respBO.getExceptionMsg())); + updatePayRefund(updateRefundDO); + PayOrderDO updateOrderDO = new PayOrderDO(); + //更新订å•è¡¨ + updateOrderDO.setId(respBO.getOrderId()) + .setRefundTimes(respBO.getRefundedTimes() + 1); + updatePayOrder(updateOrderDO); + // 立刻æ’入退款通知记录 + // TODO 通知商户æˆåŠŸæˆ–者失败. 现在通知似乎没有实现, åªæ˜¯å›žè°ƒ + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder() + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(respBO.getRefundId()).build()); + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelNotifyHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelNotifyHandler.java new file mode 100644 index 000000000..3cced50f4 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelNotifyHandler.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import org.springframework.stereotype.Service; + +/** + * 支付退款订å•æ¸ é“返回通知 {@link PayChannelRespEnum#PROCESSING_NOTIFY},åŽç½®å¤„ç†ç±» + * 支付å®é€€æ¬¾å•å¥½åƒæ²¡æœ‰å›žè°ƒï¼Œ 微信会触å‘回调 + */ +@Service +public class PayRefundChannelNotifyHandler extends PayRefundAbstractChannelPostHandler { + + public PayRefundChannelNotifyHandler(PayOrderCoreMapper payOrderCoreMapper, + PayRefundMapper payRefundMapper) { + super(payOrderCoreMapper, payRefundMapper); + } + + @Override + public PayChannelRespEnum[] supportHandleResp() { + return new PayChannelRespEnum[] {PayChannelRespEnum.PROCESSING_NOTIFY}; + } + + @Override + public void handleRefundChannelResp(PayRefundPostReqBO respBO) { + PayRefundDO updateRefundDO = new PayRefundDO(); + //更新退款å•è¡¨ + updateRefundDO.setId(respBO.getRefundId()) + .setStatus(PayRefundStatusEnum.PROCESSING_NOTIFY.getStatus()); + updatePayRefund(updateRefundDO); + + PayOrderDO updateOrderDO = new PayOrderDO(); + //更新订å•è¡¨ + updateOrderDO.setId(respBO.getOrderId()) + .setRefundTimes(respBO.getRefundedTimes() + 1); + updatePayOrder(updateOrderDO); + + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java new file mode 100644 index 000000000..e39ebb9d6 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import org.springframework.stereotype.Service; + +import java.util.Objects; + +/** + * 支付退款订å•æ¸ é“返回需调用查询接å£çš„åŽç½®å¤„ç†ç±» + * {@link PayChannelRespEnum#PROCESSING_QUERY} //TODO 芋é“æºç  是ä¸æ˜¯å¾®ä¿¡æœ‰è¿™æ ·çš„情况 + * {@link PayChannelRespEnum#READ_TIME_OUT_EXCEPTION} + */ +@Service +public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHandler { + + + public PayRefundChannelQueryHandler(PayOrderCoreMapper payOrderCoreMapper, + PayRefundMapper payRefundMapper) { + super(payOrderCoreMapper, payRefundMapper); + } + + @Override + public PayChannelRespEnum[] supportHandleResp() { + return new PayChannelRespEnum[]{PayChannelRespEnum.PROCESSING_QUERY, PayChannelRespEnum.READ_TIME_OUT_EXCEPTION}; + } + + @Override + public void handleRefundChannelResp(PayRefundPostReqBO respBO) { + final PayChannelRespEnum respEnum = respBO.getRespEnum(); + PayRefundStatusEnum refundStatus = + Objects.equals(PayChannelRespEnum.PROCESSING_QUERY, respEnum) ? PayRefundStatusEnum.PROCESSING_QUERY + : PayRefundStatusEnum.UNKNOWN_QUERY; + //更新退款å•è¡¨ + PayRefundDO updateRefundDO = new PayRefundDO(); + updateRefundDO.setId(respBO.getRefundId()) + .setStatus(refundStatus.getStatus()); + updatePayRefund(updateRefundDO); + + PayOrderDO updateOrderDO = new PayOrderDO(); + //更新订å•è¡¨ + updateOrderDO.setId(respBO.getOrderId()) + .setRefundTimes(respBO.getRefundedTimes() + 1); + updatePayOrder(updateOrderDO); + + //TODO å‘起查询任务 + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelRetryHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelRetryHandler.java new file mode 100644 index 000000000..e6ee5967e --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelRetryHandler.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * 支付退款订å•æ¸ é“返回é‡è¯•çš„åŽç½®å¤„ç†ç±» + * {@link PayChannelRespEnum#RETRY_FAILURE} + */ +@Service +public class PayRefundChannelRetryHandler extends PayRefundAbstractChannelPostHandler { + + + public PayRefundChannelRetryHandler(PayOrderCoreMapper payOrderCoreMapper, + PayRefundMapper payRefundMapper) { + super(payOrderCoreMapper, payRefundMapper); + } + + @Override + public PayChannelRespEnum[] supportHandleResp() { + return new PayChannelRespEnum[] {PayChannelRespEnum.RETRY_FAILURE}; + } + + @Override + public void handleRefundChannelResp(PayRefundPostReqBO respBO) { + + PayRefundDO updateRefundDO = new PayRefundDO(); + //更新退款å•è¡¨ + updateRefundDO.setId(respBO.getRefundId()) + .setStatus(PayRefundStatusEnum.UNKNOWN_RETRY.getStatus()) + .setChannelErrorCode(respBO.getChannelErrCode()) + .setChannelErrorMsg(respBO.getChannelErrMsg()); + updatePayRefund(updateRefundDO); + + PayOrderDO updateOrderDO = new PayOrderDO(); + //更新订å•è¡¨ + updateOrderDO.setId(respBO.getOrderId()) + .setRefundTimes(respBO.getRefundedTimes() + 1); + updatePayOrder(updateOrderDO); + + //TODO å‘èµ·é‡è¯•ä»»åŠ¡ + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelSuccessHandler.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelSuccessHandler.java new file mode 100644 index 000000000..ca6fad3a0 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelSuccessHandler.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.PayNotifyCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundAbstractChannelPostHandler; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import java.util.Date; +/** + * 支付退款订å•æ¸ é“返回退款æˆåŠŸçš„åŽç½®å¤„ç†ç±» + * {@link PayChannelRespEnum#SYNC_SUCCESS} + */ +@Service +public class PayRefundChannelSuccessHandler extends PayRefundAbstractChannelPostHandler { + + + @Resource + private PayNotifyCoreService payNotifyCoreService; + + + public PayRefundChannelSuccessHandler(PayOrderCoreMapper payOrderCoreMapper, + PayRefundMapper payRefundMapper) { + super(payOrderCoreMapper, payRefundMapper); + } + + @Override + public PayChannelRespEnum[] supportHandleResp() { + return new PayChannelRespEnum[]{PayChannelRespEnum.SYNC_SUCCESS}; + } + + @Override + public void handleRefundChannelResp(PayRefundPostReqBO respBO) { + //退款æˆåŠŸ + PayRefundDO updateRefundDO = new PayRefundDO(); + //更新退款å•è¡¨ + updateRefundDO.setId(respBO.getRefundId()) + .setStatus(PayRefundStatusEnum.SUCCESS.getStatus()) + .setChannelRefundNo(respBO.getChannelRefundNo()) + .setSuccessTime(new Date()); + updatePayRefund(updateRefundDO); + + PayOrderDO updateOrderDO = new PayOrderDO(); + //更新订å•è¡¨ + updateOrderDO.setId(respBO.getOrderId()) + .setRefundTimes(respBO.getRefundedTimes() + 1) + .setRefundStatus(respBO.getRefundTypeEnum().getStatus()) + .setRefundAmount(respBO.getRefundedAmount()+respBO.getRefundAmount()); + updatePayOrder(updateOrderDO); + + // 立刻æ’入退款通知记录 + // TODO 通知商户æˆåŠŸæˆ–者失败. 现在通知似乎没有实现, åªæ˜¯å›žè°ƒ + payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder() + .type(PayNotifyTypeEnum.REFUND.getType()).dataId(respBO.getRefundId()).build()); + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java new file mode 100644 index 000000000..1e5fef729 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java @@ -0,0 +1,203 @@ +package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayRefundCoreConvert; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper; +import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayRefundMapper; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundStatusEnum; +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundChannelPostHandler; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundPostReqBO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO; +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils; +import cn.iocoder.yudao.framework.pay.core.client.PayClient; +import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.*; + +import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; + +@Service +@Slf4j +public class PayRefundCoreServiceImpl implements PayRefundCoreService { + + @Resource + private PayOrderCoreMapper payOrderCoreMapper; + + @Resource + private PayRefundMapper payRefundMapper; + + @Resource + private PayOrderExtensionCoreMapper payOrderExtensionCoreMapper; + + @Resource + private PayAppCoreService payAppCoreService; + + @Resource + private PayChannelCoreService payChannelCoreService; + + @Resource + private PayClientFactory payClientFactory; + + /** + * 处ç†æ¸ é“返回结果的åŽç½®å¤„ç†å™¨ é›†åˆ + */ + @Resource + private List handlerList; + + + private final EnumMap mapHandler = new EnumMap<>(PayChannelRespEnum.class); + + + + @PostConstruct + public void init(){ + + if (Objects.nonNull(handlerList)) { + handlerList.forEach(t->{ + for (PayChannelRespEnum item : t.supportHandleResp()) { + mapHandler.put(item, t); + } + }); + } + + } + + + @Override + @Transactional(rollbackFor = Exception.class) + public PayRefundRespBO refund(PayRefundReqBO reqBO) { + // 获得 PayOrderDO + PayOrderDO order = payOrderCoreMapper.selectById(reqBO.getPayOrderId()); + // 校验订å•æ˜¯å¦å­˜åœ¨ + if (Objects.isNull(order) ) { + throw exception(PAY_ORDER_NOT_FOUND); + } + // 校验 App + PayAppDO app = payAppCoreService.validPayApp(order.getAppId()); + // 校验支付渠é“是å¦æœ‰æ•ˆ + PayChannelDO channel = payChannelCoreService.validPayChannel(order.getChannelId()); + // 校验支付客户端是å¦æ­£ç¡®åˆå§‹åŒ– + PayClient client = payClientFactory.getPayClient(channel.getId()); + if (client == null) { + log.error("[refund][渠é“ç¼–å·({}) 找ä¸åˆ°å¯¹åº”的支付客户端]", channel.getId()); + throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); + } + + //校验退款的æ¡ä»¶ + validatePayRefund(reqBO, order); + + //退款类型 + PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME; + if (Objects.equals(reqBO.getAmount(), order.getAmount())) { + refundType = PayRefundTypeEnum.ALL; + } + + //退款å•å…¥åº“ 退款å•çŠ¶æ€ï¼šç”Ÿæˆ, 没有和渠é“产生交互 + PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId()); + PayRefundDO refundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo()) + .appId(order.getAppId()) + .channelOrderNo(order.getChannelOrderNo()) + .channelCode(order.getChannelCode()) + .channelId(order.getChannelId()) + .merchantId(order.getMerchantId()) + .orderId(order.getId()) + .merchantRefundNo(reqBO.getMerchantRefundNo()) + .notifyUrl(app.getRefundNotifyUrl()) + .payAmount(order.getAmount()) + .refundAmount(reqBO.getAmount()) + .userIp(reqBO.getUserIp()) + .merchantOrderId(order.getMerchantOrderId()) + .tradeNo(orderExtensionDO.getNo()) + .status(PayRefundStatusEnum.CREATE.getStatus()) + .reason(reqBO.getReason()) + .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus()) + .reqNo(PaySeqUtils.genRefundReqNo()) + .type(refundType.getStatus()) + .build(); + + payRefundMapper.insert(refundDO); + + PayRefundUnifiedReqDTO unifiedReqDTO = PayRefundUnifiedReqDTO.builder() + .userIp(reqBO.getUserIp()) + .channelOrderNo(refundDO.getChannelOrderNo()) + .payTradeNo(refundDO.getTradeNo()) + .refundReqNo(refundDO.getReqNo()) + .amount(reqBO.getAmount()) + .reason(refundDO.getReason()) + .build(); + + //调用渠é“进行退款 + PayRefundUnifiedRespDTO refundUnifiedRespDTO = client.unifiedRefund(unifiedReqDTO); + + //æ ¹æ®æ¸ é“返回,获å–退款åŽç½®å¤„ç†ï¼Œç”±postHandler è¿›è¡Œå¤„ç† + PayRefundChannelPostHandler payRefundChannelPostHandler = mapHandler.get(refundUnifiedRespDTO.getRespEnum()); + + if(Objects.isNull(payRefundChannelPostHandler)){ + throw exception(PAY_REFUND_POST_HANDLER_NOT_FOUND); + } + + PayRefundPostReqBO bo = PayRefundCoreConvert.INSTANCE.convert(refundUnifiedRespDTO); + bo.setRefundAmount(reqBO.getAmount()) + .setRefundedAmount(order.getRefundAmount()) + .setRefundedTimes(order.getRefundTimes()) + .setRefundId(refundDO.getId()) + .setOrderId(order.getId()) + .setRefundTypeEnum(refundType); + + //调用退款的åŽç½®å¤„ç† + payRefundChannelPostHandler.handleRefundChannelResp(bo); + + return PayRefundRespBO.builder().refundId(refundDO.getId()).build(); + } + + + /** + * 校验是å¦è¿›è¡Œé€€æ¬¾ + * @param reqBO é€€æ¬¾ç”³è¯·ä¿¡æ¯ + * @param order 原始支付订å•ä¿¡æ¯ + */ + private void validatePayRefund(PayRefundReqBO reqBO, PayOrderDO order) { + + // 校验状æ€ï¼Œå¿…é¡»æ˜¯æ”¯ä»˜çŠ¶æ€ + if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) { + throw exception(PAY_ORDER_STATUS_IS_NOT_SUCCESS); + } + //是å¦å·²ç»å…¨é¢é€€æ¬¾ + if (PayRefundTypeEnum.ALL.getStatus().equals(order.getRefundStatus())) { + throw exception(PAY_REFUND_ALL_REFUNDED); + } + // æ ¡éªŒé‡‘é¢ é€€æ¬¾é‡‘é¢ä¸èƒ½å¤§äºŽ åŽŸå®šçš„é‡‘é¢ + if(reqBO.getAmount() + order.getRefundAmount() > order.getAmount()){ + throw exception(PAY_REFUND_AMOUNT_EXCEED); + } + //校验渠é“订å•å· + if (StrUtil.isEmpty(order.getChannelOrderNo())) { + throw exception(PAY_REFUND_CHN_ORDER_NO_IS_NULL); + } + //TODO é€€æ¬¾çš„æœŸé™ é€€æ¬¾æ¬¡æ•°çš„æŽ§åˆ¶ + + + } +} diff --git a/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java new file mode 100644 index 000000000..959cef746 --- /dev/null +++ b/yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/util/PaySeqUtils.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.coreservice.modules.pay.util; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.RandomUtil; + +import java.util.Date; +import java.util.concurrent.atomic.AtomicLong; + +/** + * 支付相关编å·çš„生产 + */ +public class PaySeqUtils { + + private static final AtomicLong REFUND_REQ_NO_SEQ = new AtomicLong(0L); + + private static final AtomicLong MER_REFUND_NO_SEQ = new AtomicLong(0L); + + private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L); + + /** + * 生æˆå•†æˆ·é€€æ¬¾å•å·ï¼Œç”¨äºŽæµ‹è¯•ï¼Œåº”è¯¥ç”±å•†æˆ·ç³»ç»Ÿç”Ÿæˆ + * @return å•†æˆ·é€€æ¬¾å• + */ + public static String genMerchantRefundNo() { + return String.format("%s%s%04d", "MR", + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN), + (int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000); + } + + /** + * 生æˆé€€æ¬¾è¯·æ±‚å· + * @return é€€æ¬¾è¯·æ±‚å· + */ + public static String genRefundReqNo() { + return String.format("%s%s%04d", "RR", + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN), + (int) REFUND_REQ_NO_SEQ.getAndIncrement() % 10000); + } + + /** + * 生æˆå•†æˆ·è®¢å•ç¼–å·å· ç”¨äºŽæµ‹è¯•ï¼Œåº”è¯¥ç”±å•†æˆ·ç³»ç»Ÿç”Ÿæˆ + * @return 商户订å•ç¼–å· + */ + public static String genMerchantOrderNo() { + return String.format("%s%s%04d", "MO", + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN), + (int) MER_ORDER_NO_SEQ.getAndIncrement() % 10000); + } +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java index d15a207f9..f49d27981 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java @@ -1,9 +1,7 @@ package cn.iocoder.yudao.framework.pay.core.client; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.*; /** * 支付客户端,用于对接å„支付渠é“çš„ SDK,实现å‘起支付ã€é€€æ¬¾ç­‰åŠŸèƒ½ @@ -36,4 +34,12 @@ public interface PayClient { */ PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception; + + /** + * 调用支付渠é“,进行退款 + * @param reqDTO ç»Ÿä¸€é€€æ¬¾è¯·æ±‚ä¿¡æ¯ + * @return å„支付渠é“的统一返回结果 + */ + PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO); + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java index 344e98810..7b0672460 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java +++ b/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; import java.util.Map; -// TODO @jason:注释è¦å†™ä¸‹å“ˆã€‚字段ä¸è¦ä½¿ç”¨ // 注释,éžæ ‡å‡† + +/** + * 支付订å•å›žè°ƒï¼Œæ¸ é“çš„ç»Ÿä¸€é€šçŸ¥è¯·æ±‚æ•°æ® + */ @Data @ToString @Builder diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java new file mode 100644 index 000000000..585400b40 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.hibernate.validator.constraints.URL; + +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +/** + * 统一 退款 Request DTO + * + * @author jason + */ +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class PayRefundUnifiedReqDTO { + + /** + * 用户 IP + */ + private String userIp; + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 transaction_id + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 trade_no + * 渠é“订å•å· + */ + private String channelOrderNo; + + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no + * æ”¯ä»˜äº¤æ˜“å· {PayOrderExtensionDO no字段} å’Œ 渠é“订å•å· ä¸èƒ½åŒæ—¶ä¸ºç©º + */ + private String payTradeNo; + + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no + * 退款请求å•å· åŒä¸€é€€æ¬¾è¯·æ±‚å•å·å¤šæ¬¡è¯·æ±‚åªé€€ä¸€ç¬”。 + */ + @NotEmpty(message = "退款请求å•å·") + private String refundReqNo; + + + /** + * 退款原因 + */ + @NotEmpty(message = "退款原因ä¸èƒ½ä¸ºç©º") + private String reason; + + + /** + * 退款金é¢ï¼Œå•ä½ï¼šåˆ† + */ + @NotNull(message = "退款金é¢ä¸èƒ½ä¸ºç©º") + @DecimalMin(value = "0", inclusive = false, message = "支付金é¢å¿…须大于零") + private Long amount; + + + + + /** + * 退款结果 notify 回调地å€ï¼Œ 支付å®é€€æ¬¾ä¸éœ€è¦å›žè°ƒåœ°å€ï¼Œ å¾®ä¿¡éœ€è¦ + */ + @URL(message = "支付结果的 notify 回调地å€å¿…须是 URL æ ¼å¼") + private String notifyUrl; +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java new file mode 100644 index 000000000..956e31a55 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java @@ -0,0 +1,73 @@ +package cn.iocoder.yudao.framework.pay.core.client.dto; + +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotEmpty; +/** + * 统一 退款 Response DTO + * + * @author jason + */ +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class PayRefundUnifiedRespDTO { + + + + /** + * 渠é“的通用返回结果 + */ + private PayChannelRespEnum respEnum; + + + + /** + * 渠é“退款å•å· + */ + private String channelRefundNo; + + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_trade_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no + * æ”¯ä»˜äº¤æ˜“å· {PayOrderExtensionDO no字段} å’Œ 渠é“订å•å· ä¸èƒ½åŒæ—¶ä¸ºç©º + */ + private String payTradeNo; + + + /** + * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no + * https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no + * 退款请求å•å· åŒä¸€é€€æ¬¾è¯·æ±‚å•å·å¤šæ¬¡è¯·æ±‚åªé€€ä¸€ç¬”。 + */ + private String refundReqNo; + + + + /** + * è°ƒç”¨å¼‚å¸¸é”™è¯¯ä¿¡æ¯ + */ + private String exceptionMsg; + + + /** + * 渠é“çš„é”™è¯¯ç  + */ + private String channelErrCode; + + + /** + * 渠é“的错误æè¿° + */ + private String channelErrMsg; + + //TODO 退款资金渠 ??? +} diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java index 1e228b252..8f5678b8b 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java +++ b/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; import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; import lombok.extern.slf4j.Slf4j; +import java.net.SocketTimeoutException; + import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; /** @@ -22,6 +27,7 @@ public abstract class AbstractPayClient implemen * 渠é“ç¼–å· */ private final Long channelId; + /** * 渠é“ç¼–ç  */ @@ -91,7 +97,37 @@ public abstract class AbstractPayClient implemen return result; } + + protected abstract PayCommonResult doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable; + + @Override + public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + + PayRefundUnifiedRespDTO resp; + try { + resp = doUnifiedRefund(reqDTO); + }catch (SocketTimeoutException ex){ + //网络 read time out 异常 + log.error("[unifiedRefund][request({}) å‘起退款失败,网络读超时,退款状æ€æœªçŸ¥]", toJsonString(reqDTO), ex); + return PayRefundUnifiedRespDTO.builder() + .exceptionMsg(ex.getMessage()) + .respEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION) + .build(); + } catch (Throwable ex) { + // 打å°å¼‚常日志 + log.error("[unifiedRefund][request({}) å‘起退款失败]", toJsonString(reqDTO), ex); + return PayRefundUnifiedRespDTO.builder() + .exceptionMsg(ex.getMessage()) + .respEnum(PayChannelRespEnum.CALL_EXCEPTION) + .build(); + } + return resp; + } + + + protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable; + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java index c64fc5a60..650592998 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java +++ b/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; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateUtil; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.*; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import com.alipay.api.AlipayApiException; @@ -71,6 +69,8 @@ public class AlipayQrPayClient extends AbstractPayClient return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping); } + + @Override public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception { //ç»“æžœè½¬æ¢ @@ -82,4 +82,10 @@ public class AlipayQrPayClient extends AbstractPayClient .data(data.getBody()).build(); } + + @Override + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + //TODO 需è¦å®žçŽ° + throw new UnsupportedOperationException(); + } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java index b14ece3fa..658acc915 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java +++ b/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; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.date.DateUtil; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.*; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum; import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayConfig; import com.alipay.api.DefaultAlipayClient; +import com.alipay.api.domain.AlipayTradeRefundModel; import com.alipay.api.domain.AlipayTradeWapPayModel; +import com.alipay.api.request.AlipayTradeRefundRequest; import com.alipay.api.request.AlipayTradeWapPayRequest; +import com.alipay.api.response.AlipayTradeRefundResponse; import com.alipay.api.response.AlipayTradeWapPayResponse; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import java.net.SocketTimeoutException; import java.util.Map; import java.util.Objects; +import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; + /** * 支付å®ã€æ‰‹æœºç½‘站】的 PayClient 实现类 * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay * * @author 芋é“æºç  */ +@Slf4j public class AlipayWapPayClient extends AbstractPayClient { private DefaultAlipayClient client; @@ -96,4 +103,62 @@ public class AlipayWapPayClient extends AbstractPayClient .data(data.getBody()).build(); } + + @Override + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) { + AlipayTradeRefundModel model=new AlipayTradeRefundModel(); + model.setTradeNo(reqDTO.getChannelOrderNo()); + model.setOutTradeNo(reqDTO.getPayTradeNo()); + model.setOutRequestNo(reqDTO.getRefundReqNo()); + model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString()); + model.setRefundReason(reqDTO.getReason()); + AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest(); + refundRequest.setBizModel(model); + PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO(); + try { + AlipayTradeRefundResponse response = client.execute(refundRequest); + log.info("[doUnifiedRefund][response({}) å‘起退款 渠é“返回", toJsonString(response)); + if (response.isSuccess()) { + //退款æˆåŠŸ + //TODO 沙箱环境 返回 çš„tradeNo(渠é“退款å•å·ï¼‰ å’Œ 订å•çš„tradNo 是一个值,是ä¸æ˜¯ç†è§£ä¸å¯¹? + respDTO.setRespEnum(PayChannelRespEnum.SYNC_SUCCESS) + .setChannelRefundNo(response.getTradeNo()) + .setPayTradeNo(response.getOutTradeNo()); + }else{ + //ç‰¹æ®Šå¤„ç† sub_code ACQ.SYSTEM_ERROR(系统错误), 需è¦è°ƒç”¨é‡è¯•ä»»åŠ¡ + //沙箱环境返回的貌似是â€aop.ACQ.SYSTEM_ERROR“, 用contain + if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) { + respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE) + .setChannelErrMsg(response.getSubMsg()) + .setChannelErrCode(response.getSubCode()); + }else{ + //其他当åšä¸å¯ä»¥é‡è¯•çš„错误 + respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE) + .setChannelErrCode(response.getSubCode()) + .setChannelErrMsg(response.getSubMsg()); + } + } + return respDTO; + } catch (AlipayApiException e) { + //TODO 记录异常日志 + log.error("[doUnifiedRefund][request({}) å‘起退款失败,网络读超时,退款状æ€æœªçŸ¥]", toJsonString(reqDTO), e); + Throwable cause = e.getCause(); + //网络 read time out 异常, 退款状æ€æœªçŸ¥ + if (cause instanceof SocketTimeoutException) { + respDTO.setExceptionMsg(e.getMessage()) + .setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION); + }else{ + respDTO.setExceptionMsg(e.getMessage()) + .setChannelErrCode(e.getErrCode()) + .setChannelErrMsg(e.getErrMsg()) + .setRespEnum(PayChannelRespEnum.CALL_EXCEPTION); + } + + return respDTO; + + } + + + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java index 41c762562..bf4f5f32c 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java +++ b/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; import cn.iocoder.yudao.framework.common.util.io.FileUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO; -import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; +import cn.iocoder.yudao.framework.pay.core.client.dto.*; import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; @@ -92,6 +90,7 @@ public class WXPubPayClient extends AbstractPayClient { return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping); } + private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException { // 构建 WxPayUnifiedOrderRequest 对象 WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder() @@ -142,4 +141,11 @@ public class WXPubPayClient extends AbstractPayClient { .data(data.getBody()).build(); } + + @Override + protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable { + //TODO 需è¦å®žçŽ° + throw new UnsupportedOperationException(); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java new file mode 100644 index 000000000..e9d29a9f3 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelRespEnum.java @@ -0,0 +1,50 @@ +package cn.iocoder.yudao.framework.pay.core.enums; + +/** + * 统一的渠é“返回结果 + * @author jason + */ +public enum PayChannelRespEnum { + + /** + * 接å£é€šè®¯æ­£å¸¸è¿”回, 并明确处ç†æˆåŠŸ ,ä¸éœ€è¦é€šè¿‡æŸ¥è¯¢æˆ–è€…å›žè°ƒæŽ¥å£ è¿›è¡Œä¸‹ä¸€æ­¥å¤„ç† + */ + SYNC_SUCCESS, + + /** + * 接å£é€šè®¯æ­£å¸¸è¿”回, 但返回错误,并且ä¸èƒ½é€šè¿‡é‡è¯•è§£å†³çš„错误, + * 如æ交失败, 业务错误(余é¢ä¸è¶³ï¼‰ï¼Œ 或者å‚数错误, ç­¾å错误, 需è¦å¹²é¢„åŽæ‰èƒ½å¤„ç† + */ + CAN_NOT_RETRY_FAILURE, + + + /** + * 接å£é€šè®¯æ­£å¸¸è¿”回, + * å¯ä»¥é€šè¿‡é‡è¯•è§£å†³çš„错误. 如系统超时, 系统ç¹å¿™ã€‚状æ€æœªçŸ¥ ä¸èƒ½æ”¹å˜è¯·æ±‚å‚数,如退款å•è¯·æ±‚å·ï¼Œé‡å‘请求 + */ + RETRY_FAILURE, + + + /** + * 接å£é€šè®¯æ­£å¸¸è¿”回,但是处ç†ç»“æžœ 需è¦æ¸ é“å›žè°ƒè¿›è¡Œä¸‹ä¸€æ­¥å¤„ç† + */ + PROCESSING_NOTIFY, + + + /** + * 接å£é€šè®¯æ­£å¸¸è¿”回, 但是处ç†ç»“æžœ,需è¦è°ƒç”¨æŸ¥è¯¢æŽ¥å£ 进行查询 + */ + PROCESSING_QUERY, + + + /** + * 本系统调用渠é“接å£å¼‚常, 渠é“接å£è¯·æ±‚未正常å‘é€ï¼Œ 本系统ä¸å¯é¢„知的异常,较少å‘生, å¯è®¤ä¸ºå¤±è´¥ã€‚ ä¸ç”¨é‡è¯•. + */ + CALL_EXCEPTION, + + + /** + * 本系统调用渠é“接å£æˆåŠŸï¼Œ 但是未接å—到请求结果,较少å‘生(需åˆç†è®¾ç½®read time out ) 结果未知。 需è¦è°ƒç”¨æŸ¥è¯¢æŽ¥å£è¿›è¡ŒæŸ¥è¯¢ + */ + READ_TIME_OUT_EXCEPTION; +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java new file mode 100644 index 000000000..42d042720 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayRefundController.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.userserver.modules.pay.controller.order; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO; +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundReqVO; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundRespVO; +import cn.iocoder.yudao.userserver.modules.pay.convert.order.PayRefundConvert; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; + +@Api(tags = "退款订å•") +@RestController +@RequestMapping("/pay/order") +@Validated +@Slf4j +public class PayRefundController { + + @Resource + private PayRefundCoreService payRefundCoreService; + + @PostMapping("/refund") + @ApiOperation("æ交退款订å•") + public CommonResult refund(@RequestBody PayRefundReqVO reqVO){ + PayRefundReqBO reqBO = PayRefundConvert.INSTANCE.convert(reqVO); + reqBO.setUserIp(getClientIP()); + //TODO 测试暂时模拟生æˆå•†æˆ·é€€æ¬¾è®¢å• + reqBO.setMerchantRefundNo(PaySeqUtils.genMerchantRefundNo()); + return CommonResult.success( PayRefundConvert.INSTANCE.convert(payRefundCoreService.refund(reqBO))); + } +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java new file mode 100644 index 000000000..c9d73fdf9 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.userserver.modules.pay.controller.order.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotEmpty; + +@ApiModel("é€€æ¬¾è®¢å• Req VO") +@Data +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundReqVO { + + @ApiModelProperty(value = "支付订å•ç¼–å·è‡ªå¢ž", required = true, example = "10") + @NotEmpty(message = "支付订å•ç¼–å·è‡ªå¢ž") + private Long payOrderId; + + @ApiModelProperty(value = "退款金é¢", required = true, example = "1") + @NotEmpty(message = "退款金é¢") + private Long amount; + + + @ApiModelProperty(value = "退款原因", required = true, example = "ä¸å–œæ¬¢") + @NotEmpty(message = "退款原因") + private String reason; + + @ApiModelProperty(value = "商户退款订å•å·", required = true, example = "MR202111180000000001") + //TODO æµ‹è¯•æš‚æ—¶æ¨¡æ‹Ÿç”Ÿæˆ + //@NotEmpty(message = "商户退款订å•å·") + private String merchantRefundNo; + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java new file mode 100644 index 000000000..29059ab28 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/vo/PayRefundRespVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.userserver.modules.pay.controller.order.vo; + +import io.swagger.annotations.ApiModel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@ApiModel("æäº¤é€€æ¬¾è®¢å• Response VO") +@Data +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PayRefundRespVO { + + /** + * 支付退款å•ç¼–å·ï¼Œ 自增 + */ + private Long refundId; +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java new file mode 100644 index 000000000..9d5c479b3 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/order/PayRefundConvert.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.userserver.modules.pay.convert.order; + +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundReqBO; +import cn.iocoder.yudao.coreservice.modules.pay.service.order.bo.PayRefundRespBO; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundReqVO; +import cn.iocoder.yudao.userserver.modules.pay.controller.order.vo.PayRefundRespVO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * 支付退款 Convert + * @author jason + */ +@Mapper +public interface PayRefundConvert { + + PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class); + + PayRefundReqBO convert(PayRefundReqVO reqVO); + + PayRefundRespVO convert(PayRefundRespBO respBO); +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java new file mode 100644 index 000000000..3f27f0b4b --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/package-info.java @@ -0,0 +1,6 @@ +/** + * æä¾› POJO ç±»çš„å®žä½“è½¬æ¢ + * + * ç›®å‰ä½¿ç”¨ MapStruct 框架 + */ +package cn.iocoder.yudao.userserver.modules.pay.convert; diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/ã€ŠèŠ‹é“ Spring Boot å¯¹è±¡è½¬æ¢ MapStruct 入门》.md b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/ã€ŠèŠ‹é“ Spring Boot å¯¹è±¡è½¬æ¢ MapStruct 入门》.md new file mode 100644 index 000000000..8153487b7 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/convert/ã€ŠèŠ‹é“ Spring Boot å¯¹è±¡è½¬æ¢ MapStruct 入门》.md @@ -0,0 +1 @@ + diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java index 4767c074d..6dc0a3032 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/shop/controller/ShopOrderController.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.userserver.modules.shop.controller; import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayNotifyOrderReqVO; +import cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo.PayRefundOrderReqVO; import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService; import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderCreateReqDTO; +import cn.iocoder.yudao.coreservice.modules.pay.util.PaySeqUtils; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.userserver.modules.shop.controller.vo.ShopOrderCreateRespVO; @@ -43,10 +45,10 @@ public class ShopOrderController { PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO(); reqDTO.setAppId(6L); reqDTO.setUserIp(getClientIP()); - reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis())); + reqDTO.setMerchantOrderId(PaySeqUtils.genMerchantOrderNo()); reqDTO.setSubject("标题:" + shopOrderId); reqDTO.setBody("内容:" + shopOrderId); - reqDTO.setAmount(1); // å•ä½ï¼šåˆ† + reqDTO.setAmount(200); // å•ä½ï¼šåˆ† reqDTO.setExpireTime(DateUtils.addTime(Duration.ofDays(1))); Long payOrderId = payOrderCoreService.createPayOrder(reqDTO); @@ -55,6 +57,8 @@ public class ShopOrderController { .payOrderId(payOrderId).build()); } + + @PostMapping("/pay-notify") @ApiOperation("支付回调") public CommonResult payNotify(@RequestBody @Valid PayNotifyOrderReqVO reqVO) { @@ -62,4 +66,11 @@ public class ShopOrderController { return success(true); } + @PostMapping("/refund-notify") + @ApiOperation("退款回调") + public CommonResult refundNotify(@RequestBody @Valid PayRefundOrderReqVO reqVO) { + log.info("[refundNotify][回调æˆåŠŸ]"); + return success(true); + } + }