多模块重构 7:pay 模块的初始化
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
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.PayOrderExtensionDO;
|
||||
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.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayOrderCoreConvert {
|
||||
|
||||
PayOrderCoreConvert INSTANCE = Mappers.getMapper(PayOrderCoreConvert.class);
|
||||
|
||||
PayOrderDO convert(PayOrderCreateReqDTO bean);
|
||||
|
||||
PayOrderExtensionDO convert(PayOrderSubmitReqDTO bean);
|
||||
|
||||
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqDTO bean);
|
||||
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
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 org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayRefundCoreConvert {
|
||||
|
||||
PayRefundCoreConvert INSTANCE = Mappers.getMapper(PayRefundCoreConvert.class);
|
||||
|
||||
//TODO 太多需要处理了, 暂时不用
|
||||
@Mappings(value = {
|
||||
@Mapping(source = "amount", target = "payAmount"),
|
||||
@Mapping(source = "id", target = "orderId"),
|
||||
@Mapping(target = "status",ignore = true)
|
||||
})
|
||||
PayRefundDO convert(PayOrderDO orderDO);
|
||||
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 提供 POJO 类的实体转换
|
||||
*
|
||||
* 目前使用 MapStruct 框架
|
||||
*/
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.convert;
|
@@ -1 +0,0 @@
|
||||
<http://www.iocoder.cn/Spring-Boot/MapStruct/?yudao>
|
@@ -1,62 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 支付应用 DO
|
||||
* 一个商户下,可能会有多个支付应用。例如说,京东有京东商城、京东到家等等
|
||||
* 不过一般来说,一个商户,只有一个应用哈~
|
||||
*
|
||||
* 即 PayMerchantDO : PayAppDO = 1 : n
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("pay_app")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayAppDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 应用编号,数据库自增
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
/**
|
||||
* 支付结果的回调地址
|
||||
*/
|
||||
private String payNotifyUrl;
|
||||
/**
|
||||
* 退款结果的回调地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*
|
||||
* 关联 {@link PayMerchantDO#getId()}
|
||||
*/
|
||||
private Long merchantId;
|
||||
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 支付渠道 DO
|
||||
* 一个应用下,会有多种支付渠道,例如说微信支付、支付宝支付等等
|
||||
*
|
||||
* 即 PayAppDO : PayChannelDO = 1 : n
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@TableName(value = "pay_channel", autoResultMap = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayChannelDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 渠道编号,数据库自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 {@link PayChannelEnum}
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 渠道费率,单位:百分比
|
||||
*/
|
||||
private Double feeRate;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*
|
||||
* 关联 {@link PayMerchantDO#getId()}
|
||||
*/
|
||||
private Long merchantId;
|
||||
/**
|
||||
* 应用编号
|
||||
*
|
||||
* 关联 {@link PayAppDO#getId()}
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 支付渠道配置
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private PayClientConfig config;
|
||||
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 支付商户信息 DO
|
||||
* 目前暂时没有特别的用途,主要为未来多商户提供基础。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@TableName("pay_merchant")
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayMerchantDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 商户编号,数据库自增
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
/**
|
||||
* 商户号
|
||||
* 例如说,M233666999
|
||||
* 只有新增时插入,不允许修改
|
||||
*/
|
||||
private String no;
|
||||
/**
|
||||
* 商户全称
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 商户简称
|
||||
*/
|
||||
private String shortName;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
}
|
@@ -1,49 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
/**
|
||||
* 商户支付、退款等的通知 Log
|
||||
* 每次通知时,都会在该表中,记录一次 Log,方便排查问题
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("pay_notify_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayNotifyLogDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 日志编号,自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 通知任务编号
|
||||
*
|
||||
* 关联 {@link PayNotifyTaskDO#getId()}
|
||||
*/
|
||||
private Long taskId;
|
||||
/**
|
||||
* 第几次被通知
|
||||
*
|
||||
* 对应到 {@link PayNotifyTaskDO#getNotifyTimes()}
|
||||
*/
|
||||
private Integer notifyTimes;
|
||||
/**
|
||||
* HTTP 响应结果
|
||||
*/
|
||||
private String response;
|
||||
/**
|
||||
* 支付通知状态
|
||||
*
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
@@ -1,99 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayMerchantDO;
|
||||
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.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 商户支付、退款等的通知
|
||||
* 在支付系统收到支付渠道的支付、退款的结果后,需要不断的通知到业务系统,直到成功。
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("pay_notify_task")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayNotifyTaskDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 通知频率,单位为秒。
|
||||
*
|
||||
* 算上首次的通知,实际是一共 1 + 8 = 9 次。
|
||||
*/
|
||||
public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{
|
||||
15, 15, 30, 180,
|
||||
1800, 1800, 1800, 3600
|
||||
};
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 商户编号
|
||||
*
|
||||
* 关联 {@link PayMerchantDO#getId()}
|
||||
*/
|
||||
private Long merchantId;
|
||||
/**
|
||||
* 应用编号
|
||||
*
|
||||
* 关联 {@link PayAppDO#getId()}
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 通知类型
|
||||
*
|
||||
* 外键 {@link PayNotifyTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 数据编号,根据不同 type 进行关联:
|
||||
*
|
||||
* 1. {@link PayNotifyTypeEnum#ORDER} 时,关联 {@link PayOrderDO#getId()}
|
||||
* 2. {@link PayNotifyTypeEnum#REFUND} 时,关联 {@link PayRefundDO#getId()}
|
||||
*/
|
||||
private Long dataId;
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 通知状态
|
||||
*
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 下一次通知时间
|
||||
*/
|
||||
private Date nextNotifyTime;
|
||||
/**
|
||||
* 最后一次执行时间
|
||||
*/
|
||||
private Date lastExecuteTime;
|
||||
/**
|
||||
* 当前通知次数
|
||||
*/
|
||||
private Integer notifyTimes;
|
||||
/**
|
||||
* 最大可通知次数
|
||||
*/
|
||||
private Integer maxNotifyTimes;
|
||||
/**
|
||||
* 通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
|
||||
|
||||
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.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;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付订单 DO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("pay_order")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayOrderDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 订单编号,数据库自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 商户编号
|
||||
*
|
||||
* 关联 {@link PayMerchantDO#getId()}
|
||||
*/
|
||||
private Long merchantId;
|
||||
/**
|
||||
* 应用编号
|
||||
*
|
||||
* 关联 {@link PayAppDO#getId()}
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 渠道编号
|
||||
*
|
||||
* 关联 {@link PayChannelDO#getId()}
|
||||
*/
|
||||
private Long channelId;
|
||||
/**
|
||||
* 渠道编码
|
||||
*
|
||||
* 枚举 {@link PayChannelEnum}
|
||||
*/
|
||||
private String channelCode;
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
* 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
private String subject;
|
||||
/**
|
||||
* 商品描述信息
|
||||
*/
|
||||
private String body;
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 通知商户支付结果的回调状态
|
||||
*
|
||||
* 枚举 {@link PayOrderNotifyStatusEnum}
|
||||
*/
|
||||
private Integer notifyStatus;
|
||||
// /**
|
||||
// * 商户拓展参数
|
||||
// */
|
||||
// private Map<String, String> merchantExtras;
|
||||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
private Long amount;
|
||||
/**
|
||||
* 渠道手续费,单位:百分比
|
||||
*
|
||||
* 冗余 {@link PayChannelDO#getFeeRate()}
|
||||
*/
|
||||
private Double channelFeeRate;
|
||||
/**
|
||||
* 渠道手续金额,单位:分
|
||||
*/
|
||||
private Long channelFeeAmount;
|
||||
/**
|
||||
* 支付状态
|
||||
*
|
||||
* 枚举 {@link PayOrderStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
/**
|
||||
* 订单失效时间
|
||||
*/
|
||||
private Date expireTime;
|
||||
/**
|
||||
* 订单支付成功时间
|
||||
*/
|
||||
private Date successTime;
|
||||
/**
|
||||
* 订单支付通知时间,即支付渠道的通知时间
|
||||
*/
|
||||
private Date notifyTime;
|
||||
/**
|
||||
* 支付成功的订单拓展单编号
|
||||
*
|
||||
* 关联 {@link PayOrderDO#getId()}
|
||||
*/
|
||||
private Long successExtensionId;
|
||||
|
||||
// ========== 退款相关字段 ==========
|
||||
/**
|
||||
* 退款状态
|
||||
*
|
||||
* 枚举 {@link PayRefundTypeEnum}
|
||||
*/
|
||||
private Integer refundStatus;
|
||||
/**
|
||||
* 退款次数
|
||||
*/
|
||||
private Integer refundTimes;
|
||||
/**
|
||||
* 退款总金额,单位:分
|
||||
*/
|
||||
private Long refundAmount;
|
||||
|
||||
// ========== 渠道相关字段 ==========
|
||||
/**
|
||||
* 渠道用户编号
|
||||
*
|
||||
* 例如说,微信 openid、支付宝账号
|
||||
*/
|
||||
private String channelUserId;
|
||||
/**
|
||||
* 渠道订单号
|
||||
*/
|
||||
private String channelOrderNo;
|
||||
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付订单拓展 DO
|
||||
*
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName(value = "pay_order_extension",autoResultMap = true)
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayOrderExtensionDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 订单拓展编号,数据库自增
|
||||
*/
|
||||
private Long id;
|
||||
/**
|
||||
* 支付订单号,根据规则生成
|
||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
||||
* 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
|
||||
* 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
|
||||
*
|
||||
* 例如说,P202110132239124200055
|
||||
*/
|
||||
private String no;
|
||||
/**
|
||||
* 订单号
|
||||
*
|
||||
* 关联 {@link PayOrderDO#getId()}
|
||||
*/
|
||||
private Long orderId;
|
||||
/**
|
||||
* 渠道编号
|
||||
*
|
||||
* 关联 {@link PayChannelDO#getId()}
|
||||
*/
|
||||
private Long channelId;
|
||||
/**
|
||||
* 渠道编码
|
||||
*/
|
||||
private String channelCode;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
/**
|
||||
* 支付状态
|
||||
*
|
||||
* 枚举 {@link PayOrderStatusEnum}
|
||||
* 注意,只包含上述枚举的 WAITING 和 SUCCESS
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
*
|
||||
* 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
|
||||
*/
|
||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||
private Map<String, String> channelExtras;
|
||||
/**
|
||||
* 支付渠道异步通知的内容
|
||||
*
|
||||
* 在支持成功后,会记录回调的数据
|
||||
*/
|
||||
private String channelNotifyData;
|
||||
|
||||
}
|
@@ -1,196 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order;
|
||||
|
||||
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.PayRefundStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayRefundTypeEnum;
|
||||
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付退款单 DO
|
||||
* 一个支付订单,可以拥有多个支付退款单
|
||||
*
|
||||
* 即 PayOrderDO : PayRefundDO = 1 : n
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@TableName("pay_refund")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 退款单编号,数据库自增
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 商户编号
|
||||
*
|
||||
* 关联 {@link PayMerchantDO#getId()}
|
||||
*/
|
||||
private Long merchantId;
|
||||
/**
|
||||
* 应用编号
|
||||
*
|
||||
* 关联 {@link PayAppDO#getId()}
|
||||
*/
|
||||
private Long appId;
|
||||
/**
|
||||
* 渠道编号
|
||||
*
|
||||
* 关联 {@link PayChannelDO#getId()}
|
||||
*/
|
||||
private Long channelId;
|
||||
/**
|
||||
* 商户编码
|
||||
*
|
||||
* 枚举 {@link PayChannelEnum}
|
||||
*/
|
||||
private String channelCode;
|
||||
/**
|
||||
* 订单编号
|
||||
*
|
||||
* 关联 {@link PayOrderDO#getId()}
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 交易订单号,根据规则生成
|
||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
||||
* 1. 调用微信支付 https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 时,使用该字段作为 out_trade_no
|
||||
* 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
|
||||
* 这里对应 pay_extension 里面的 no
|
||||
* 例如说,P202110132239124200055
|
||||
*/
|
||||
private String tradeNo;
|
||||
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
private String merchantOrderId;
|
||||
|
||||
/**
|
||||
* 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。
|
||||
* 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
|
||||
* 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等
|
||||
* 使用商户退款单,作为退款请求号
|
||||
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml 中的 out_refund_no
|
||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
|
||||
* 退款请求号。
|
||||
* 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
|
||||
* 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
|
||||
* 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
|
||||
* 退款单请求号,根据规则生成
|
||||
* 例如说,R202109181134287570000
|
||||
*/
|
||||
// TODO @jason:merchantRefundNo =》merchantRefundOId
|
||||
private String merchantRefundNo;
|
||||
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
|
||||
/**
|
||||
* 通知商户退款结果的回调状态
|
||||
* TODO 0 未发送 1 已发送
|
||||
*/
|
||||
private Integer notifyStatus;
|
||||
|
||||
// ========== 退款相关字段 ==========
|
||||
/**
|
||||
* 退款状态
|
||||
*
|
||||
* 枚举 {@link PayRefundStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 退款类型(部分退款,全部退款)
|
||||
*
|
||||
* 枚举 {@link PayRefundTypeEnum}
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
private Long payAmount;
|
||||
/**
|
||||
* 退款金额,单位:分
|
||||
*/
|
||||
private Long refundAmount;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
|
||||
// ========== 渠道相关字段 ==========
|
||||
/**
|
||||
* 渠道订单号,pay_order 中的channel_order_no 对应
|
||||
*/
|
||||
private String channelOrderNo;
|
||||
/**
|
||||
* 微信中的 refund_id
|
||||
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
|
||||
* 支付宝没有
|
||||
* 渠道退款单号,渠道返回
|
||||
*/
|
||||
private String channelRefundNo;
|
||||
|
||||
/**
|
||||
* 调用渠道的错误码
|
||||
*/
|
||||
private String channelErrorCode;
|
||||
|
||||
/**
|
||||
* 调用渠道报错时,错误信息
|
||||
*/
|
||||
private String channelErrorMsg;
|
||||
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
* 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
|
||||
*/
|
||||
private String channelExtras;
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* 退款失效时间
|
||||
*/
|
||||
private Date expireTime;
|
||||
/**
|
||||
* 退款成功时间
|
||||
*/
|
||||
private Date successTime;
|
||||
/**
|
||||
* 退款通知时间
|
||||
*/
|
||||
private Date notifyTime;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayAppCoreMapper extends BaseMapperX<PayAppDO> {
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Mapper
|
||||
public interface PayChannelCoreMapper extends BaseMapperX<PayChannelDO> {
|
||||
|
||||
default PayChannelDO selectByAppIdAndCode(Long appId, String code) {
|
||||
return selectOne(PayChannelDO::getAppId, appId, PayChannelDO::getCode, code);
|
||||
}
|
||||
|
||||
@Select("SELECT id FROM pay_channel WHERE update_time > #{maxUpdateTime} LIMIT 1")
|
||||
Long selectExistsByUpdateTimeAfter(Date maxUpdateTime);
|
||||
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyLogDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyLogCoreMapper extends BaseMapperX<PayNotifyLogDO> {
|
||||
}
|
@@ -1,30 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.notify;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> {
|
||||
|
||||
/**
|
||||
* 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件:
|
||||
*
|
||||
* 1. status 非成功
|
||||
* 2. nextNotifyTime 小于当前时间
|
||||
*
|
||||
* @return PayTransactionNotifyTaskDO 数组
|
||||
*/
|
||||
default List<PayNotifyTaskDO> selectListByNotify() {
|
||||
return selectList(new QueryWrapper<PayNotifyTaskDO>()
|
||||
.in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(),
|
||||
PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
|
||||
.le("next_notify_time", new Date()));
|
||||
}
|
||||
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
|
||||
|
||||
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.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayOrderCoreMapper extends BaseMapperX<PayOrderDO> {
|
||||
|
||||
default PayOrderDO selectByAppIdAndMerchantOrderId(Long appId, String merchantOrderId) {
|
||||
return selectOne(new QueryWrapper<PayOrderDO>().eq("app_id", appId)
|
||||
.eq("merchant_order_id", merchantOrderId));
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer status, PayOrderDO update) {
|
||||
return update(update, new QueryWrapper<PayOrderDO>()
|
||||
.eq("id", id).eq("status", status));
|
||||
}
|
||||
|
||||
}
|
@@ -1,20 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderExtensionDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface PayOrderExtensionCoreMapper extends BaseMapperX<PayOrderExtensionDO> {
|
||||
|
||||
default PayOrderExtensionDO selectByNo(String no) {
|
||||
return selectOne(PayOrderExtensionDO::getNo, no);
|
||||
}
|
||||
|
||||
default int updateByIdAndStatus(Long id, Integer status, PayOrderExtensionDO update) {
|
||||
return update(update, new LambdaQueryWrapper<PayOrderExtensionDO>()
|
||||
.eq(PayOrderExtensionDO::getId, id).eq(PayOrderExtensionDO::getStatus, status));
|
||||
}
|
||||
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayRefundDO;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
|
||||
/**
|
||||
* 退款订单 Mapper
|
||||
*
|
||||
*/
|
||||
@Mapper
|
||||
public interface PayRefundCoreMapper extends BaseMapperX<PayRefundDO> {
|
||||
|
||||
default PayRefundDO selectByReqNo(String reqNo) {
|
||||
return selectOne("req_no", reqNo);
|
||||
}
|
||||
|
||||
default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
|
||||
return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
|
||||
}
|
||||
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import org.redisson.api.RLock;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.HASH;
|
||||
|
||||
/**
|
||||
* Lock4j Redis Key 枚举类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayRedisKeyCoreConstants {
|
||||
|
||||
RedisKeyDefine PAY_NOTIFY_LOCK = new RedisKeyDefine("通知任务的分布式锁",
|
||||
"pay_notify:lock:", // 参数来自 DefaultLockKeyBuilder 类
|
||||
HASH, RLock.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); // Redisson 的 Lock 锁,使用 Hash 数据结构
|
||||
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.dal.redis.notify;
|
||||
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.iocoder.yudao.coreservice.modules.pay.dal.redis.PayRedisKeyCoreConstants.PAY_NOTIFY_LOCK;
|
||||
|
||||
/**
|
||||
* 支付通知的锁 Redis DAO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Repository
|
||||
public class PayNotifyLockCoreRedisDAO {
|
||||
|
||||
@Resource
|
||||
private RedissonClient redissonClient;
|
||||
|
||||
public void lock(Long id, Long timeoutMillis, Runnable runnable) {
|
||||
String lockKey = formatKey(id);
|
||||
RLock lock = redissonClient.getLock(lockKey);
|
||||
try {
|
||||
lock.lock(timeoutMillis, TimeUnit.MILLISECONDS);
|
||||
// 执行逻辑
|
||||
runnable.run();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatKey(Long id) {
|
||||
return String.format(PAY_NOTIFY_LOCK.getKeyTemplate(), id);
|
||||
}
|
||||
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.enums;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* Pay 错误码 Core 枚举类
|
||||
*
|
||||
* pay 系统,使用 1-007-000-000 段
|
||||
*/
|
||||
public interface PayErrorCodeCoreConstants {
|
||||
|
||||
/**
|
||||
* ========== APP 模块 1-007-000-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_APP_NOT_FOUND = new ErrorCode(1007000000, "App 不存在");
|
||||
ErrorCode PAY_APP_IS_DISABLE = new ErrorCode(1007000002, "App 已经被禁用");
|
||||
ErrorCode PAY_APP_EXIST_TRANSACTION_ORDER_CANT_DELETE = new ErrorCode(1007000003, "支付应用存在交易中的订单,无法删除");
|
||||
|
||||
/**
|
||||
* ========== CHANNEL 模块 1-007-001-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_CHANNEL_NOT_FOUND = new ErrorCode(1007001000, "支付渠道的配置不存在");
|
||||
ErrorCode PAY_CHANNEL_IS_DISABLE = new ErrorCode(1007001001, "支付渠道已经禁用");
|
||||
ErrorCode PAY_CHANNEL_CLIENT_NOT_FOUND = new ErrorCode(1007001002, "支付渠道的客户端不存在");
|
||||
ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在");
|
||||
ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道");
|
||||
ErrorCode CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v2版本中商户密钥不可为空");
|
||||
ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001007,"微信渠道v3版本apiclient_key.pem不可为空");
|
||||
ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001008,"微信渠道v3版本中apiclient_cert.pem不可为空");
|
||||
ErrorCode PAY_CHANNEL_NOTIFY_VERIFY_FAILED = new ErrorCode(1007001009, "渠道通知校验失败");
|
||||
/**
|
||||
* ========== ORDER 模块 1-007-002-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_ORDER_NOT_FOUND = new ErrorCode(1007002000, "支付订单不存在");
|
||||
ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1007002001, "支付订单不处于待支付");
|
||||
ErrorCode PAY_ORDER_STATUS_IS_NOT_SUCCESS = new ErrorCode(1007002002, "支付订单不处于已支付");
|
||||
ErrorCode PAY_ORDER_ERROR_USER = new ErrorCode(1007002003, "支付订单用户不正确");
|
||||
|
||||
/**
|
||||
* ========== ORDER 模块(拓展单) 1-007-003-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_ORDER_EXTENSION_NOT_FOUND = new ErrorCode(1007003000, "支付交易拓展单不存在");
|
||||
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_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
|
||||
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
|
||||
|
||||
|
||||
/**
|
||||
* ========== 支付商户信息 1-007-004-000 ==========
|
||||
*/
|
||||
ErrorCode PAY_MERCHANT_NOT_EXISTS = new ErrorCode(1007004000, "支付商户信息不存在");
|
||||
ErrorCode PAY_MERCHANT_EXIST_APP_CANT_DELETE = new ErrorCode(1007004001, "支付商户存在支付应用,无法删除");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.enums.notify;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付通知状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayNotifyStatusEnum {
|
||||
|
||||
WAITING(1, "等待通知"),
|
||||
SUCCESS(2, "通知成功"),
|
||||
FAILURE(3, "通知失败"), // 多次尝试,彻底失败
|
||||
REQUEST_SUCCESS(4, "请求成功,但是结果失败"),
|
||||
REQUEST_FAILURE(5, "请求失败"),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.enums.notify;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付通知类型
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayNotifyTypeEnum {
|
||||
|
||||
ORDER(1, "支付单"),
|
||||
REFUND(2, "退款单"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付订单的通知状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayOrderNotifyStatusEnum implements IntArrayValuable {
|
||||
|
||||
NO(0, "未通知"),
|
||||
SUCCESS(10, "通知成功"),
|
||||
FAILURE(20, "通知失败")
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付订单的状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayOrderStatusEnum implements IntArrayValuable {
|
||||
|
||||
WAITING(0, "未支付"),
|
||||
SUCCESS(10, "支付成功"),
|
||||
CLOSED(20, "支付关闭"), // 未付款交易超时关闭,或支付完成后全额退款 TODO 芋艿:需要优化下
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
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, "退款失败"),
|
||||
CLOSE(99, "退款关闭");
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.enums.order;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付订单的退款状态枚举
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayRefundTypeEnum implements IntArrayValuable {
|
||||
|
||||
NO(0, "未退款"),
|
||||
SOME(10, "部分退款"),
|
||||
ALL(20, "全部退款")
|
||||
;
|
||||
|
||||
private final Integer status;
|
||||
private final String name;
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* pay 包下,我们放支付业务,提供业务的支付能力。
|
||||
* 例如说:商户、应用、支付、退款等等
|
||||
*
|
||||
* 缩写:pay
|
||||
*/
|
||||
package cn.iocoder.yudao.coreservice.modules.pay;
|
@@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.merchant;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
|
||||
/**
|
||||
* 支付应用 Core Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayAppCoreService {
|
||||
|
||||
/**
|
||||
* 支付应用的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link ServiceException} 业务异常
|
||||
*
|
||||
* @param id 应用编号
|
||||
* @return 应用信息
|
||||
*/
|
||||
PayAppDO validPayApp(Long id);
|
||||
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.merchant;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
|
||||
/**
|
||||
* 支付渠道 Core Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayChannelCoreService {
|
||||
|
||||
/**
|
||||
* 初始化支付客户端
|
||||
*/
|
||||
void initPayClients();
|
||||
|
||||
/**
|
||||
* 支付渠道的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link ServiceException} 业务异常
|
||||
*
|
||||
* @param id 渠道编号
|
||||
* @return 渠道信息
|
||||
*/
|
||||
PayChannelDO validPayChannel(Long id);
|
||||
|
||||
/**
|
||||
* 支付渠道的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link ServiceException} 业务异常
|
||||
*
|
||||
* @param appId 应用编号
|
||||
* @param code 支付渠道
|
||||
* @return 渠道信息
|
||||
*/
|
||||
PayChannelDO validPayChannel(Long appId, String code);
|
||||
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayAppDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant.PayAppCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayAppCoreService;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 支付应用 Core Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Valid
|
||||
@Slf4j
|
||||
public class PayAppCoreServiceImpl implements PayAppCoreService {
|
||||
|
||||
@Resource
|
||||
private PayAppCoreMapper payAppCoreMapper;
|
||||
|
||||
@Override
|
||||
public PayAppDO validPayApp(Long id) {
|
||||
PayAppDO app = payAppCoreMapper.selectById(id);
|
||||
// 校验是否存在
|
||||
if (app == null) {
|
||||
throw exception(PAY_APP_NOT_FOUND);
|
||||
}
|
||||
// 校验是否禁用
|
||||
if (CommonStatusEnum.DISABLE.getStatus().equals(app.getStatus())) {
|
||||
throw exception(PAY_APP_IS_DISABLE);
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
}
|
@@ -1,121 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.merchant.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.merchant.PayChannelCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 支付渠道 Core Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Valid
|
||||
@Slf4j
|
||||
public class PayChannelCoreServiceImpl implements PayChannelCoreService {
|
||||
|
||||
/**
|
||||
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
|
||||
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
|
||||
*/
|
||||
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* 缓存菜单的最大更新时间,用于后续的增量轮询,判断是否有更新
|
||||
*/
|
||||
private volatile Date maxUpdateTime;
|
||||
|
||||
@Resource
|
||||
private PayChannelCoreMapper payChannelCoreMapper;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initPayClients() {
|
||||
// 获取支付渠道,如果有更新
|
||||
List<PayChannelDO> payChannels = this.loadPayChannelIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(payChannels)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建或更新支付 Client
|
||||
payChannels.forEach(payChannel -> payClientFactory.createOrUpdatePayClient(payChannel.getId(),
|
||||
payChannel.getCode(), payChannel.getConfig()));
|
||||
|
||||
// 写入缓存
|
||||
assert payChannels.size() > 0; // 断言,避免告警
|
||||
maxUpdateTime = payChannels.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime();
|
||||
log.info("[initPayClients][初始化 PayChannel 数量为 {}]", payChannels.size());
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
initPayClients();
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果支付渠道发生变化,从数据库中获取最新的全量支付渠道。
|
||||
* 如果未发生变化,则返回空
|
||||
*
|
||||
* @param maxUpdateTime 当前支付渠道的最大更新时间
|
||||
* @return 支付渠道列表
|
||||
*/
|
||||
private List<PayChannelDO> loadPayChannelIfUpdate(Date maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadPayChannelIfUpdate][首次加载全量支付渠道]");
|
||||
} else { // 判断数据库中是否有更新的支付渠道
|
||||
if (payChannelCoreMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadPayChannelIfUpdate][增量加载全量支付渠道]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有支付渠道
|
||||
return payChannelCoreMapper.selectList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelDO validPayChannel(Long id) {
|
||||
PayChannelDO channel = payChannelCoreMapper.selectById(id);
|
||||
this.validPayChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayChannelDO validPayChannel(Long appId, String code) {
|
||||
PayChannelDO channel = payChannelCoreMapper.selectByAppIdAndCode(appId, code);
|
||||
this.validPayChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
|
||||
private void validPayChannel(PayChannelDO channel) {
|
||||
if (channel == null) {
|
||||
throw exception(PAY_CHANNEL_NOT_FOUND);
|
||||
}
|
||||
if (CommonStatusEnum.DISABLE.getStatus().equals(channel.getStatus())) {
|
||||
throw exception(PayErrorCodeCoreConstants.PAY_CHANNEL_IS_DISABLE);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.notify;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 支付通知 Core Service 接口
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayNotifyCoreService {
|
||||
|
||||
/**
|
||||
* 创建支付通知任务
|
||||
*
|
||||
* @param reqDTO 任务信息
|
||||
*/
|
||||
void createPayNotifyTask(@Valid PayNotifyTaskCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 执行支付通知
|
||||
*
|
||||
* 注意,该方法提供给定时任务调用。目前是 yudao-admin-server 进行调用
|
||||
* @return 通知数量
|
||||
*/
|
||||
int executeNotify() throws InterruptedException;
|
||||
|
||||
}
|
@@ -1,32 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 支付通知创建 DTO
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayNotifyTaskCreateReqDTO {
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
@NotNull(message = "类型不能为空")
|
||||
private Integer type;
|
||||
/**
|
||||
* 数据编号
|
||||
*/
|
||||
@NotNull(message = "数据编号不能为空")
|
||||
private Long dataId;
|
||||
|
||||
}
|
@@ -1,263 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.notify.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
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.PayRefundCoreMapper;
|
||||
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;
|
||||
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.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.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
|
||||
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.SECOND_MILLIS;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* 支付通知 Core Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Valid
|
||||
@Slf4j
|
||||
public class PayNotifyCoreServiceImpl implements PayNotifyCoreService {
|
||||
|
||||
/**
|
||||
* 通知超时时间,单位:秒
|
||||
*/
|
||||
public static final int NOTIFY_TIMEOUT = 120;
|
||||
/**
|
||||
* {@link #NOTIFY_TIMEOUT} 的毫秒
|
||||
*/
|
||||
public static final long NOTIFY_TIMEOUT_MILLIS = 120 * SECOND_MILLIS;
|
||||
|
||||
@Resource
|
||||
@Lazy // 循环依赖,避免报错
|
||||
private PayOrderCoreService payOrderCoreService;
|
||||
|
||||
@Resource
|
||||
private PayNotifyTaskCoreMapper payNotifyTaskCoreMapper;
|
||||
@Resource
|
||||
private PayNotifyLogCoreMapper payNotifyLogCoreMapper;
|
||||
|
||||
@Resource
|
||||
private ThreadPoolTaskExecutor threadPoolTaskExecutor; // TODO 芋艿:未来提供独立的线程池
|
||||
|
||||
@Resource
|
||||
private PayNotifyLockCoreRedisDAO payNotifyLockCoreRedisDAO;
|
||||
|
||||
@Resource
|
||||
private PayRefundCoreMapper payRefundCoreMapper;
|
||||
|
||||
@Resource
|
||||
@Lazy // 循环依赖(自己依赖自己),避免报错
|
||||
private PayNotifyCoreServiceImpl self;
|
||||
|
||||
@Override
|
||||
public void createPayNotifyTask(PayNotifyTaskCreateReqDTO reqDTO) {
|
||||
PayNotifyTaskDO task = new PayNotifyTaskDO();
|
||||
task.setType(reqDTO.getType()).setDataId(reqDTO.getDataId());
|
||||
task.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setNextNotifyTime(new Date())
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1);
|
||||
// 补充 merchantId + appId + notifyUrl 字段
|
||||
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
||||
PayOrderDO order = payOrderCoreService.getPayOrder(task.getDataId()); // 不进行非空判断,有问题直接异常
|
||||
task.setMerchantId(order.getMerchantId()).setAppId(order.getAppId()).
|
||||
setMerchantOrderId(order.getMerchantOrderId()).setNotifyUrl(order.getNotifyUrl());
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
// TODO 芋艿,需要实现下哈
|
||||
PayRefundDO refundDO = payRefundCoreMapper.selectById(task.getDataId());
|
||||
task.setMerchantId(refundDO.getMerchantId()).setAppId(refundDO.getAppId())
|
||||
.setMerchantOrderId(refundDO.getMerchantOrderId()).setNotifyUrl(refundDO.getNotifyUrl());
|
||||
}
|
||||
|
||||
// 执行插入
|
||||
payNotifyTaskCoreMapper.insert(task);
|
||||
|
||||
// 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
|
||||
self.executeNotifyAsync(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNotify() throws InterruptedException {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
|
||||
if (CollUtil.isEmpty(tasks)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 遍历,逐个通知
|
||||
CountDownLatch latch = new CountDownLatch(tasks.size());
|
||||
tasks.forEach(task -> threadPoolTaskExecutor.execute(() -> {
|
||||
try {
|
||||
executeNotifySync(task);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}));
|
||||
// 等待完成
|
||||
this.awaitExecuteNotify(latch);
|
||||
// 返回执行完成的任务数(成功 + 失败)
|
||||
return tasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待全部支付通知的完成
|
||||
* 每 1 秒会打印一次剩余任务数量
|
||||
*
|
||||
* @param latch Latch
|
||||
* @throws InterruptedException 如果被打断
|
||||
*/
|
||||
private void awaitExecuteNotify(CountDownLatch latch) throws InterruptedException {
|
||||
long size = latch.getCount();
|
||||
for (int i = 0; i < NOTIFY_TIMEOUT; i++) {
|
||||
if (latch.await(1L, TimeUnit.SECONDS)) {
|
||||
return;
|
||||
}
|
||||
log.info("[awaitExecuteNotify][任务处理中, 总任务数({}) 剩余任务数({})]", size, latch.getCount());
|
||||
}
|
||||
log.error("[awaitExecuteNotify][任务未处理完,总任务数({}) 剩余任务数({})]", size, latch.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步执行单个支付通知
|
||||
*
|
||||
* @param task 通知任务
|
||||
*/
|
||||
@Async
|
||||
public void executeNotifyAsync(PayNotifyTaskDO task) {
|
||||
self.executeNotifySync(task); // 使用 self,避免事务不发起
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步执行单个支付通知
|
||||
*
|
||||
* @param task 通知任务
|
||||
*/
|
||||
public void executeNotifySync(PayNotifyTaskDO task) {
|
||||
// 分布式锁,避免并发问题
|
||||
payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
|
||||
// 校验,当前任务是否已经被通知过
|
||||
// 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
|
||||
PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
|
||||
if (DateUtils.afterNow(dbTask.getNextNotifyTime())) {
|
||||
log.info("[executeNotify][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]", toJsonString(dbTask));
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行通知
|
||||
executeNotify(dbTask);
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void executeNotify(PayNotifyTaskDO task) {
|
||||
// 发起回调
|
||||
CommonResult<?> invokeResult = null;
|
||||
Throwable invokeException = null;
|
||||
try {
|
||||
invokeResult = executeNotifyInvoke(task);
|
||||
} catch (Throwable e) {
|
||||
invokeException = e;
|
||||
}
|
||||
|
||||
// 处理
|
||||
Integer newStatus = this.processNotifyResult(task, invokeResult, invokeException);
|
||||
|
||||
// 记录 PayNotifyLog 日志
|
||||
String response = invokeException != null ? getRootCauseMessage(invokeException) : toJsonString(invokeResult);
|
||||
payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
|
||||
.notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单个支付任务的 HTTP 调用
|
||||
*
|
||||
* @param task 通知任务
|
||||
* @return HTTP 响应
|
||||
*/
|
||||
private CommonResult<?> executeNotifyInvoke(PayNotifyTaskDO task) {
|
||||
// 拼接参数
|
||||
Object request;
|
||||
if (Objects.equals(task.getType(), PayNotifyTypeEnum.ORDER.getType())) {
|
||||
request = PayNotifyOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payOrderId(task.getDataId()).build();
|
||||
} else if (Objects.equals(task.getType(), PayNotifyTypeEnum.REFUND.getType())) {
|
||||
request = PayRefundOrderReqVO.builder().merchantOrderId(task.getMerchantOrderId())
|
||||
.payRefundId(task.getDataId()).build();
|
||||
} else {
|
||||
throw new RuntimeException("未知的通知任务类型:" + toJsonString(task));
|
||||
}
|
||||
// 请求地址
|
||||
String response = HttpUtil.post(task.getNotifyUrl(), toJsonString(request),
|
||||
(int) NOTIFY_TIMEOUT_MILLIS);
|
||||
// 解析结果
|
||||
return JsonUtils.parseObject(response, CommonResult.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理并更新通知结果
|
||||
*
|
||||
* @param task 通知任务
|
||||
* @param invokeResult 通知结果
|
||||
* @param invokeException 通知异常
|
||||
* @return 最终任务的状态
|
||||
*/
|
||||
private Integer processNotifyResult(PayNotifyTaskDO task, CommonResult<?> invokeResult, Throwable invokeException) {
|
||||
// 设置通用的更新 PayNotifyTaskDO 的字段
|
||||
PayNotifyTaskDO updateTask = new PayNotifyTaskDO()
|
||||
.setId(task.getId())
|
||||
.setLastExecuteTime(new Date())
|
||||
.setNotifyTimes(task.getNotifyTimes() + 1);
|
||||
|
||||
// 情况一:调用成功
|
||||
if (invokeResult != null && invokeResult.isSuccess()) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 情况二:调用失败、调用异常
|
||||
// 2.1 超过最大回调次数
|
||||
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
// 2.2 未超过最大回调次数
|
||||
updateTask.setNextNotifyTime(DateUtils.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||
updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
|
||||
: PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
|
||||
return updateTask.getStatus();
|
||||
}
|
||||
|
||||
private void processNotifySuccess(PayNotifyTaskDO task, PayNotifyTaskDO updateTask) {
|
||||
payNotifyTaskCoreMapper.updateById(updateTask);
|
||||
}
|
||||
|
||||
}
|
@@ -1,28 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel(value = "支付单的通知 Request VO", description = "业务方接入支付回调时,使用该 VO 对象")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayNotifyOrderReqVO {
|
||||
|
||||
@ApiModelProperty(value = "商户订单编号", required = true, example = "10")
|
||||
@NotEmpty(message = "商户订单号不能为空")
|
||||
private String merchantOrderId;
|
||||
|
||||
@ApiModelProperty(value = "支付订单编号", required = true, example = "20")
|
||||
@NotNull(message = "支付订单编号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@ApiModel(value = "退款单的通知 Request VO", description = "业务方接入退款回调时,使用该 VO 对象")
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundOrderReqVO {
|
||||
|
||||
@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;
|
||||
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 这里的 VO 包有点特殊,是提供给接入支付模块的业务,提供回调接口时,可以直接使用 VO
|
||||
*
|
||||
* 例如说,支付单的回调,使用
|
||||
*/
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.notify.vo;
|
@@ -1,50 +0,0 @@
|
||||
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.*;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* 支付订单 Core Service
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public interface PayOrderCoreService {
|
||||
|
||||
/**
|
||||
* 获得支付单
|
||||
*
|
||||
* @param id 支付单编号
|
||||
* @return 支付单
|
||||
*/
|
||||
PayOrderDO getPayOrder(Long id);
|
||||
|
||||
/**
|
||||
* 创建支付单
|
||||
*
|
||||
* @param reqDTO 创建请求
|
||||
* @return 支付单编号
|
||||
*/
|
||||
Long createPayOrder(@Valid PayOrderCreateReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 提交支付
|
||||
* 此时,会发起支付渠道的调用
|
||||
*
|
||||
* @param reqDTO 提交请求
|
||||
* @return 提交结果
|
||||
*/
|
||||
PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 通知支付单成功
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notifyData 通知数据
|
||||
*/
|
||||
void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
|
||||
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order;
|
||||
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
|
||||
/**
|
||||
* 退款单 Core Service
|
||||
*
|
||||
* @author jason
|
||||
*/
|
||||
public interface PayRefundCoreService {
|
||||
|
||||
/**
|
||||
* 提交退款申请
|
||||
*
|
||||
* @param reqDTO 退款申请信息
|
||||
* @return 退款申请返回信息
|
||||
*/
|
||||
PayRefundRespDTO submitRefundOrder(PayRefundReqDTO reqDTO);
|
||||
|
||||
/**
|
||||
* 渠道的退款通知
|
||||
*
|
||||
* @param channelId 渠道编号
|
||||
* @param notifyData 通知数据
|
||||
* @throws Exception 退款通知异常
|
||||
*/
|
||||
void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
|
||||
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付单创建 Request DTO
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderCreateReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
// ========== 商户相关字段 ==========
|
||||
|
||||
/**
|
||||
* 商户订单编号
|
||||
*/
|
||||
@NotEmpty(message = "商户订单编号不能为空")
|
||||
private String merchantOrderId;
|
||||
/**
|
||||
* 商品标题
|
||||
*/
|
||||
@NotEmpty(message = "商品标题不能为空")
|
||||
@Length(max = 32, message = "商品标题不能超过 32")
|
||||
private String subject;
|
||||
/**
|
||||
* 商品描述
|
||||
*/
|
||||
@NotEmpty(message = "商品描述信息不能为空")
|
||||
@Length(max = 128, message = "商品描述信息长度不能超过128")
|
||||
private String body;
|
||||
|
||||
// ========== 订单相关字段 ==========
|
||||
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
@NotNull(message = "支付金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
||||
private Integer amount;
|
||||
|
||||
/**
|
||||
* 支付过期时间
|
||||
*/
|
||||
@NotNull(message = "支付过期时间不能为空")
|
||||
private Date expireTime;
|
||||
|
||||
}
|
@@ -1,47 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 支付单提交 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayOrderSubmitReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotNull(message = "应用编号不能为空")
|
||||
private Long appId;
|
||||
|
||||
/**
|
||||
* 支付单编号
|
||||
*/
|
||||
@NotNull(message = "支付单编号不能为空")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 支付渠道
|
||||
*/
|
||||
@NotEmpty(message = "支付渠道不能为空")
|
||||
private String channelCode;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
@NotEmpty(message = "用户 IP 不能为空")
|
||||
private String userIp;
|
||||
|
||||
/**
|
||||
* 支付渠道的额外参数
|
||||
*/
|
||||
private Map<String, String> channelExtras;
|
||||
|
||||
}
|
@@ -1,23 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付单提交 Response DTO
|
||||
*/
|
||||
@Data
|
||||
public class PayOrderSubmitRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 支付拓展单的编号
|
||||
*/
|
||||
private Long extensionId;
|
||||
|
||||
/**
|
||||
* 调用支付渠道的响应结果
|
||||
*/
|
||||
private Object invokeResponse;
|
||||
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 退款申请单 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PayRefundReqDTO {
|
||||
|
||||
/**
|
||||
* 支付订单编号
|
||||
*/
|
||||
@NotNull(message = "支付订单编号不能为空")
|
||||
private Long payOrderId;
|
||||
|
||||
/**
|
||||
* 退款金额
|
||||
*/
|
||||
@NotNull(message = "退款金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零")
|
||||
private Long amount;
|
||||
|
||||
/**
|
||||
* 退款原因
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 商户退款订单号
|
||||
*/
|
||||
@NotEmpty(message = "商户退款订单号不能为空")
|
||||
private String merchantRefundId;
|
||||
|
||||
/**
|
||||
* 用户 IP
|
||||
*/
|
||||
private String userIp;
|
||||
}
|
@@ -1,24 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.dto;
|
||||
|
||||
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 PayRefundRespDTO {
|
||||
|
||||
/**
|
||||
* 支付退款单编号,自增
|
||||
*/
|
||||
private Long refundId;
|
||||
|
||||
}
|
@@ -1,255 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.convert.order.PayOrderCoreConvert;
|
||||
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.mysql.order.PayOrderCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.dal.mysql.order.PayOrderExtensionCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderStatusEnum;
|
||||
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.notify.PayNotifyCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
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.service.order.dto.PayOrderSubmitReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitRespDTO;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.*;
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
|
||||
/**
|
||||
* 支付订单 Core Service 实现类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayOrderCoreServiceImpl implements PayOrderCoreService {
|
||||
|
||||
@Resource
|
||||
private PayProperties payProperties;
|
||||
|
||||
@Resource
|
||||
private PayAppCoreService payAppCoreService;
|
||||
@Resource
|
||||
private PayChannelCoreService payChannelCoreService;
|
||||
@Resource
|
||||
private PayNotifyCoreService payNotifyCoreService;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
@Resource
|
||||
private PayOrderCoreMapper payOrderCoreMapper;
|
||||
@Resource
|
||||
private PayOrderExtensionCoreMapper payOrderExtensionCoreMapper;
|
||||
|
||||
@Override
|
||||
public PayOrderDO getPayOrder(Long id) {
|
||||
return payOrderCoreMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createPayOrder(PayOrderCreateReqDTO reqDTO) {
|
||||
// 校验 App
|
||||
PayAppDO app = payAppCoreService.validPayApp(reqDTO.getAppId());
|
||||
|
||||
// 查询对应的支付交易单是否已经存在。如果是,则直接返回
|
||||
PayOrderDO order = payOrderCoreMapper.selectByAppIdAndMerchantOrderId(
|
||||
reqDTO.getAppId(), reqDTO.getMerchantOrderId());
|
||||
if (order != null) {
|
||||
log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
|
||||
order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说,不会出现这个情况
|
||||
return app.getId();
|
||||
}
|
||||
|
||||
// 创建支付交易单
|
||||
order = PayOrderCoreConvert.INSTANCE.convert(reqDTO)
|
||||
.setMerchantId(app.getMerchantId()).setAppId(app.getId());
|
||||
// 商户相关字段
|
||||
order.setNotifyUrl(app.getPayNotifyUrl())
|
||||
.setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus());
|
||||
// 订单相关字段
|
||||
order.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
// 退款相关字段
|
||||
// todo @芋艿 创建支付的订单的退款状态枚举是不是有问题,应该是 PayRefundTypeEnum 吧 您这填写的是 PayOrderNotifyStatusEnum 回调状态枚举
|
||||
order.setRefundStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||
.setRefundTimes(0).setRefundAmount(0L);
|
||||
payOrderCoreMapper.insert(order);
|
||||
// 最终返回
|
||||
return order.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) {
|
||||
// 校验 App
|
||||
payAppCoreService.validPayApp(reqDTO.getAppId());
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = payChannelCoreService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[submitPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 获得 PayOrderDO ,并校验其是否存在
|
||||
PayOrderDO order = payOrderCoreMapper.selectById(reqDTO.getId());
|
||||
if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
|
||||
throw exception(PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 插入 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = PayOrderCoreConvert.INSTANCE.convert(reqDTO)
|
||||
.setOrderId(order.getId()).setNo(generateOrderExtensionNo())
|
||||
.setChannelId(channel.getId()).setChannelCode(channel.getCode())
|
||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus());
|
||||
payOrderExtensionCoreMapper.insert(orderExtension);
|
||||
|
||||
// 调用三方接口
|
||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderCoreConvert.INSTANCE.convert2(reqDTO);
|
||||
// 商户相关字段
|
||||
//TODO jason @芋艿 是否加一个属性 如tradeNo 支付订单号, 用这个merchantOrderId让人迷糊
|
||||
unifiedOrderReqDTO.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
||||
.setSubject(order.getSubject()).setBody(order.getBody())
|
||||
.setNotifyUrl(genChannelPayNotifyUrl(channel))
|
||||
.setReturnUrl(genChannelReturnUrl(channel));
|
||||
// 订单相关字段
|
||||
unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
|
||||
CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
|
||||
unifiedOrderResult.checkError();
|
||||
|
||||
// TODO 轮询三方接口,是否已经支付的任务
|
||||
// 返回成功
|
||||
return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId())
|
||||
.setInvokeResponse(unifiedOrderResult.getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付渠道的编码,生成支付渠道的返回地址
|
||||
* @param channel 支付渠道
|
||||
* @return 支付成功返回的地址。 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelReturnUrl(PayChannelDO channel) {
|
||||
return payProperties.getPayReturnUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据支付渠道的编码,生成支付渠道的回调地址
|
||||
*
|
||||
* @param channel 支付渠道
|
||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||
*/
|
||||
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
||||
//去掉channel code, 似乎没啥用, 用统一的回调地址
|
||||
return payProperties.getPayNotifyUrl() + "/" + channel.getId();
|
||||
}
|
||||
|
||||
private String generateOrderExtensionNo() {
|
||||
// wx
|
||||
// 2014
|
||||
// 10
|
||||
// 27
|
||||
// 20
|
||||
// 09
|
||||
// 39
|
||||
// 5522657
|
||||
// a690389285100
|
||||
// 目前的算法
|
||||
// 时间序列,年月日时分秒 14 位
|
||||
// 纯随机,6 位 TODO 芋艿:此处估计是会有问题的,后续在调整
|
||||
return DateUtil.format(new Date(), "yyyyMMddHHmmss") + // 时间序列
|
||||
RandomUtil.randomInt(100000, 999999) // 随机。为什么是这个范围,因为偷懒
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception {
|
||||
// TODO 芋艿,记录回调日志
|
||||
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = payChannelCoreService.validPayChannel(channelId);
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 解析支付结果
|
||||
PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
|
||||
|
||||
// TODO 芋艿,先最严格的校验。即使调用方重复调用,实际哪个订单已经被重复回调的支付,也返回 false 。也没问题,因为实际已经回调成功了。
|
||||
// 1.1 查询 PayOrderExtensionDO
|
||||
PayOrderExtensionDO orderExtension = payOrderExtensionCoreMapper.selectByNo(
|
||||
notifyRespDTO.getOrderExtensionNo());
|
||||
if (orderExtension == null) {
|
||||
throw exception(PAY_ORDER_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(orderExtension.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 1.2 更新 PayOrderExtensionDO
|
||||
//TODO 支付宝交易超时 TRADE_FINISHED 需要更新交易关闭
|
||||
int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(),
|
||||
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
|
||||
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[notifyPayOrder][支付拓展单({}) 更新为已支付]", orderExtension.getId());
|
||||
|
||||
// 2.1 判断 PayOrderDO 是否处于待支付
|
||||
PayOrderDO order = payOrderCoreMapper.selectById(orderExtension.getOrderId());
|
||||
if (order == null) {
|
||||
throw exception(PAY_ORDER_NOT_FOUND);
|
||||
}
|
||||
if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
|
||||
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 2.2 更新 PayOrderDO
|
||||
updateCounts = payOrderCoreMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channel.getCode())
|
||||
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
|
||||
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
|
||||
.notifyTime(new Date()).build());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[notifyPayOrder][支付订单({}) 更新为已支付]", order.getId());
|
||||
|
||||
// 3. 插入支付通知记录
|
||||
payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
|
||||
}
|
||||
|
||||
}
|
@@ -1,235 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.PayRefundCoreMapper;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.notify.PayNotifyTypeEnum;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.enums.order.PayOrderNotifyStatusEnum;
|
||||
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.enums.order.PayRefundTypeEnum;
|
||||
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.notify.PayNotifyCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundReqDTO;
|
||||
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayRefundRespDTO;
|
||||
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.PayCommonResult;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
|
||||
import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
|
||||
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.PayNotifyRefundStatusEnum;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
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 PayRefundCoreMapper payRefundCoreMapper;
|
||||
@Resource
|
||||
private PayOrderExtensionCoreMapper payOrderExtensionCoreMapper;
|
||||
|
||||
@Resource
|
||||
private PayAppCoreService payAppCoreService;
|
||||
@Resource
|
||||
private PayChannelCoreService payChannelCoreService;
|
||||
@Resource
|
||||
private PayNotifyCoreService payNotifyCoreService;
|
||||
|
||||
@Resource
|
||||
private PayClientFactory payClientFactory;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
|
||||
// 获得 PayOrderDO
|
||||
PayOrderDO order = payOrderCoreMapper.selectById(req.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(req, order);
|
||||
// 退款类型
|
||||
PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
|
||||
if (Objects.equals(req.getAmount(), order.getAmount())) {
|
||||
refundType = PayRefundTypeEnum.ALL;
|
||||
}
|
||||
PayOrderExtensionDO orderExtensionDO = payOrderExtensionCoreMapper.selectById(order.getSuccessExtensionId());
|
||||
PayRefundDO payRefundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId());
|
||||
if(Objects.nonNull(payRefundDO)){
|
||||
// 退款订单已经提交过。
|
||||
//TODO 校验相同退款单的金额
|
||||
// TODO @jason:咱要不封装一个 ObjectUtils.equalsAny
|
||||
if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
|
||||
|| Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) {
|
||||
//已成功退款
|
||||
throw exception(PAY_REFUND_SUCCEED);
|
||||
}
|
||||
//可以重复提交,保证 退款请求号 一致,由渠道保证幂等
|
||||
}else {
|
||||
// 成功,插入退款单 状态为生成.没有和渠道交互
|
||||
// TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
|
||||
payRefundDO = PayRefundDO.builder().channelOrderNo(order.getChannelOrderNo())
|
||||
.appId(order.getAppId())
|
||||
.channelOrderNo(order.getChannelOrderNo())
|
||||
.channelCode(order.getChannelCode())
|
||||
.channelId(order.getChannelId())
|
||||
.merchantId(order.getMerchantId())
|
||||
.orderId(order.getId())
|
||||
.merchantRefundNo(req.getMerchantRefundId())
|
||||
.notifyUrl(app.getRefundNotifyUrl())
|
||||
.payAmount(order.getAmount())
|
||||
.refundAmount(req.getAmount())
|
||||
.userIp(req.getUserIp())
|
||||
.merchantOrderId(order.getMerchantOrderId())
|
||||
.tradeNo(orderExtensionDO.getNo())
|
||||
.status(PayRefundStatusEnum.CREATE.getStatus())
|
||||
.reason(req.getReason())
|
||||
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||
.type(refundType.getStatus())
|
||||
.build();
|
||||
payRefundCoreMapper.insert(payRefundDO);
|
||||
}
|
||||
// TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
|
||||
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
|
||||
unifiedReqDTO.setUserIp(req.getUserIp())
|
||||
.setAmount(req.getAmount())
|
||||
.setChannelOrderNo(order.getChannelOrderNo())
|
||||
.setPayTradeNo(orderExtensionDO.getNo())
|
||||
.setMerchantRefundId(req.getMerchantRefundId())
|
||||
.setReason(req.getReason());
|
||||
// 向渠道发起退款申请
|
||||
PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO);
|
||||
// 检查是否失败,失败抛出业务异常。
|
||||
// TODO 渠道的异常记录。
|
||||
// TODO @jason:可以先打个 warn log 哈;
|
||||
refundUnifiedResult.checkError();
|
||||
// 成功在 退款回调中处理
|
||||
return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
|
||||
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
|
||||
// 校验支付渠道是否有效
|
||||
PayChannelDO channel = payChannelCoreService.validPayChannel(channelId);
|
||||
// 校验支付客户端是否正确初始化
|
||||
PayClient client = payClientFactory.getPayClient(channel.getId());
|
||||
if (client == null) {
|
||||
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
|
||||
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||
}
|
||||
// 解析渠道退款通知数据, 统一处理
|
||||
PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
|
||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
|
||||
payRefundSuccess(refundNotify);
|
||||
} else {
|
||||
//TODO 支付异常, 支付宝似乎没有支付异常的通知。
|
||||
// TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
|
||||
}
|
||||
}
|
||||
|
||||
private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
|
||||
// 校验退款单存在
|
||||
PayRefundDO refundDO = payRefundCoreMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
|
||||
if (refundDO == null) {
|
||||
log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
|
||||
throw exception(PAY_REFUND_NOT_FOUND);
|
||||
}
|
||||
|
||||
// 得到已退金额
|
||||
PayOrderDO payOrderDO = payOrderCoreMapper.selectById(refundDO.getOrderId());
|
||||
Long refundedAmount = payOrderDO.getRefundAmount();
|
||||
|
||||
PayOrderStatusEnum orderStatus = PayOrderStatusEnum.SUCCESS;
|
||||
if(Objects.equals(payOrderDO.getAmount(), refundedAmount+ refundDO.getRefundAmount())){
|
||||
//支付金额 = 已退金额 + 本次退款金额。
|
||||
orderStatus = PayOrderStatusEnum.CLOSED;
|
||||
}
|
||||
// 更新支付订单
|
||||
PayOrderDO updateOrderDO = new PayOrderDO();
|
||||
updateOrderDO.setId(refundDO.getOrderId())
|
||||
.setRefundAmount(refundedAmount + refundDO.getRefundAmount())
|
||||
.setStatus(orderStatus.getStatus())
|
||||
.setRefundTimes(payOrderDO.getRefundTimes() + 1)
|
||||
.setRefundStatus(refundDO.getType());
|
||||
payOrderCoreMapper.updateById(updateOrderDO);
|
||||
|
||||
// 更新退款订单
|
||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||
updateRefundDO.setId(refundDO.getId())
|
||||
.setSuccessTime(refundNotify.getRefundSuccessTime())
|
||||
.setChannelRefundNo(refundNotify.getChannelOrderNo())
|
||||
.setTradeNo(refundNotify.getTradeNo())
|
||||
.setNotifyTime(new Date())
|
||||
.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
|
||||
payRefundCoreMapper.updateById(updateRefundDO);
|
||||
|
||||
// 插入退款通知记录
|
||||
// TODO 通知商户成功或者失败. 现在通知似乎没有实现, 只是回调
|
||||
payNotifyCoreService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
|
||||
.type(PayNotifyTypeEnum.REFUND.getType()).dataId(refundDO.getId()).build());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否进行退款
|
||||
* @param req 退款申请信息
|
||||
* @param order 原始支付订单信息
|
||||
*/
|
||||
private void validatePayRefund(PayRefundReqDTO req, 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 (req.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 退款的期限 退款次数的控制
|
||||
}
|
||||
|
||||
}
|
@@ -1 +0,0 @@
|
||||
package cn.iocoder.yudao.coreservice.modules.pay.service;
|
@@ -1,50 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user