支付退款申请,支付宝手机wap 相应实现
This commit is contained in:
parent
19f554176c
commit
7d6f205dc0
|
@ -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='退款订单';
|
|
@ -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
|
||||
-- ----------------------------
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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<PayRefundDO> {
|
||||
|
||||
|
||||
}
|
|
@ -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, "已存在相同的渠道");
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -11,7 +11,7 @@ import lombok.Getter;
|
|||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayOrderRefundStatusEnum implements IntArrayValuable {
|
||||
public enum PayRefundTypeEnum implements IntArrayValuable {
|
||||
|
||||
NO(0, "未退款"),
|
||||
SOME(10, "部分退款"),
|
|
@ -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());
|
||||
}
|
||||
|
||||
// 执行插入
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -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 发起查询任务
|
||||
}
|
||||
}
|
|
@ -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 发起重试任务
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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<PayRefundChannelPostHandler> handlerList;
|
||||
|
||||
|
||||
private final EnumMap<PayChannelRespEnum, PayRefundChannelPostHandler> 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 退款的期限 退款次数的控制
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@ import lombok.ToString;
|
|||
|
||||
import java.util.Map;
|
||||
|
||||
// TODO @jason:注释要写下哈。字段不要使用 // 注释,非标准
|
||||
|
||||
/**
|
||||
* 支付订单回调,渠道的统一通知请求数据
|
||||
*/
|
||||
@Data
|
||||
@ToString
|
||||
@Builder
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 退款资金渠 ???
|
||||
}
|
|
@ -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<Config extends PayClientConfig> implemen
|
|||
* 渠道编号
|
||||
*/
|
||||
private final Long channelId;
|
||||
|
||||
/**
|
||||
* 渠道编码
|
||||
*/
|
||||
|
@ -91,7 +97,37 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> 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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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<AlipayPayClientConfig>
|
|||
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<AlipayPayClientConfig>
|
|||
.data(data.getBody()).build();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AlipayPayClientConfig> {
|
||||
|
||||
private DefaultAlipayClient client;
|
||||
|
@ -96,4 +103,62 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig>
|
|||
.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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<WXPayClientConfig> {
|
|||
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<WXPayClientConfig> {
|
|||
.data(data.getBody()).build();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||
//TODO 需要实现
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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<PayRefundRespVO> 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)));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* 提供 POJO 类的实体转换
|
||||
*
|
||||
* 目前使用 MapStruct 框架
|
||||
*/
|
||||
package cn.iocoder.yudao.userserver.modules.pay.convert;
|
|
@ -0,0 +1 @@
|
|||
<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>
|
|
@ -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<Boolean> payNotify(@RequestBody @Valid PayNotifyOrderReqVO reqVO) {
|
||||
|
@ -62,4 +66,11 @@ public class ShopOrderController {
|
|||
return success(true);
|
||||
}
|
||||
|
||||
@PostMapping("/refund-notify")
|
||||
@ApiOperation("退款回调")
|
||||
public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundOrderReqVO reqVO) {
|
||||
log.info("[refundNotify][回调成功]");
|
||||
return success(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue