mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-22 23:31:52 +08:00
mall + pay:
1. 重构支付回调的逻辑,将回调解析改成 PayOrderRespDTO,为后续轮询做铺垫 2. 调整退款单的表结构 3. 调整退款调用的实现
This commit is contained in:
parent
fbb63ee262
commit
518e89dc4b
@ -1,10 +1,12 @@
|
|||||||
package cn.iocoder.yudao.framework.common.util.validation;
|
package cn.iocoder.yudao.framework.common.util.validation;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import javax.validation.ConstraintViolation;
|
import javax.validation.ConstraintViolation;
|
||||||
import javax.validation.ConstraintViolationException;
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import javax.validation.Validation;
|
||||||
import javax.validation.Validator;
|
import javax.validation.Validator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
@ -37,6 +39,12 @@ public class ValidationUtils {
|
|||||||
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
&& PATTERN_XML_NCNAME.matcher(str).matches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validate(Object object, Class<?>... groups) {
|
||||||
|
Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
|
||||||
|
Assert.notNull(validator);
|
||||||
|
validate(validator, object, groups);
|
||||||
|
}
|
||||||
|
|
||||||
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
public static void validate(Validator validator, Object object, Class<?>... groups) {
|
||||||
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);
|
||||||
if (CollUtil.isNotEmpty(constraintViolations)) {
|
if (CollUtil.isNotEmpty(constraintViolations)) {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client;
|
package cn.iocoder.yudao.framework.pay.core.client;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能
|
* 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能
|
||||||
@ -32,20 +32,22 @@ public interface PayClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用支付渠道,进行退款
|
* 调用支付渠道,进行退款
|
||||||
|
*
|
||||||
* @param reqDTO 统一退款请求信息
|
* @param reqDTO 统一退款请求信息
|
||||||
* @return 各支付渠道的统一返回结果
|
* @return 退款信息
|
||||||
*/
|
*/
|
||||||
PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析回调数据
|
* 解析回调数据
|
||||||
*
|
*
|
||||||
* @param rawNotify 通知内容
|
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
||||||
|
* @param body HTTP 回调接口的 request body
|
||||||
* @return 回调对象
|
* @return 回调对象
|
||||||
* 1. {@link PayRefundNotifyRespDTO} 退款通知
|
* 1. {@link PayRefundRespDTO} 退款通知
|
||||||
* 2. {@link PayOrderNotifyRespDTO} 支付通知
|
* 2. {@link PayOrderRespDTO} 支付通知
|
||||||
*/
|
*/
|
||||||
default Object parseNotify(PayNotifyReqDTO rawNotify) {
|
default Object parseNotify(Map<String, String> params, String body) {
|
||||||
throw new UnsupportedOperationException("未实现 parseNotify 方法!");
|
throw new UnsupportedOperationException("未实现 parseNotify 方法!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
|
||||||
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付订单,退款订单回调,渠道的统一通知请求数据
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@ToString
|
|
||||||
@Builder
|
|
||||||
public class PayNotifyReqDTO {
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP 回调接口的 request body
|
|
||||||
*/
|
|
||||||
private String body;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
|
||||||
*/
|
|
||||||
private Map<String,String> params;
|
|
||||||
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付通知 Response DTO
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class PayOrderNotifyRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付订单号(支付模块的)
|
|
||||||
*/
|
|
||||||
private String orderExtensionNo;
|
|
||||||
/**
|
|
||||||
* 支付渠道编号
|
|
||||||
*/
|
|
||||||
private String channelOrderNo;
|
|
||||||
/**
|
|
||||||
* 支付渠道用户编号
|
|
||||||
*/
|
|
||||||
private String channelUserId;
|
|
||||||
/**
|
|
||||||
* 支付成功时间
|
|
||||||
*/
|
|
||||||
private LocalDateTime successTime;
|
|
||||||
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayNotifyRefundStatusEnum;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从渠道返回数据中解析得到的支付退款通知的Notify DTO
|
|
||||||
*
|
|
||||||
* @author jason
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@ToString
|
|
||||||
@Builder
|
|
||||||
public class PayRefundNotifyRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付渠道编号
|
|
||||||
*/
|
|
||||||
private String channelOrderNo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 交易订单号,根据规则生成
|
|
||||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
|
|
||||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_request_no
|
|
||||||
* 退款请求号。
|
|
||||||
* 标识一次退款请求,需要保证在交易号下唯一,如需部分退款,则此参数必传。
|
|
||||||
* 注:针对同一次退款请求,如果调用接口失败或异常了,重试时需要保证退款请求号不能变更,
|
|
||||||
* 防止该笔交易重复退款。支付宝会保证同样的退款请求号多次请求只会退一次。
|
|
||||||
* 退款单请求号,根据规则生成
|
|
||||||
*
|
|
||||||
* 例如说,RR202109181134287570000
|
|
||||||
*/
|
|
||||||
private String reqNo;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退款是否成功
|
|
||||||
*/
|
|
||||||
private PayNotifyRefundStatusEnum status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 退款成功时间
|
|
||||||
*/
|
|
||||||
private LocalDateTime refundSuccessTime;
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,55 @@
|
|||||||
|
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渠道支付订单 Response DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PayOrderRespDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付状态
|
||||||
|
*
|
||||||
|
* 枚举:{@link PayOrderStatusRespEnum}
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外部订单号
|
||||||
|
*
|
||||||
|
* 对应 PayOrderExtensionDO 的 no 字段
|
||||||
|
*/
|
||||||
|
private String outTradeNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付渠道编号
|
||||||
|
*/
|
||||||
|
private String channelOrderNo;
|
||||||
|
/**
|
||||||
|
* 支付渠道用户编号
|
||||||
|
*/
|
||||||
|
private String channelUserId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付成功时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime successTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始的异步通知结果
|
||||||
|
*/
|
||||||
|
private Object rawData;
|
||||||
|
|
||||||
|
}
|
@ -28,10 +28,12 @@ public class PayOrderUnifiedReqDTO {
|
|||||||
// ========== 商户相关字段 ==========
|
// ========== 商户相关字段 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商户订单编号
|
* 外部订单号
|
||||||
|
*
|
||||||
|
* 对应 PayOrderExtensionDO 的 no 字段
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "商户订单编号不能为空")
|
@NotEmpty(message = "外部订单编号不能为空")
|
||||||
private String merchantOrderId;
|
private String outTradeNo;
|
||||||
/**
|
/**
|
||||||
* 商品标题
|
* 商品标题
|
||||||
*/
|
*/
|
||||||
@ -63,7 +65,7 @@ public class PayOrderUnifiedReqDTO {
|
|||||||
*/
|
*/
|
||||||
@NotNull(message = "支付金额不能为空")
|
@NotNull(message = "支付金额不能为空")
|
||||||
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
||||||
private Integer amount;
|
private Integer price;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付过期时间
|
* 支付过期时间
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
package cn.iocoder.yudao.framework.pay.core.client.dto.order;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@ -24,11 +23,12 @@ public class PayOrderUnifiedRespDTO {
|
|||||||
private String displayContent;
|
private String displayContent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步的通知信息
|
* 渠道支付订单
|
||||||
*
|
*
|
||||||
|
* 只有在订单直接支付成功时,才会进行返回。
|
||||||
* 目前只有 bar 条码支付才会出现,它是支付发起时,直接返回是否支付成功的,而其它支付还是异步通知
|
* 目前只有 bar 条码支付才会出现,它是支付发起时,直接返回是否支付成功的,而其它支付还是异步通知
|
||||||
*/
|
*/
|
||||||
private PayOrderNotifyRespDTO notify;
|
private PayOrderRespDTO order;
|
||||||
|
|
||||||
public PayOrderUnifiedRespDTO(String displayMode, String displayContent) {
|
public PayOrderUnifiedRespDTO(String displayMode, String displayContent) {
|
||||||
this.displayMode = displayMode;
|
this.displayMode = displayMode;
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渠道退款订单 Response DTO
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PayRefundRespDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款状态
|
||||||
|
*
|
||||||
|
* 枚举 {@link PayRefundStatusRespEnum}
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渠道退款单号
|
||||||
|
*
|
||||||
|
* 对应 PayRefundDO.channelRefundNo 字段
|
||||||
|
*/
|
||||||
|
private String channelRefundNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款成功时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime successTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原始的异步通知结果
|
||||||
|
*/
|
||||||
|
private Object rawData;
|
||||||
|
|
||||||
|
}
|
@ -24,33 +24,20 @@ import javax.validation.constraints.NotNull;
|
|||||||
public class PayRefundUnifiedReqDTO {
|
public class PayRefundUnifiedReqDTO {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户 IP
|
* 外部订单号
|
||||||
|
*
|
||||||
|
* 对应 PayOrderExtensionDO 的 no 字段
|
||||||
*/
|
*/
|
||||||
private String userIp;
|
@NotEmpty(message = "外部订单编号不能为空")
|
||||||
|
private String outTradeNo;
|
||||||
// TODO @jason:这个是否为非必传字段呀,只需要传递 payTradeNo 字段即可。尽可能精简
|
|
||||||
/**
|
|
||||||
* 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字段} 和 渠道订单号 不能同时为空
|
* 对应 PayRefundDO 的 no 字段
|
||||||
*/
|
*/
|
||||||
private String payTradeNo;
|
@NotEmpty(message = "退款请求单号不能为空")
|
||||||
|
private String outRefundNo;
|
||||||
/**
|
|
||||||
* https://api.mch.weixin.qq.com/v3/refund/domestic/refunds 中的 out_refund_no
|
|
||||||
* https://opendocs.alipay.com/apis alipay.trade.refund 中的 out_trade_no
|
|
||||||
* 退款请求单号 同一退款请求单号多次请求只退一笔。
|
|
||||||
* 使用 商户的退款单号。{PayRefundDO 字段 merchantRefundNo}
|
|
||||||
*/
|
|
||||||
@NotEmpty(message = "退款请求单号")
|
|
||||||
private String merchantRefundId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退款原因
|
* 退款原因
|
||||||
@ -63,11 +50,12 @@ public class PayRefundUnifiedReqDTO {
|
|||||||
*/
|
*/
|
||||||
@NotNull(message = "退款金额不能为空")
|
@NotNull(message = "退款金额不能为空")
|
||||||
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
|
||||||
private Integer amount;
|
private Integer price;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退款结果 notify 回调地址, 支付宝退款不需要回调地址, 微信需要
|
* 退款结果的 notify 回调地址
|
||||||
*/
|
*/
|
||||||
|
@NotEmpty(message = "支付结果的回调地址不能为空")
|
||||||
@URL(message = "支付结果的 notify 回调地址必须是 URL 格式")
|
@URL(message = "支付结果的 notify 回调地址必须是 URL 格式")
|
||||||
private String notifyUrl;
|
private String notifyUrl;
|
||||||
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
/**
|
|
||||||
* 统一退款 Response DTO
|
|
||||||
*
|
|
||||||
* @author jason
|
|
||||||
*/
|
|
||||||
@Accessors(chain = true)
|
|
||||||
@Builder
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Data
|
|
||||||
public class PayRefundUnifiedRespDTO {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道退款单编号
|
|
||||||
*/
|
|
||||||
private String channelRefundId;
|
|
||||||
}
|
|
@ -5,8 +5,8 @@ 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.PayClientConfig;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
|
import cn.iocoder.yudao.framework.pay.core.client.exception.PayException;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@ -92,10 +92,10 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||||||
throws Throwable;
|
throws Throwable;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
public PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||||
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
|
Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
|
||||||
// 执行统一退款
|
// 执行统一退款
|
||||||
PayRefundUnifiedRespDTO resp;
|
PayRefundRespDTO resp;
|
||||||
try {
|
try {
|
||||||
resp = doUnifiedRefund(reqDTO);
|
resp = doUnifiedRefund(reqDTO);
|
||||||
} catch (ServiceException ex) {
|
} catch (ServiceException ex) {
|
||||||
@ -109,7 +109,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
|
|||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
protected abstract PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
|
||||||
|
|
||||||
// ========== 各种工具方法 ==========
|
// ========== 各种工具方法 ==========
|
||||||
|
|
||||||
|
@ -2,16 +2,19 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
|||||||
|
|
||||||
import cn.hutool.core.bean.BeanUtil;
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayNotifyRefundStatusEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||||
import com.alipay.api.*;
|
import com.alipay.api.AlipayApiException;
|
||||||
|
import com.alipay.api.AlipayConfig;
|
||||||
|
import com.alipay.api.AlipayResponse;
|
||||||
|
import com.alipay.api.DefaultAlipayClient;
|
||||||
import com.alipay.api.domain.AlipayTradeRefundModel;
|
import com.alipay.api.domain.AlipayTradeRefundModel;
|
||||||
import com.alipay.api.internal.util.AlipaySignature;
|
import com.alipay.api.internal.util.AlipaySignature;
|
||||||
import com.alipay.api.request.AlipayTradeRefundRequest;
|
import com.alipay.api.request.AlipayTradeRefundRequest;
|
||||||
@ -22,6 +25,8 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
|
||||||
@ -52,69 +57,72 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付宝统一的退款接口 alipay.trade.refund
|
* 支付宝统一的退款接口 alipay.trade.refund
|
||||||
|
*
|
||||||
* @param reqDTO 退款请求 request DTO
|
* @param reqDTO 退款请求 request DTO
|
||||||
* @return 退款请求 Response
|
* @return 退款请求 Response
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||||
AlipayTradeRefundModel model=new AlipayTradeRefundModel();
|
// 1.1 构建 AlipayTradeRefundModel 请求
|
||||||
model.setTradeNo(reqDTO.getChannelOrderNo());
|
AlipayTradeRefundModel model = new AlipayTradeRefundModel();
|
||||||
model.setOutTradeNo(reqDTO.getPayTradeNo());
|
model.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
|
model.setOutRequestNo(reqDTO.getOutRefundNo());
|
||||||
model.setOutRequestNo(reqDTO.getMerchantRefundId());
|
model.setRefundAmount(formatAmount(reqDTO.getPrice()));
|
||||||
model.setRefundAmount(formatAmount(reqDTO.getAmount()));
|
// model.setRefundAmount(formatAmount(reqDTO.getPrice() / 2));
|
||||||
model.setRefundReason(reqDTO.getReason());
|
model.setRefundReason(reqDTO.getReason());
|
||||||
|
// 1.2 构建 AlipayTradePayRequest 请求
|
||||||
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
|
AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
|
||||||
refundRequest.setBizModel(model);
|
request.setBizModel(model);
|
||||||
refundRequest.setNotifyUrl(reqDTO.getNotifyUrl());
|
|
||||||
refundRequest.setReturnUrl(reqDTO.getNotifyUrl());
|
|
||||||
try {
|
try {
|
||||||
AlipayTradeRefundResponse response = client.execute(refundRequest);
|
// 2.1 执行请求
|
||||||
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
|
AlipayTradeRefundResponse response = client.execute(request);
|
||||||
|
PayRefundRespDTO refund = new PayRefundRespDTO()
|
||||||
|
.setRawData(response);
|
||||||
|
// 支付宝只要退款调用返回 success,就认为退款成功,不需要回调。具体可见 parseNotify 方法的说明。
|
||||||
|
// 另外,支付宝没有退款单号,所以不用设置
|
||||||
if (response.isSuccess()) {
|
if (response.isSuccess()) {
|
||||||
//退款导致触发的异步通知是发送到支付接口中设置的notify_url
|
refund.setStatus(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||||
//支付宝不返回退款单号,设置为空
|
.setSuccessTime(LocalDateTimeUtil.of(response.getGmtRefundPay()));
|
||||||
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
|
Assert.notNull(refund.getSuccessTime(), "退款成功时间不能为空");
|
||||||
respDTO.setChannelRefundId("");
|
} else {
|
||||||
// return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping); TODO
|
refund.setStatus(PayOrderStatusRespEnum.CLOSED.getStatus());
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// 失败。需要抛出异常
|
return refund;
|
||||||
// return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping); TODO
|
|
||||||
return null;
|
|
||||||
} catch (AlipayApiException e) {
|
} catch (AlipayApiException e) {
|
||||||
// TODO 记录异常日志
|
log.error("[doUnifiedRefund][request({}) 发起退款异常]", toJsonString(reqDTO), e);
|
||||||
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
|
|
||||||
// return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); TODO
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public Object parseNotify(PayNotifyReqDTO rawNotify) {
|
public Object parseNotify(Map<String, String> params, String body) {
|
||||||
|
// 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
|
||||||
|
// ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
|
||||||
|
// ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有
|
||||||
|
// 所以,这里在解析时,即使是退款导致的订单状态同步,我们也忽略不做为“退款同步”,而是订单的回调。
|
||||||
|
// 实际上,支付宝退款只要发起成功,就可以认为退款成功,不需要等待回调。
|
||||||
|
|
||||||
// 1. 校验回调数据
|
// 1. 校验回调数据
|
||||||
String body = rawNotify.getBody();
|
|
||||||
Map<String, String> params = rawNotify.getParams();
|
|
||||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||||
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
||||||
StandardCharsets.UTF_8.name(), "RSA2");
|
StandardCharsets.UTF_8.name(), config.getSignType());
|
||||||
|
|
||||||
// 2.1 退款的情况
|
// 2. 解析订单的状态
|
||||||
if (bodyObj.containsKey("refund_fee")) {
|
String tradeStatus = bodyObj.get("trade_status");
|
||||||
return PayRefundNotifyRespDTO.builder().channelOrderNo(bodyObj.get("trade_no"))
|
PayOrderStatusRespEnum status = Objects.equals("WAIT_BUYER_PAY", tradeStatus) ? PayOrderStatusRespEnum.WAITING
|
||||||
.tradeNo(bodyObj.get("out_trade_no")).reqNo(bodyObj.get("out_biz_no"))
|
: Objects.equals("TRADE_SUCCESS", tradeStatus) ? PayOrderStatusRespEnum.SUCCESS
|
||||||
.status(PayNotifyRefundStatusEnum.SUCCESS)
|
: Objects.equals("TRADE_CLOSED", tradeStatus) ? PayOrderStatusRespEnum.CLOSED : null;
|
||||||
.refundSuccessTime(parseTime(params.get("gmt_refund")))
|
Assert.notNull(status, (Supplier<Throwable>) () -> {
|
||||||
.build();
|
throw new IllegalArgumentException(StrUtil.format("body({}) 的 trade_status 不正确", body));
|
||||||
}
|
});
|
||||||
// 2.2 支付的情况
|
return PayOrderRespDTO.builder()
|
||||||
return PayOrderNotifyRespDTO.builder()
|
.status(Objects.requireNonNull(status).getStatus())
|
||||||
.orderExtensionNo(bodyObj.get("out_trade_no"))
|
.outTradeNo(bodyObj.get("out_trade_no"))
|
||||||
.channelOrderNo(bodyObj.get("trade_no"))
|
.channelOrderNo(bodyObj.get("trade_no"))
|
||||||
.channelUserId(bodyObj.get("seller_id"))
|
.channelUserId(bodyObj.get("seller_id"))
|
||||||
.successTime(parseTime(params.get("notify_time")))
|
.successTime(parseTime(params.get("gmt_payment")))
|
||||||
|
.rawData(body)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,10 +31,10 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
|
|||||||
// 1.1 构建 AlipayTradeAppPayModel 请求
|
// 1.1 构建 AlipayTradeAppPayModel 请求
|
||||||
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
|
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
|
||||||
// ① 通用的参数
|
// ① 通用的参数
|
||||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
model.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
model.setSubject(reqDTO.getSubject());
|
model.setSubject(reqDTO.getSubject());
|
||||||
model.setBody(reqDTO.getBody());
|
model.setBody(reqDTO.getBody());
|
||||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
model.setTotalAmount(formatAmount(reqDTO.getPrice()));
|
||||||
model.setProductCode(" QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品
|
model.setProductCode(" QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品
|
||||||
// ② 个性化的参数【无】
|
// ② 个性化的参数【无】
|
||||||
// ③ 支付宝扫码支付只有一种展示
|
// ③ 支付宝扫码支付只有一种展示
|
||||||
|
@ -39,10 +39,10 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
|
|||||||
// 1.1 构建 AlipayTradePayModel 请求
|
// 1.1 构建 AlipayTradePayModel 请求
|
||||||
AlipayTradePayModel model = new AlipayTradePayModel();
|
AlipayTradePayModel model = new AlipayTradePayModel();
|
||||||
// ① 通用的参数
|
// ① 通用的参数
|
||||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
model.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
model.setSubject(reqDTO.getSubject());
|
model.setSubject(reqDTO.getSubject());
|
||||||
model.setBody(reqDTO.getBody());
|
model.setBody(reqDTO.getBody());
|
||||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
model.setTotalAmount(formatAmount(reqDTO.getPrice()));
|
||||||
model.setScene("bar_code"); // 当面付条码支付场景
|
model.setScene("bar_code"); // 当面付条码支付场景
|
||||||
// ② 个性化的参数
|
// ② 个性化的参数
|
||||||
model.setAuthCode(authCode);
|
model.setAuthCode(authCode);
|
||||||
|
@ -33,10 +33,10 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
|
|||||||
// 1.1 构建 AlipayTradePagePayModel 请求
|
// 1.1 构建 AlipayTradePagePayModel 请求
|
||||||
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
|
||||||
// ① 通用的参数
|
// ① 通用的参数
|
||||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
model.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
model.setSubject(reqDTO.getSubject());
|
model.setSubject(reqDTO.getSubject());
|
||||||
model.setBody(reqDTO.getBody());
|
model.setBody(reqDTO.getBody());
|
||||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
model.setTotalAmount(formatAmount(reqDTO.getPrice()));
|
||||||
model.setTimeExpire(formatTime(reqDTO.getExpireTime()));
|
model.setTimeExpire(formatTime(reqDTO.getExpireTime()));
|
||||||
model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY
|
model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY
|
||||||
// ② 个性化的参数
|
// ② 个性化的参数
|
||||||
|
@ -29,10 +29,10 @@ public class AlipayQrPayClient extends AbstractAlipayPayClient {
|
|||||||
// 1.1 构建 AlipayTradePrecreateModel 请求
|
// 1.1 构建 AlipayTradePrecreateModel 请求
|
||||||
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
|
||||||
// ① 通用的参数
|
// ① 通用的参数
|
||||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
model.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
model.setSubject(reqDTO.getSubject());
|
model.setSubject(reqDTO.getSubject());
|
||||||
model.setBody(reqDTO.getBody());
|
model.setBody(reqDTO.getBody());
|
||||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
model.setTotalAmount(formatAmount(reqDTO.getPrice()));
|
||||||
model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT
|
model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT
|
||||||
// ② 个性化的参数【无】
|
// ② 个性化的参数【无】
|
||||||
// ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
|
// ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
|
||||||
|
@ -30,10 +30,10 @@ public class AlipayWapPayClient extends AbstractAlipayPayClient {
|
|||||||
// 1.1 构建 AlipayTradeWapPayModel 请求
|
// 1.1 构建 AlipayTradeWapPayModel 请求
|
||||||
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
|
||||||
// ① 通用的参数
|
// ① 通用的参数
|
||||||
model.setOutTradeNo(reqDTO.getMerchantOrderId());
|
model.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
model.setSubject(reqDTO.getSubject());
|
model.setSubject(reqDTO.getSubject());
|
||||||
model.setBody(reqDTO.getBody());
|
model.setBody(reqDTO.getBody());
|
||||||
model.setTotalAmount(formatAmount(reqDTO.getAmount()));
|
model.setTotalAmount(formatAmount(reqDTO.getPrice()));
|
||||||
model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
|
model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
|
||||||
// ② 个性化的参数【无】
|
// ② 个性化的参数【无】
|
||||||
// ③ 支付宝 Wap 支付只有一种展示:URL
|
// ③ 支付宝 Wap 支付只有一种展示:URL
|
||||||
|
@ -6,8 +6,7 @@ import cn.hutool.core.date.TemporalAccessorUtil;
|
|||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||||
@ -22,11 +21,13 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
|
import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
|
||||||
import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN;
|
import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN;
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -103,47 +104,47 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object parseNotify(PayNotifyReqDTO rawNotify) {
|
public Object parseNotify(Map<String, String> params, String body) {
|
||||||
log.info("[parseNotify][微信支付回调 data 数据: {}]", rawNotify.getBody());
|
log.info("[parseNotify][微信支付回调 data 数据: {}]", body);
|
||||||
try {
|
try {
|
||||||
// 微信支付 v2 回调结果处理
|
// 微信支付 v2 回调结果处理
|
||||||
switch (config.getApiVersion()) {
|
switch (config.getApiVersion()) {
|
||||||
case WxPayClientConfig.API_VERSION_V2:
|
case WxPayClientConfig.API_VERSION_V2:
|
||||||
return parseOrderNotifyV2(rawNotify);
|
return parseOrderNotifyV2(body);
|
||||||
case WxPayClientConfig.API_VERSION_V3:
|
case WxPayClientConfig.API_VERSION_V3:
|
||||||
return parseOrderNotifyV3(rawNotify);
|
return parseOrderNotifyV3(body);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||||
}
|
}
|
||||||
} catch (WxPayException e) {
|
} catch (WxPayException e) {
|
||||||
log.error("[parseNotify][rawNotify({}) 解析失败]", toJsonString(rawNotify), e);
|
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
|
||||||
// throw buildPayException(e);
|
// throw buildPayException(e);
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
// TODO 芋艿:缺一个异常翻译
|
// TODO 芋艿:缺一个异常翻译
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
|
private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
|
||||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
|
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(body);
|
||||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
||||||
// 转换结果
|
// 转换结果
|
||||||
return PayOrderNotifyRespDTO
|
return PayOrderRespDTO
|
||||||
.builder()
|
.builder()
|
||||||
.orderExtensionNo(notifyResult.getOutTradeNo())
|
.outTradeNo(notifyResult.getOutTradeNo())
|
||||||
.channelOrderNo(notifyResult.getTransactionId())
|
.channelOrderNo(notifyResult.getTransactionId())
|
||||||
.channelUserId(notifyResult.getOpenid())
|
.channelUserId(notifyResult.getOpenid())
|
||||||
.successTime(parseDateV2(notifyResult.getTimeEnd()))
|
.successTime(parseDateV2(notifyResult.getTimeEnd()))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
|
private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
|
||||||
WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(data.getBody(), null);
|
WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(body, null);
|
||||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
|
WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
|
||||||
// 转换结果
|
// 转换结果
|
||||||
Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
|
Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
|
||||||
"支付结果非 SUCCESS");
|
"支付结果非 SUCCESS");
|
||||||
return PayOrderNotifyRespDTO.builder()
|
return PayOrderRespDTO.builder()
|
||||||
.orderExtensionNo(result.getOutTradeNo())
|
.outTradeNo(result.getOutTradeNo())
|
||||||
.channelOrderNo(result.getTradeState())
|
.channelOrderNo(result.getTradeState())
|
||||||
.channelUserId(result.getPayer() != null ? result.getPayer().getOpenid() : null)
|
.channelUserId(result.getPayer() != null ? result.getPayer().getOpenid() : null)
|
||||||
.successTime(parseDateV3(result.getSuccessTime()))
|
.successTime(parseDateV3(result.getSuccessTime()))
|
||||||
@ -175,7 +176,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
if (Objects.equals(e.getReturnCode(), "FAIL")) {
|
if (Objects.equals(e.getReturnCode(), "FAIL")) {
|
||||||
throw exception(PayFrameworkErrorCodeConstants.ORDER_UNIFIED_ERROR, e.getReturnMsg());
|
throw exception(PayFrameworkErrorCodeConstants.ORDER_UNIFIED_ERROR, e.getReturnMsg());
|
||||||
}
|
}
|
||||||
// 情况三:系统异常,这里暂时不打,交给上层的 AbstractPayClient 统一打
|
// 情况三:系统异常,这里暂时不打,交给上层的 AbstractPayClient 统一打日志
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||||
@ -21,7 +21,7 @@ public class WxAppPayClient extends AbstractWxPayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,13 +5,14 @@ import cn.hutool.core.thread.ThreadUtil;
|
|||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||||
import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest;
|
import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest;
|
||||||
import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
|
import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
|
||||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||||
@ -57,10 +58,10 @@ public class WxBarPayClient extends AbstractWxPayClient {
|
|||||||
}
|
}
|
||||||
// 构建 WxPayMicropayRequest 对象
|
// 构建 WxPayMicropayRequest 对象
|
||||||
WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder()
|
WxPayMicropayRequest request = WxPayMicropayRequest.newBuilder()
|
||||||
.outTradeNo(reqDTO.getMerchantOrderId())
|
.outTradeNo(reqDTO.getOutTradeNo())
|
||||||
.body(reqDTO.getSubject())
|
.body(reqDTO.getSubject())
|
||||||
.detail(reqDTO.getBody())
|
.detail(reqDTO.getBody())
|
||||||
.totalFee(reqDTO.getAmount()) // 单位分
|
.totalFee(reqDTO.getPrice()) // 单位分
|
||||||
.timeExpire(formatDateV2(expireTime))
|
.timeExpire(formatDateV2(expireTime))
|
||||||
.spbillCreateIp(reqDTO.getUserIp())
|
.spbillCreateIp(reqDTO.getUserIp())
|
||||||
.authCode(getAuthCode(reqDTO))
|
.authCode(getAuthCode(reqDTO))
|
||||||
@ -70,15 +71,17 @@ public class WxBarPayClient extends AbstractWxPayClient {
|
|||||||
try {
|
try {
|
||||||
WxPayMicropayResult response = client.micropay(request);
|
WxPayMicropayResult response = client.micropay(request);
|
||||||
// 支付成功(例如说,用户输入了密码)
|
// 支付成功(例如说,用户输入了密码)
|
||||||
PayOrderNotifyRespDTO notify = PayOrderNotifyRespDTO.builder()
|
PayOrderRespDTO order = PayOrderRespDTO.builder()
|
||||||
.orderExtensionNo(response.getOutTradeNo())
|
.status(PayOrderStatusRespEnum.SUCCESS.getStatus())
|
||||||
|
.outTradeNo(response.getOutTradeNo())
|
||||||
.channelOrderNo(response.getTransactionId())
|
.channelOrderNo(response.getTransactionId())
|
||||||
.channelUserId(response.getOpenid())
|
.channelUserId(response.getOpenid())
|
||||||
.successTime(parseDateV2(response.getTimeEnd()))
|
.successTime(parseDateV2(response.getTimeEnd()))
|
||||||
|
.rawData(response)
|
||||||
.build();
|
.build();
|
||||||
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.BAR_CODE.getMode(),
|
return new PayOrderUnifiedRespDTO(PayOrderDisplayModeEnum.BAR_CODE.getMode(),
|
||||||
JsonUtils.toJsonString(response))
|
JsonUtils.toJsonString(response))
|
||||||
.setNotify(notify);
|
.setOrder(order);
|
||||||
} catch (WxPayException ex) {
|
} catch (WxPayException ex) {
|
||||||
// 如果不满足这 3 种任一的,则直接抛出 WxPayException 异常,不仅需处理
|
// 如果不满足这 3 种任一的,则直接抛出 WxPayException 异常,不仅需处理
|
||||||
// 1. SYSTEMERROR:接口返回错误:请立即调用被扫订单结果查询API,查询当前订单状态,并根据订单的状态决定下一步的操作。
|
// 1. SYSTEMERROR:接口返回错误:请立即调用被扫订单结果查询API,查询当前订单状态,并根据订单的状态决定下一步的操作。
|
||||||
@ -102,7 +105,7 @@ public class WxBarPayClient extends AbstractWxPayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
||||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||||
@ -21,7 +21,7 @@ public class WxH5PayClient extends AbstractWxPayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||||
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
|
import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult;
|
||||||
@ -37,11 +37,11 @@ public class WxNativePayClient extends AbstractWxPayClient {
|
|||||||
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||||
// 构建 WxPayUnifiedOrderRequest 对象
|
// 构建 WxPayUnifiedOrderRequest 对象
|
||||||
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
||||||
.outTradeNo(reqDTO.getMerchantOrderId())
|
.outTradeNo(reqDTO.getOutTradeNo())
|
||||||
.body(reqDTO.getSubject())
|
.body(reqDTO.getSubject())
|
||||||
.detail(reqDTO.getBody())
|
.detail(reqDTO.getBody())
|
||||||
.totalFee(reqDTO.getAmount()) // 单位分
|
.totalFee(reqDTO.getPrice()) // 单位分
|
||||||
.productId(reqDTO.getMerchantOrderId())
|
.productId(reqDTO.getOutTradeNo())
|
||||||
.timeExpire(formatDateV2(reqDTO.getExpireTime()))
|
.timeExpire(formatDateV2(reqDTO.getExpireTime()))
|
||||||
.spbillCreateIp(reqDTO.getUserIp())
|
.spbillCreateIp(reqDTO.getUserIp())
|
||||||
.notifyUrl(reqDTO.getNotifyUrl())
|
.notifyUrl(reqDTO.getNotifyUrl())
|
||||||
@ -58,9 +58,9 @@ public class WxNativePayClient extends AbstractWxPayClient {
|
|||||||
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||||
// 构建 WxPayUnifiedOrderRequest 对象
|
// 构建 WxPayUnifiedOrderRequest 对象
|
||||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||||
request.setOutTradeNo(reqDTO.getMerchantOrderId());
|
request.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
request.setDescription(reqDTO.getBody());
|
request.setDescription(reqDTO.getBody());
|
||||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
|
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分
|
||||||
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
|
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
|
||||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||||
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
request.setNotifyUrl(reqDTO.getNotifyUrl());
|
||||||
@ -73,7 +73,7 @@ public class WxNativePayClient extends AbstractWxPayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,8 +5,8 @@ import cn.hutool.core.util.StrUtil;
|
|||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
|
||||||
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
|
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
|
||||||
@ -47,10 +47,10 @@ public class WxPubPayClient extends AbstractWxPayClient {
|
|||||||
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
protected PayOrderUnifiedRespDTO doUnifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||||
// 构建 WxPayUnifiedOrderRequest 对象
|
// 构建 WxPayUnifiedOrderRequest 对象
|
||||||
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
||||||
.outTradeNo(reqDTO.getMerchantOrderId())
|
.outTradeNo(reqDTO.getOutTradeNo())
|
||||||
.body(reqDTO.getSubject())
|
.body(reqDTO.getSubject())
|
||||||
.detail(reqDTO.getBody())
|
.detail(reqDTO.getBody())
|
||||||
.totalFee(reqDTO.getAmount()) // 单位分
|
.totalFee(reqDTO.getPrice()) // 单位分
|
||||||
.timeExpire(formatDateV2(reqDTO.getExpireTime()))
|
.timeExpire(formatDateV2(reqDTO.getExpireTime()))
|
||||||
.spbillCreateIp(reqDTO.getUserIp())
|
.spbillCreateIp(reqDTO.getUserIp())
|
||||||
.openid(getOpenid(reqDTO))
|
.openid(getOpenid(reqDTO))
|
||||||
@ -68,9 +68,9 @@ public class WxPubPayClient extends AbstractWxPayClient {
|
|||||||
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
protected PayOrderUnifiedRespDTO doUnifiedOrderV3(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||||
// 构建 WxPayUnifiedOrderRequest 对象
|
// 构建 WxPayUnifiedOrderRequest 对象
|
||||||
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
|
||||||
request.setOutTradeNo(reqDTO.getMerchantOrderId());
|
request.setOutTradeNo(reqDTO.getOutTradeNo());
|
||||||
request.setDescription(reqDTO.getSubject());
|
request.setDescription(reqDTO.getSubject());
|
||||||
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getAmount())); // 单位分
|
request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(reqDTO.getPrice())); // 单位分
|
||||||
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
|
request.setTimeExpire(formatDateV3(reqDTO.getExpireTime()));
|
||||||
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
|
request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(getOpenid(reqDTO)));
|
||||||
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(reqDTO.getUserIp()));
|
||||||
@ -84,7 +84,7 @@ public class WxPubPayClient extends AbstractWxPayClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
|
||||||
// TODO 需要实现
|
// TODO 需要实现
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package cn.iocoder.yudao.framework.pay.core.enums.order;
|
|||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渠道的支付状态枚举
|
* 渠道的支付状态枚举
|
||||||
*
|
*
|
||||||
* @author 遇到源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@ -20,4 +22,14 @@ public enum PayOrderStatusRespEnum {
|
|||||||
private final Integer status;
|
private final Integer status;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否支付成功
|
||||||
|
*
|
||||||
|
* @param status 状态
|
||||||
|
* @return 是否支付成功
|
||||||
|
*/
|
||||||
|
public static boolean isSuccess(Integer status) {
|
||||||
|
return Objects.equals(status, SUCCESS.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.enums.refund;
|
|
||||||
|
|
||||||
// TODO 芋艿:看看能不能去掉
|
|
||||||
/**
|
|
||||||
* 退款通知, 统一的渠道退款状态
|
|
||||||
*
|
|
||||||
* @author jason
|
|
||||||
*/
|
|
||||||
public enum PayNotifyRefundStatusEnum {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付宝 中 全额退款 trade_status=TRADE_CLOSED, 部分退款 trade_status=TRADE_SUCCESS
|
|
||||||
* 退款成功
|
|
||||||
*/
|
|
||||||
SUCCESS,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 支付宝退款通知没有这个状态
|
|
||||||
* 退款异常
|
|
||||||
*/
|
|
||||||
ABNORMAL;
|
|
||||||
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.enums.refund;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 渠道的退款状态枚举
|
|
||||||
*
|
|
||||||
* @author jason
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum PayRefundRespEnum {
|
|
||||||
|
|
||||||
SUCCESS(1, "退款成功"),
|
|
||||||
FAILURE(2, "退款失败"),
|
|
||||||
PROCESSING(3,"退款处理中"),
|
|
||||||
CLOSED(4, "退款关闭");
|
|
||||||
|
|
||||||
private final Integer status;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,28 @@
|
|||||||
|
package cn.iocoder.yudao.framework.pay.core.enums.refund;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渠道的退款状态枚举
|
||||||
|
*
|
||||||
|
* @author jason
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum PayRefundStatusRespEnum {
|
||||||
|
|
||||||
|
WAITING(0, "未退款"),
|
||||||
|
SUCCESS(10, "退款成功"),
|
||||||
|
FAILURE(20, "退款失败");
|
||||||
|
|
||||||
|
private final Integer status;
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
public static boolean isSuccess(Integer status) {
|
||||||
|
return Objects.equals(status, SUCCESS.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -121,10 +121,10 @@ public class PayClientFactoryImplIntegrationTest {
|
|||||||
|
|
||||||
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
|
private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {
|
||||||
PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
|
PayOrderUnifiedReqDTO reqDTO = new PayOrderUnifiedReqDTO();
|
||||||
reqDTO.setAmount(123);
|
reqDTO.setPrice(123);
|
||||||
reqDTO.setSubject("IPhone 13");
|
reqDTO.setSubject("IPhone 13");
|
||||||
reqDTO.setBody("biubiubiu");
|
reqDTO.setBody("biubiubiu");
|
||||||
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
|
reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis()));
|
||||||
reqDTO.setUserIp("127.0.0.1");
|
reqDTO.setUserIp("127.0.0.1");
|
||||||
reqDTO.setNotifyUrl("http://127.0.0.1:8080");
|
reqDTO.setNotifyUrl("http://127.0.0.1:8080");
|
||||||
return reqDTO;
|
return reqDTO;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
|
||||||
import cn.hutool.core.util.ReflectUtil;
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
import com.alipay.api.AlipayApiException;
|
import com.alipay.api.AlipayApiException;
|
||||||
import com.alipay.api.DefaultAlipayClient;
|
import com.alipay.api.DefaultAlipayClient;
|
||||||
@ -74,8 +72,8 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
|
|||||||
// 这里,设置可以直接随机整个对象。
|
// 这里,设置可以直接随机整个对象。
|
||||||
Long shopOrderId = System.currentTimeMillis();
|
Long shopOrderId = System.currentTimeMillis();
|
||||||
PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
|
PayOrderUnifiedReqDTO reqDTO=new PayOrderUnifiedReqDTO();
|
||||||
reqDTO.setMerchantOrderId(String.valueOf(System.currentTimeMillis()));
|
reqDTO.setOutTradeNo(String.valueOf(System.currentTimeMillis()));
|
||||||
reqDTO.setAmount(1);
|
reqDTO.setPrice(1);
|
||||||
reqDTO.setBody("内容:" + shopOrderId);
|
reqDTO.setBody("内容:" + shopOrderId);
|
||||||
reqDTO.setSubject("标题:"+shopOrderId);
|
reqDTO.setSubject("标题:"+shopOrderId);
|
||||||
String notify="http://niubi.natapp1.cc/api/pay/order/notify";
|
String notify="http://niubi.natapp1.cc/api/pay/order/notify";
|
||||||
|
@ -5,14 +5,18 @@ import lombok.Getter;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渠道的退款状态枚举
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum PayRefundStatusEnum {
|
public enum PayRefundStatusEnum {
|
||||||
|
|
||||||
CREATE(0, "退款订单生成"),
|
WAITING(0, "未退款"),
|
||||||
SUCCESS(1, "退款成功"),
|
SUCCESS(10, "退款成功"),
|
||||||
FAILURE(2, "退款失败"),
|
FAILURE(20, "退款失败");
|
||||||
CLOSE(99, "退款关闭");
|
|
||||||
|
|
||||||
private final Integer status;
|
private final Integer status;
|
||||||
private final String name;
|
private final String name;
|
||||||
|
@ -3,9 +3,8 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
|||||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
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.PayClientFactory;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -61,18 +60,17 @@ public class PayNotifyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 解析通知数据
|
// 2. 解析通知数据
|
||||||
PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
|
Object notify = payClient.parseNotify(params, body);
|
||||||
Object notify = payClient.parseNotify(rawNotify);
|
|
||||||
|
|
||||||
// 3. 处理通知
|
// 3. 处理通知
|
||||||
// 3.1:退款通知
|
// 3.1:退款通知
|
||||||
if (notify instanceof PayRefundNotifyRespDTO) {
|
if (notify instanceof PayRefundRespDTO) {
|
||||||
refundService.notifyPayRefund(channelId, (PayRefundNotifyRespDTO) notify, rawNotify);
|
refundService.notifyPayRefund(channelId, (PayRefundRespDTO) notify);
|
||||||
return "success";
|
return "success";
|
||||||
}
|
}
|
||||||
// 3.2:支付通知
|
// 3.2:支付通知
|
||||||
if (notify instanceof PayOrderNotifyRespDTO) {
|
if (notify instanceof PayOrderRespDTO) {
|
||||||
orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
|
orderService.notifyPayOrder(channelId, (PayOrderRespDTO) notify);
|
||||||
return "success";
|
return "success";
|
||||||
}
|
}
|
||||||
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
|
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
|
||||||
|
@ -76,9 +76,6 @@ public class PayRefundExcelVO {
|
|||||||
@ExcelProperty("退款成功时间")
|
@ExcelProperty("退款成功时间")
|
||||||
private LocalDateTime successTime;
|
private LocalDateTime successTime;
|
||||||
|
|
||||||
@ExcelProperty("退款通知时间")
|
|
||||||
private LocalDateTime notifyTime;
|
|
||||||
|
|
||||||
@ExcelProperty("退款失效时间")
|
@ExcelProperty("退款失效时间")
|
||||||
private LocalDateTime expireTime;
|
private LocalDateTime expireTime;
|
||||||
|
|
||||||
|
@ -60,9 +60,10 @@ public interface PayRefundConvert {
|
|||||||
PayRefundExcelVO payRefundExcelVO = new PayRefundExcelVO();
|
PayRefundExcelVO payRefundExcelVO = new PayRefundExcelVO();
|
||||||
|
|
||||||
payRefundExcelVO.setId(bean.getId());
|
payRefundExcelVO.setId(bean.getId());
|
||||||
payRefundExcelVO.setTradeNo(bean.getTradeNo());
|
payRefundExcelVO.setTradeNo(bean.getNo());
|
||||||
payRefundExcelVO.setMerchantOrderId(bean.getMerchantOrderId());
|
payRefundExcelVO.setMerchantOrderId(bean.getMerchantOrderId());
|
||||||
payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo());
|
// TODO 芋艿:晚点在改
|
||||||
|
// payRefundExcelVO.setMerchantRefundNo(bean.getMerchantRefundNo());
|
||||||
payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl());
|
payRefundExcelVO.setNotifyUrl(bean.getNotifyUrl());
|
||||||
payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus());
|
payRefundExcelVO.setNotifyStatus(bean.getNotifyStatus());
|
||||||
payRefundExcelVO.setStatus(bean.getStatus());
|
payRefundExcelVO.setStatus(bean.getStatus());
|
||||||
@ -71,9 +72,7 @@ public interface PayRefundConvert {
|
|||||||
payRefundExcelVO.setUserIp(bean.getUserIp());
|
payRefundExcelVO.setUserIp(bean.getUserIp());
|
||||||
payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
|
payRefundExcelVO.setChannelOrderNo(bean.getChannelOrderNo());
|
||||||
payRefundExcelVO.setChannelRefundNo(bean.getChannelRefundNo());
|
payRefundExcelVO.setChannelRefundNo(bean.getChannelRefundNo());
|
||||||
payRefundExcelVO.setExpireTime(bean.getExpireTime());
|
|
||||||
payRefundExcelVO.setSuccessTime(bean.getSuccessTime());
|
payRefundExcelVO.setSuccessTime(bean.getSuccessTime());
|
||||||
payRefundExcelVO.setNotifyTime(bean.getNotifyTime());
|
|
||||||
payRefundExcelVO.setCreateTime(bean.getCreateTime());
|
payRefundExcelVO.setCreateTime(bean.getCreateTime());
|
||||||
|
|
||||||
BigDecimal multiple = new BigDecimal(100);
|
BigDecimal multiple = new BigDecimal(100);
|
||||||
|
@ -55,7 +55,8 @@ public class PayOrderDO extends BaseDO {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 商户订单编号
|
* 商户订单编号
|
||||||
* 例如说,内部系统 A 的订单号。需要保证每个 PayMerchantDO 唯一
|
*
|
||||||
|
* 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
|
||||||
*/
|
*/
|
||||||
private String merchantOrderId;
|
private String merchantOrderId;
|
||||||
/**
|
/**
|
||||||
|
@ -32,10 +32,11 @@ public class PayOrderExtensionDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private Long id;
|
private Long id;
|
||||||
/**
|
/**
|
||||||
* 支付订单号,根据规则生成
|
* 外部订单号,根据规则生成
|
||||||
* 调用支付渠道时,使用该字段作为对接的订单号。
|
*
|
||||||
* 1. 调用微信支付 https://api.mch.weixin.qq.com/pay/unifiedorder 时,使用该字段作为 out_trade_no
|
* 调用支付渠道时,使用该字段作为对接的订单号:
|
||||||
* 2. 调用支付宝 https://opendocs.alipay.com/apis 时,使用该字段作为 out_trade_no
|
* 1. 微信支付:对应 <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_1.shtml">JSAPI 支付</a> 的 out_trade_no 字段
|
||||||
|
* 2. 支付宝支付:对应 <a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a> 的 out_trade_no 字段
|
||||||
*
|
*
|
||||||
* 例如说,P202110132239124200055
|
* 例如说,P202110132239124200055
|
||||||
*/
|
*/
|
||||||
@ -70,7 +71,7 @@ public class PayOrderExtensionDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 支付渠道的额外参数
|
* 支付渠道的额外参数
|
||||||
*
|
*
|
||||||
* 参见 https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html
|
* 参见 <a href="https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html">参数说明</>
|
||||||
*/
|
*/
|
||||||
@TableField(typeHandler = JacksonTypeHandler.class)
|
@TableField(typeHandler = JacksonTypeHandler.class)
|
||||||
private Map<String, String> channelExtras;
|
private Map<String, String> channelExtras;
|
||||||
|
@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
|
|||||||
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
|
||||||
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
|
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
|
||||||
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
|
||||||
|
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
|
||||||
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
|
import cn.iocoder.yudao.module.pay.enums.refund.PayRefundTypeEnum;
|
||||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||||
@ -37,6 +38,14 @@ public class PayRefundDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
@TableId
|
@TableId
|
||||||
private Long id;
|
private Long id;
|
||||||
|
/**
|
||||||
|
* 外部退款号,根据规则生成
|
||||||
|
*
|
||||||
|
* 调用支付渠道时,使用该字段作为对接的退款号:
|
||||||
|
* 1. 微信退款:对应 <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_4">申请退款</a> 的 out_refund_no 字段
|
||||||
|
* 2. 支付宝退款:对应 <a href="https://opendocs.alipay.com/open/02e7go"统一收单交易退款接口></a> 的 out_request_no 字段
|
||||||
|
*/
|
||||||
|
private String no;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用编号
|
* 应用编号
|
||||||
@ -63,47 +72,27 @@ public class PayRefundDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private Long orderId;
|
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;
|
|
||||||
|
|
||||||
// ========== 商户相关字段 ==========
|
// ========== 商户相关字段 ==========
|
||||||
/**
|
/**
|
||||||
* 商户订单编号
|
* 商户订单编号
|
||||||
|
*
|
||||||
|
* 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
|
||||||
*/
|
*/
|
||||||
private String merchantOrderId;
|
private String merchantOrderId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商户退款订单号, 由商户系统产生, 由他们保证唯一,不能为空,通知商户时会传该字段。
|
* 商户退款订单号
|
||||||
* 例如说,内部系统 A 的退款订单号。需要保证每个 PayMerchantDO 唯一
|
*
|
||||||
* 个商户退款订单,对应一条退款请求记录。可多次提交。 渠道保持幂等
|
* 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一
|
||||||
* 使用商户退款单,作为退款请求号
|
|
||||||
* 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 merchantRefundId;
|
||||||
private String merchantRefundNo;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 异步通知地址
|
* 异步通知地址
|
||||||
*/
|
*/
|
||||||
private String notifyUrl;
|
private String notifyUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通知商户退款结果的回调状态
|
* 通知商户退款结果的回调状态
|
||||||
* TODO 0 未发送 1 已发送
|
*
|
||||||
|
* 枚举 {@link PayNotifyStatusEnum}
|
||||||
*/
|
*/
|
||||||
private Integer notifyStatus;
|
private Integer notifyStatus;
|
||||||
|
|
||||||
@ -142,22 +131,27 @@ public class PayRefundDO extends BaseDO {
|
|||||||
|
|
||||||
// ========== 渠道相关字段 ==========
|
// ========== 渠道相关字段 ==========
|
||||||
/**
|
/**
|
||||||
* 渠道订单号,pay_order 中的channel_order_no 对应
|
* 渠道订单号
|
||||||
|
*
|
||||||
|
* 冗余 {@link PayOrderDO#getChannelOrderNo()}
|
||||||
*/
|
*/
|
||||||
private String channelOrderNo;
|
private String channelOrderNo;
|
||||||
/**
|
/**
|
||||||
* 微信中的 refund_id
|
* 渠道退款单号
|
||||||
* https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_9.shtml
|
*
|
||||||
* 支付宝没有
|
* 1. 微信退款:对应 <a href="https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_4">申请退款</a> 的 refund_id 字段
|
||||||
* 渠道退款单号,渠道返回
|
* 2. 支付宝退款:没有字段
|
||||||
*/
|
*/
|
||||||
private String channelRefundNo;
|
private String channelRefundNo;
|
||||||
|
/**
|
||||||
|
* 退款成功时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime successTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用渠道的错误码
|
* 调用渠道的错误码
|
||||||
*/
|
*/
|
||||||
private String channelErrorCode;
|
private String channelErrorCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用渠道报错时,错误信息
|
* 调用渠道报错时,错误信息
|
||||||
*/
|
*/
|
||||||
@ -165,22 +159,15 @@ public class PayRefundDO extends BaseDO {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付渠道的额外参数
|
* 支付渠道的额外参数
|
||||||
* 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
|
*
|
||||||
|
* 参见 <a href="https://www.pingxx.com/api/支付渠道%20extra%20参数说明.html">参数说明</>
|
||||||
*/
|
*/
|
||||||
private String channelExtras;
|
private String channelExtras;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO
|
* 支付渠道异步通知的内容
|
||||||
* 退款失效时间
|
*
|
||||||
|
* 在退款成功后,会记录回调的数据
|
||||||
*/
|
*/
|
||||||
private LocalDateTime expireTime;
|
private String channelNotifyData;
|
||||||
/**
|
|
||||||
* 退款成功时间
|
|
||||||
*/
|
|
||||||
private LocalDateTime successTime;
|
|
||||||
/**
|
|
||||||
* 退款通知时间
|
|
||||||
*/
|
|
||||||
private LocalDateTime notifyTime;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,10 @@ public interface PayRefundMapper extends BaseMapperX<PayRefundDO> {
|
|||||||
return selectOne("req_no", reqNo);
|
return selectOne("req_no", reqNo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO 芋艿:要重构
|
||||||
default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
|
default PayRefundDO selectByTradeNoAndMerchantRefundNo(String tradeNo, String merchantRefundNo){
|
||||||
return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
|
// return selectOne("trade_no", tradeNo, "merchant_refund_no", merchantRefundNo);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -83,17 +83,20 @@ public class PayChannelServiceImpl implements PayChannelService {
|
|||||||
*/
|
*/
|
||||||
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
|
@Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
|
||||||
public void refreshLocalCache() {
|
public void refreshLocalCache() {
|
||||||
// 情况一:如果缓存里没有数据,则直接刷新缓存
|
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||||
if (CollUtil.isEmpty(channelCache)) {
|
TenantUtils.executeIgnore(() -> {
|
||||||
initLocalCache();
|
// 情况一:如果缓存里没有数据,则直接刷新缓存
|
||||||
return;
|
if (CollUtil.isEmpty(channelCache)) {
|
||||||
}
|
initLocalCache();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
|
// 情况二,如果缓存里数据,则通过 updateTime 判断是否有数据变更,有变更则刷新缓存
|
||||||
LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannelDO::getUpdateTime);
|
LocalDateTime maxTime = CollectionUtils.getMaxValue(channelCache, PayChannelDO::getUpdateTime);
|
||||||
if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
|
if (channelMapper.selectCountByUpdateTimeGt(maxTime) > 0) {
|
||||||
initLocalCache();
|
initLocalCache();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,8 +2,7 @@ package cn.iocoder.yudao.module.pay.service.order;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
|
||||||
@ -103,8 +102,7 @@ public interface PayOrderService {
|
|||||||
*
|
*
|
||||||
* @param channelId 渠道编号
|
* @param channelId 渠道编号
|
||||||
* @param notify 通知
|
* @param notify 通知
|
||||||
* @param rawNotify 通知数据
|
|
||||||
*/
|
*/
|
||||||
void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
|
void notifyPayOrder(Long channelId, PayOrderRespDTO notify);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,10 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
|||||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
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.PayClient;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||||
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
|
||||||
@ -148,17 +148,17 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
// 3. 调用三方接口
|
// 3. 调用三方接口
|
||||||
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp)
|
PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp)
|
||||||
// 商户相关的字段
|
// 商户相关的字段
|
||||||
.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
.setOutTradeNo(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
|
||||||
.setSubject(order.getSubject()).setBody(order.getBody())
|
.setSubject(order.getSubject()).setBody(order.getBody())
|
||||||
.setNotifyUrl(genChannelPayNotifyUrl(channel))
|
.setNotifyUrl(genChannelPayNotifyUrl(channel))
|
||||||
.setReturnUrl(reqVO.getReturnUrl())
|
.setReturnUrl(reqVO.getReturnUrl())
|
||||||
// 订单相关字段
|
// 订单相关字段
|
||||||
.setAmount(order.getPrice()).setExpireTime(order.getExpireTime());
|
.setPrice(order.getPrice()).setExpireTime(order.getExpireTime());
|
||||||
PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
|
PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
|
||||||
|
|
||||||
// 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
|
// 4. 如果调用直接支付成功,则直接更新支付单状态为成功。例如说:付款码支付,免密支付时,就直接验证支付成功
|
||||||
if (unifiedOrderRespDTO.getNotify() != null) {
|
if (unifiedOrderRespDTO.getOrder() != null) {
|
||||||
notifyPayOrderSuccess(channel, unifiedOrderRespDTO.getNotify(), null);
|
notifyPayOrder(channel, unifiedOrderRespDTO.getOrder());
|
||||||
// 此处需要读取最新的状态
|
// 此处需要读取最新的状态
|
||||||
order = orderMapper.selectById(order.getId());
|
order = orderMapper.selectById(order.getId());
|
||||||
}
|
}
|
||||||
@ -226,16 +226,26 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
public void notifyPayOrder(Long channelId, PayOrderRespDTO notify) {
|
||||||
// 校验支付渠道是否有效
|
// 校验支付渠道是否有效
|
||||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||||
// 更新支付订单为已支付
|
// 更新支付订单为已支付
|
||||||
TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrderSuccess(channel, notify, rawNotify));
|
TenantUtils.execute(channel.getTenantId(), () -> notifyPayOrder(channel, notify));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyPayOrderSuccess(PayChannelDO channel, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
private void notifyPayOrder(PayChannelDO channel, PayOrderRespDTO notify) {
|
||||||
|
// 情况一:支付成功的回调
|
||||||
|
if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
|
||||||
|
notifyPayOrderSuccess(channel, notify);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 情况二:非支付成功的回调,进行忽略
|
||||||
|
log.info("[notifyPayOrder][非支付成功的回调({}),直接忽略]", toJsonString(notify));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyPayOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
|
||||||
// 1. 更新 PayOrderExtensionDO 支付成功
|
// 1. 更新 PayOrderExtensionDO 支付成功
|
||||||
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(), rawNotify);
|
PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify);
|
||||||
// 2. 更新 PayOrderDO 支付成功
|
// 2. 更新 PayOrderDO 支付成功
|
||||||
Pair<Boolean, PayOrderDO> order = updatePayOrderSuccess(channel, orderExtension, notify);
|
Pair<Boolean, PayOrderDO> order = updatePayOrderSuccess(channel, orderExtension, notify);
|
||||||
if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
|
if (order.getKey()) { // 如果之前已经成功回调,则直接返回,不用重复记录支付通知记录;例如说:支付平台重复回调
|
||||||
@ -250,13 +260,12 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
/**
|
/**
|
||||||
* 更新 PayOrderExtensionDO 支付成功
|
* 更新 PayOrderExtensionDO 支付成功
|
||||||
*
|
*
|
||||||
* @param no 支付订单号(支付模块)
|
* @param notify 通知
|
||||||
* @param rawNotify 通知数据
|
|
||||||
* @return PayOrderExtensionDO 对象
|
* @return PayOrderExtensionDO 对象
|
||||||
*/
|
*/
|
||||||
private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
|
private PayOrderExtensionDO updatePayOrderExtensionSuccess(PayOrderRespDTO notify) {
|
||||||
// 1. 查询 PayOrderExtensionDO
|
// 1. 查询 PayOrderExtensionDO
|
||||||
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
|
PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(notify.getOutTradeNo());
|
||||||
if (orderExtension == null) {
|
if (orderExtension == null) {
|
||||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
|
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_NOT_FOUND);
|
||||||
}
|
}
|
||||||
@ -269,10 +278,8 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 更新 PayOrderExtensionDO
|
// 2. 更新 PayOrderExtensionDO
|
||||||
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
|
int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(), PayOrderStatusEnum.WAITING.getStatus(),
|
||||||
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
|
PayOrderExtensionDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(toJsonString(notify)).build());
|
||||||
.status(PayOrderStatusEnum.SUCCESS.getStatus())
|
|
||||||
.channelNotifyData(toJsonString(rawNotify)).build());
|
|
||||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||||
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||||
}
|
}
|
||||||
@ -290,7 +297,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
* value:PayOrderDO 对象
|
* value:PayOrderDO 对象
|
||||||
*/
|
*/
|
||||||
private Pair<Boolean, PayOrderDO> updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
private Pair<Boolean, PayOrderDO> updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
|
||||||
PayOrderNotifyRespDTO notify) {
|
PayOrderRespDTO notify) {
|
||||||
// 1. 判断 PayOrderDO 是否处于待支付
|
// 1. 判断 PayOrderDO 是否处于待支付
|
||||||
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
|
||||||
if (order == null) {
|
if (order == null) {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
package cn.iocoder.yudao.module.pay.service.refund;
|
package cn.iocoder.yudao.module.pay.service.refund;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
||||||
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -62,8 +61,7 @@ public interface PayRefundService {
|
|||||||
*
|
*
|
||||||
* @param channelId 渠道编号
|
* @param channelId 渠道编号
|
||||||
* @param notify 通知
|
* @param notify 通知
|
||||||
* @param rawNotify 通知数据
|
|
||||||
*/
|
*/
|
||||||
void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
|
void notifyPayRefund(Long channelId, PayRefundRespDTO notify);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,9 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|||||||
import cn.iocoder.yudao.framework.pay.config.PayProperties;
|
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.PayClient;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayNotifyRefundStatusEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
||||||
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
|
||||||
@ -39,14 +38,13 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 退款订单 Service 实现类
|
* 退款订单 Service 实现类
|
||||||
*
|
*
|
||||||
* @author aquan
|
* @author jason
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -116,7 +114,7 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO 芋艿:待实现
|
// TODO 芋艿:待实现
|
||||||
String merchantRefundId = RandomUtil.randomNumbers(16);
|
String merchantRefundId = "rrr" + RandomUtil.randomNumbers(16);
|
||||||
|
|
||||||
// 校验退款的条件
|
// 校验退款的条件
|
||||||
validatePayRefund(reqDTO, order);
|
validatePayRefund(reqDTO, order);
|
||||||
@ -128,12 +126,11 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
|
PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
|
||||||
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
|
PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
|
||||||
merchantRefundId); // TODO 芋艿:需要优化
|
merchantRefundId); // TODO 芋艿:需要优化
|
||||||
if(Objects.nonNull(payRefundDO)){
|
if (Objects.nonNull(payRefundDO)) {
|
||||||
// 退款订单已经提交过。
|
// 退款订单已经提交过。
|
||||||
//TODO 校验相同退款单的金额
|
//TODO 校验相同退款单的金额
|
||||||
// TODO @jason:咱要不封装一个 ObjectUtils.equalsAny
|
// TODO @jason:咱要不封装一个 ObjectUtils.equalsAny
|
||||||
if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())
|
if (Objects.equals(PayRefundStatusEnum.SUCCESS.getStatus(), payRefundDO.getStatus())) {
|
||||||
|| Objects.equals(PayRefundStatusEnum.CLOSE.getStatus(), payRefundDO.getStatus())) {
|
|
||||||
//已成功退款
|
//已成功退款
|
||||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_SUCCEED);
|
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_SUCCEED);
|
||||||
}
|
}
|
||||||
@ -147,14 +144,14 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
.channelCode(order.getChannelCode())
|
.channelCode(order.getChannelCode())
|
||||||
.channelId(order.getChannelId())
|
.channelId(order.getChannelId())
|
||||||
.orderId(order.getId())
|
.orderId(order.getId())
|
||||||
.merchantRefundNo(merchantRefundId) // TODO 芋艿:需要优化
|
.merchantRefundId(merchantRefundId)
|
||||||
.notifyUrl(app.getRefundNotifyUrl())
|
.notifyUrl(app.getRefundNotifyUrl())
|
||||||
.payPrice(order.getPrice())
|
.payPrice(order.getPrice())
|
||||||
.refundPrice(reqDTO.getPrice())
|
.refundPrice(reqDTO.getPrice())
|
||||||
.userIp(reqDTO.getUserIp())
|
.userIp(reqDTO.getUserIp())
|
||||||
.merchantOrderId(order.getMerchantOrderId())
|
.merchantOrderId(order.getMerchantOrderId())
|
||||||
.tradeNo(orderExtensionDO.getNo())
|
.no(orderExtensionDO.getNo())
|
||||||
.status(PayRefundStatusEnum.CREATE.getStatus())
|
.status(PayRefundStatusEnum.WAITING.getStatus())
|
||||||
.reason(reqDTO.getReason())
|
.reason(reqDTO.getReason())
|
||||||
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
.notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||||
.type(refundType.getStatus())
|
.type(refundType.getStatus())
|
||||||
@ -163,11 +160,9 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
}
|
}
|
||||||
// TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
|
// TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
|
||||||
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
|
PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
|
||||||
unifiedReqDTO.setUserIp(reqDTO.getUserIp())
|
unifiedReqDTO.setPrice(reqDTO.getPrice())
|
||||||
.setAmount(reqDTO.getPrice())
|
.setOutTradeNo(orderExtensionDO.getNo())
|
||||||
.setChannelOrderNo(order.getChannelOrderNo())
|
.setOutRefundNo(merchantRefundId) // TODO 芋艿:需要优化
|
||||||
.setPayTradeNo(orderExtensionDO.getNo())
|
|
||||||
.setMerchantRefundId(merchantRefundId) // TODO 芋艿:需要优化
|
|
||||||
.setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
|
.setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
|
||||||
.setReason(reqDTO.getReason());
|
.setReason(reqDTO.getReason());
|
||||||
// 向渠道发起退款申请
|
// 向渠道发起退款申请
|
||||||
@ -191,24 +186,25 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
|
public void notifyPayRefund(Long channelId, PayRefundRespDTO notify) {
|
||||||
// 校验支付渠道是否有效
|
// 校验支付渠道是否有效
|
||||||
// TODO 芋艿:需要重构下这块的逻辑
|
// TODO 芋艿:需要重构下这块的逻辑
|
||||||
PayChannelDO channel = channelService.validPayChannel(channelId);
|
PayChannelDO channel = channelService.validPayChannel(channelId);
|
||||||
if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
|
if (PayRefundStatusRespEnum.isSuccess(notify.getStatus())) {
|
||||||
payRefundSuccess(notify);
|
payRefundSuccess(notify);
|
||||||
} else {
|
} else {
|
||||||
//TODO 支付异常, 支付宝似乎没有支付异常的通知。
|
|
||||||
// TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
|
// TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
|
private void payRefundSuccess(PayRefundRespDTO refundNotify) {
|
||||||
// 校验退款单存在
|
// 校验退款单存在
|
||||||
PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
|
PayRefundDO refundDO = null; // TODO 芋艿:临时注释
|
||||||
refundNotify.getReqNo());
|
// PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
|
||||||
|
// refundNotify.getReqNo());
|
||||||
if (refundDO == null) {
|
if (refundDO == null) {
|
||||||
log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
|
// TODO 芋艿:临时注释
|
||||||
|
// log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
|
||||||
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
|
throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,10 +229,10 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
// 更新退款订单
|
// 更新退款订单
|
||||||
PayRefundDO updateRefundDO = new PayRefundDO();
|
PayRefundDO updateRefundDO = new PayRefundDO();
|
||||||
updateRefundDO.setId(refundDO.getId())
|
updateRefundDO.setId(refundDO.getId())
|
||||||
.setSuccessTime(refundNotify.getRefundSuccessTime())
|
.setSuccessTime(refundNotify.getSuccessTime())
|
||||||
.setChannelRefundNo(refundNotify.getChannelOrderNo())
|
// TODO 芋艿:如下两行,临时注释
|
||||||
.setTradeNo(refundNotify.getTradeNo())
|
// .setChannelRefundNo(refundNotify.getChannelOrderNo())
|
||||||
.setNotifyTime(LocalDateTime.now())
|
// .setNo(refundNotify.getTradeNo())
|
||||||
.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
|
.setStatus(PayRefundStatusEnum.SUCCESS.getStatus());
|
||||||
refundMapper.updateById(updateRefundDO);
|
refundMapper.updateById(updateRefundDO);
|
||||||
|
|
||||||
|
@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.pay.service.app;
|
|||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||||
import cn.iocoder.yudao.framework.test.core.util.RandomUtils;
|
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppCreateReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppPageReqVO;
|
||||||
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO;
|
import cn.iocoder.yudao.module.pay.controller.admin.app.vo.PayAppUpdateReqVO;
|
||||||
@ -18,8 +16,6 @@ import org.springframework.boot.test.mock.mockito.MockBean;
|
|||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;
|
||||||
|
@ -62,7 +62,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
|
|||||||
o.setChannelId(1L);
|
o.setChannelId(1L);
|
||||||
o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
|
o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
|
||||||
o.setOrderId(1L);
|
o.setOrderId(1L);
|
||||||
o.setTradeNo("OT0000001");
|
o.setNo("OT0000001");
|
||||||
o.setMerchantOrderId("MOT0000001");
|
o.setMerchantOrderId("MOT0000001");
|
||||||
o.setMerchantRefundNo("MRF0000001");
|
o.setMerchantRefundNo("MRF0000001");
|
||||||
o.setNotifyUrl("https://www.cancanzi.com");
|
o.setNotifyUrl("https://www.cancanzi.com");
|
||||||
@ -127,7 +127,7 @@ public class PayRefundServiceTest extends BaseDbUnitTest {
|
|||||||
o.setChannelId(1L);
|
o.setChannelId(1L);
|
||||||
o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
|
o.setChannelCode(PayChannelEnum.WX_PUB.getCode());
|
||||||
o.setOrderId(1L);
|
o.setOrderId(1L);
|
||||||
o.setTradeNo("OT0000001");
|
o.setNo("OT0000001");
|
||||||
o.setMerchantOrderId("MOT0000001");
|
o.setMerchantOrderId("MOT0000001");
|
||||||
o.setMerchantRefundNo("MRF0000001");
|
o.setMerchantRefundNo("MRF0000001");
|
||||||
o.setNotifyUrl("https://www.cancanzi.com");
|
o.setNotifyUrl("https://www.cancanzi.com");
|
||||||
|
@ -28,14 +28,14 @@ export function deleteChannel(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获得支付渠道
|
// 获得支付渠道
|
||||||
export function getChannel(appId,code) {
|
export function getChannel(appId, code) {
|
||||||
return request({
|
return request({
|
||||||
url: '/pay/channel/get-channel',
|
url: '/pay/channel/get',
|
||||||
|
method: 'get',
|
||||||
params:{
|
params:{
|
||||||
appId:appId,
|
appId,
|
||||||
code:code
|
code
|
||||||
},
|
},
|
||||||
method: 'get'
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user