后端:完成退款逻辑
This commit is contained in:
parent
1be40cb195
commit
207fdc1d55
|
@ -2,7 +2,6 @@ package cn.iocoder.mall.order.api;
|
||||||
|
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
import cn.iocoder.mall.order.api.dto.OrderReturnApplyDTO;
|
import cn.iocoder.mall.order.api.dto.OrderReturnApplyDTO;
|
||||||
import cn.iocoder.mall.order.api.dto.OrderReturnCreateDTO;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单退货
|
* 订单退货
|
||||||
|
@ -21,4 +20,16 @@ public interface OrderReturnService {
|
||||||
*/
|
*/
|
||||||
CommonResult orderReturnApply(OrderReturnApplyDTO orderReturnApplyDTO);
|
CommonResult orderReturnApply(OrderReturnApplyDTO orderReturnApplyDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新退款成功
|
||||||
|
*
|
||||||
|
* 如果成功,则返回 success
|
||||||
|
* 如果失败,则返回具体原因
|
||||||
|
*
|
||||||
|
* @param orderId 订单编号
|
||||||
|
* @param refundPrice 退款金额
|
||||||
|
* @return 支付结果
|
||||||
|
*/
|
||||||
|
String updateRefundSuccess(String orderId, Integer refundPrice);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,4 +52,9 @@ public class OrderReturnServiceImpl implements OrderReturnService {
|
||||||
orderReturnMapper.insert(orderReturnDO);
|
orderReturnMapper.insert(orderReturnDO);
|
||||||
return CommonResult.success(null);
|
return CommonResult.success(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String updateRefundSuccess(String orderId, Integer refundPrice) {
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,9 +258,11 @@ public class OrderServiceImpl implements OrderService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标记优惠劵已使用
|
// 标记优惠劵已使用
|
||||||
CommonResult<Boolean> useCouponCardResult = couponService.useCouponCard(userId, orderCreateDTO.getCouponCardId());
|
if (orderCreateDTO.getCouponCardId() != null) {
|
||||||
if (useCouponCardResult.isError()) {
|
CommonResult<Boolean> useCouponCardResult = couponService.useCouponCard(userId, orderCreateDTO.getCouponCardId());
|
||||||
return CommonResult.error(useCouponCardResult);
|
if (useCouponCardResult.isError()) {
|
||||||
|
return CommonResult.error(useCouponCardResult);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 芋艿,扣除库存
|
// TODO 芋艿,扣除库存
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package cn.iocoder.mall.pay.api.message;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class AbstractPayNotifySuccessMessage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 任务编号
|
||||||
|
*/
|
||||||
|
private Integer id;
|
||||||
|
/**
|
||||||
|
* 应用编号
|
||||||
|
*/
|
||||||
|
private String appId;
|
||||||
|
/**
|
||||||
|
* 当前通知次数
|
||||||
|
*/
|
||||||
|
private Integer notifyTimes;
|
||||||
|
/**
|
||||||
|
* 通知地址
|
||||||
|
*/
|
||||||
|
private String notifyUrl;
|
||||||
|
|
||||||
|
}
|
|
@ -1,16 +1,17 @@
|
||||||
package cn.iocoder.mall.pay.api.message;
|
package cn.iocoder.mall.pay.api.message;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付退款成功的消息对象
|
* 支付退款成功的消息对象
|
||||||
*/
|
*/
|
||||||
public class PayRefundSuccessMessage {
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class PayRefundSuccessMessage extends AbstractPayNotifySuccessMessage {
|
||||||
|
|
||||||
public static final String TOPIC = "PAY_REFUND_SUCCESS";
|
public static final String TOPIC = "PAY_REFUND_SUCCESS";
|
||||||
|
|
||||||
/**
|
|
||||||
* 任务编号
|
|
||||||
*/
|
|
||||||
private Integer id;
|
|
||||||
/**
|
/**
|
||||||
* 退款单编号
|
* 退款单编号
|
||||||
*/
|
*/
|
||||||
|
@ -19,75 +20,9 @@ public class PayRefundSuccessMessage {
|
||||||
* 交易编号
|
* 交易编号
|
||||||
*/
|
*/
|
||||||
private Integer transactionId;
|
private Integer transactionId;
|
||||||
/**
|
|
||||||
* 应用编号
|
|
||||||
*/
|
|
||||||
private String appId;
|
|
||||||
/**
|
/**
|
||||||
* 应用订单编号
|
* 应用订单编号
|
||||||
*/
|
*/
|
||||||
private String orderId;
|
private String orderId;
|
||||||
/**
|
|
||||||
* 当前通知次数
|
|
||||||
*/
|
|
||||||
private Integer notifyTimes;
|
|
||||||
/**
|
|
||||||
* 通知地址
|
|
||||||
*/
|
|
||||||
private String notifyUrl;
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayRefundSuccessMessage setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAppId() {
|
|
||||||
return appId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayRefundSuccessMessage setAppId(String appId) {
|
|
||||||
this.appId = appId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrderId() {
|
|
||||||
return orderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayRefundSuccessMessage setOrderId(String orderId) {
|
|
||||||
this.orderId = orderId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getNotifyTimes() {
|
|
||||||
return notifyTimes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayRefundSuccessMessage setNotifyTimes(Integer notifyTimes) {
|
|
||||||
this.notifyTimes = notifyTimes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotifyUrl() {
|
|
||||||
return notifyUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayRefundSuccessMessage setNotifyUrl(String notifyUrl) {
|
|
||||||
this.notifyUrl = notifyUrl;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getTransactionId() {
|
|
||||||
return transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayRefundSuccessMessage setTransactionId(Integer transactionId) {
|
|
||||||
this.transactionId = transactionId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +1,24 @@
|
||||||
package cn.iocoder.mall.pay.api.message;
|
package cn.iocoder.mall.pay.api.message;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付交易单支付成功的消息对象
|
* 支付交易单支付成功的消息对象
|
||||||
*/
|
*/
|
||||||
public class PayTransactionSuccessMessage {
|
@Data
|
||||||
|
@Accessors(chain = true)
|
||||||
|
public class PayTransactionSuccessMessage extends AbstractPayNotifySuccessMessage {
|
||||||
|
|
||||||
public static final String TOPIC = "PAY_TRANSACTION_SUCCESS";
|
public static final String TOPIC = "PAY_TRANSACTION_SUCCESS";
|
||||||
|
|
||||||
/**
|
|
||||||
* 任务编号
|
|
||||||
*/
|
|
||||||
private Integer id;
|
|
||||||
/**
|
/**
|
||||||
* 交易编号
|
* 交易编号
|
||||||
*/
|
*/
|
||||||
private Integer transactionId;
|
private Integer transactionId;
|
||||||
/**
|
|
||||||
* 应用编号
|
|
||||||
*/
|
|
||||||
private String appId;
|
|
||||||
/**
|
/**
|
||||||
* 应用订单编号
|
* 应用订单编号
|
||||||
*/
|
*/
|
||||||
private String orderId;
|
private String orderId;
|
||||||
/**
|
|
||||||
* 当前通知次数
|
|
||||||
*/
|
|
||||||
private Integer notifyTimes;
|
|
||||||
/**
|
|
||||||
* 通知地址
|
|
||||||
*/
|
|
||||||
private String notifyUrl;
|
|
||||||
|
|
||||||
public Integer getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayTransactionSuccessMessage setId(Integer id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAppId() {
|
|
||||||
return appId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayTransactionSuccessMessage setAppId(String appId) {
|
|
||||||
this.appId = appId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOrderId() {
|
|
||||||
return orderId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayTransactionSuccessMessage setOrderId(String orderId) {
|
|
||||||
this.orderId = orderId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getNotifyTimes() {
|
|
||||||
return notifyTimes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayTransactionSuccessMessage setNotifyTimes(Integer notifyTimes) {
|
|
||||||
this.notifyTimes = notifyTimes;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNotifyUrl() {
|
|
||||||
return notifyUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayTransactionSuccessMessage setNotifyUrl(String notifyUrl) {
|
|
||||||
this.notifyUrl = notifyUrl;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getTransactionId() {
|
|
||||||
return transactionId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PayTransactionSuccessMessage setTransactionId(Integer transactionId) {
|
|
||||||
this.transactionId = transactionId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public class PingxxPaySDK extends AbstractPaySDK {
|
||||||
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||||
String chargeId = chargeObj.getString("id");
|
String chargeId = chargeObj.getString("id");
|
||||||
// 请求ping++
|
// 请求ping++
|
||||||
Map<String, Object> reqObj = createRefundRequest(chargeId, refund.getOrderDescription(), refund.getPrice());
|
Map<String, Object> reqObj = createRefundRequest(refund, chargeId, refund.getOrderDescription(), refund.getPrice());
|
||||||
try {
|
try {
|
||||||
Refund pingxxRefund = Refund.create(chargeId, reqObj);
|
Refund pingxxRefund = Refund.create(chargeId, reqObj);
|
||||||
System.out.println(pingxxRefund.toString());
|
System.out.println(pingxxRefund.toString());
|
||||||
|
@ -97,16 +97,28 @@ public class PingxxPaySDK extends AbstractPaySDK {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// {"id":"evt_400190427005305341228202","created":1556297585,"livemode":false,"type":"refund.succeeded","data":{"object":{"id":"re_HO0m9GOGOi50KCmX104ufHe1","object":"refund","order_no":"HO0m9GOGOi50KCmX104ufHe1","amount":1,"created":1556297585,"succeed":true,"status":"succeeded","time_succeed":1556297585,"description":"测试下退款","failure_code":null,"failure_msg":null,"metadata":{},"charge":"ch_y1iXjLnDS4G4OO4uT4a5C4W1","charge_order_no":"20190427004410165545","transaction_no":"201904270053053608824","extra":{}}},"object":"event","request":"iar_Oa188KCiHC40iLibbHX5WrHC","pending_webhooks":0}
|
||||||
@Override
|
@Override
|
||||||
public CommonResult<RefundSuccessBO> parseRefundSuccessParams(String params) {
|
public CommonResult<RefundSuccessBO> parseRefundSuccessParams(String params) {
|
||||||
return null;
|
JSONObject paramsObj = JSON.parseObject(params);
|
||||||
|
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||||
|
RefundSuccessBO refundSuccessBO = new RefundSuccessBO()
|
||||||
|
.setRefundCode(chargeObj.getJSONObject("metadata").getString("refundCode"))
|
||||||
|
.setRefundTime(new Date(chargeObj.getLong("time_succeed") * 1000))
|
||||||
|
.setTradeNo(chargeObj.getString("transaction_no"))
|
||||||
|
// TODO 芋艿,需要测试下,退款失败
|
||||||
|
.setSuccess(chargeObj.containsValue("failure_code") || chargeObj.containsValue("failure_msg"));
|
||||||
|
return CommonResult.success(refundSuccessBO);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> createRefundRequest(String chargeId, String orderDescription, Integer price) {
|
private Map<String, Object> createRefundRequest(PayRefundDO refund, String chargeId, String orderDescription, Integer price) {
|
||||||
Map<String, Object> reqObj = new HashMap<>();
|
Map<String, Object> reqObj = new HashMap<>();
|
||||||
// reqObj.put("CHARGE_ID", chargeId);
|
// reqObj.put("CHARGE_ID", chargeId);
|
||||||
reqObj.put("description", orderDescription);
|
reqObj.put("description", orderDescription);
|
||||||
reqObj.put("amount", price);
|
reqObj.put("amount", price);
|
||||||
|
Map<String, Object> metadata = new HashMap<>();
|
||||||
|
metadata.put("refundCode", refund.getRefundCode());
|
||||||
|
reqObj.put("metadata", metadata);
|
||||||
return reqObj;
|
return reqObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,73 @@
|
||||||
package cn.iocoder.mall.pay.biz.component;
|
package cn.iocoder.mall.pay.biz.component;
|
||||||
|
|
||||||
|
import com.alibaba.dubbo.config.ApplicationConfig;
|
||||||
|
import com.alibaba.dubbo.config.ReferenceConfig;
|
||||||
|
import com.alibaba.dubbo.config.RegistryConfig;
|
||||||
|
import com.alibaba.dubbo.rpc.service.GenericService;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class DubboReferencePool {
|
public class DubboReferencePool {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ReferenceMeta {
|
||||||
|
|
||||||
|
private final ReferenceConfig config; // TODO 芋艿,后续需要做销毁
|
||||||
|
private final GenericService service;
|
||||||
|
private final String methodName;
|
||||||
|
|
||||||
|
private ReferenceMeta(ReferenceConfig config, GenericService service, String methodName) {
|
||||||
|
this.config = config;
|
||||||
|
this.service = service;
|
||||||
|
this.methodName = methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private LoadingCache<String, ReferenceMeta> referenceMetaCache = CacheBuilder.newBuilder()
|
||||||
|
.build(new CacheLoader<String, ReferenceMeta>() {
|
||||||
|
@Override
|
||||||
|
public ReferenceMeta load(String notifyUrl) {
|
||||||
|
return createGenericService(notifyUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@Value("${dubbo.registry.address}")
|
||||||
|
private String dubboRegistryAddress;
|
||||||
|
@Value("${dubbo.application.name}")
|
||||||
|
private String dubboApplicationName;
|
||||||
|
|
||||||
|
private ReferenceMeta createGenericService(String notifyUrl) {
|
||||||
|
String[] notifyUrlParts = notifyUrl.split("#");
|
||||||
|
// 创建 ApplicationConfig 对象
|
||||||
|
ApplicationConfig application = new ApplicationConfig();
|
||||||
|
application.setName(dubboApplicationName);
|
||||||
|
// 创建 RegistryConfig 对象
|
||||||
|
RegistryConfig registry = new RegistryConfig();
|
||||||
|
// registry.setAddress("zookeeper://127.0.0.1:2181");
|
||||||
|
registry.setAddress(dubboRegistryAddress);
|
||||||
|
application.setRegistry(registry);
|
||||||
|
// 创建 ReferenceConfig 对象
|
||||||
|
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
|
||||||
|
reference.setInterface(notifyUrlParts[0]); // 弱类型接口名
|
||||||
|
reference.setGeneric(true); // 声明为泛化接口
|
||||||
|
reference.setApplication(application);
|
||||||
|
// 获得 GenericService 对象
|
||||||
|
GenericService genericService = reference.get();
|
||||||
|
// 构建最终的 ReferenceMeta 对象
|
||||||
|
return new ReferenceMeta(reference, genericService, notifyUrlParts[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReferenceMeta getReferenceMeta(String notifyUrl) {
|
||||||
|
DubboReferencePool.ReferenceMeta referenceMeta = referenceMetaCache.getUnchecked(notifyUrl);
|
||||||
|
Assert.notNull(referenceMeta, String.format("notifyUrl(%s) 不存在对应的 ReferenceMeta 对象", notifyUrl));
|
||||||
|
return referenceMeta;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cn.iocoder.mall.pay.biz.convert;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.pay.api.message.PayRefundSuccessMessage;
|
||||||
|
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PayNotifyConvert {
|
||||||
|
|
||||||
|
PayNotifyConvert INSTANCE = Mappers.getMapper(PayNotifyConvert.class);
|
||||||
|
|
||||||
|
@Mappings({
|
||||||
|
@Mapping(source = "transaction.transactionId", target = "transactionId"),
|
||||||
|
@Mapping(source = "transaction.orderId", target = "orderId"),
|
||||||
|
})
|
||||||
|
PayTransactionSuccessMessage convertTransaction(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
||||||
|
|
||||||
|
@Mappings({
|
||||||
|
@Mapping(source = "refund.transactionId", target = "transactionId"),
|
||||||
|
@Mapping(source = "refund.orderId", target = "orderId"),
|
||||||
|
@Mapping(source = "refund.refundId", target = "refundId"),
|
||||||
|
})
|
||||||
|
PayRefundSuccessMessage convertRefund(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.iocoder.mall.pay.biz.convert;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
||||||
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mappings;
|
||||||
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PayRefundConvert {
|
||||||
|
|
||||||
|
PayRefundConvert INSTANCE = Mappers.getMapper(PayRefundConvert.class);
|
||||||
|
|
||||||
|
@Mappings({})
|
||||||
|
PayRefundDO convert(PayRefundSubmitDTO payRefundSubmitDTO);
|
||||||
|
|
||||||
|
}
|
|
@ -1,17 +1,11 @@
|
||||||
package cn.iocoder.mall.pay.biz.convert;
|
package cn.iocoder.mall.pay.biz.convert;
|
||||||
|
|
||||||
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
|
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
|
||||||
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
|
||||||
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
|
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
|
||||||
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
|
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
|
||||||
import cn.iocoder.mall.pay.api.message.PayRefundSuccessMessage;
|
|
||||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
|
||||||
import org.mapstruct.Mappings;
|
import org.mapstruct.Mappings;
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
|
@ -29,20 +23,4 @@ public interface PayTransactionConvert {
|
||||||
@Mappings({})
|
@Mappings({})
|
||||||
PayTransactionExtensionDO convert(PayTransactionSubmitDTO payTransactionSubmitDTO);
|
PayTransactionExtensionDO convert(PayTransactionSubmitDTO payTransactionSubmitDTO);
|
||||||
|
|
||||||
@Mappings({
|
|
||||||
@Mapping(source = "transaction.transactionId", target = "transactionId"),
|
|
||||||
@Mapping(source = "transaction.orderId", target = "orderId"),
|
|
||||||
})
|
|
||||||
PayTransactionSuccessMessage convert(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
|
||||||
|
|
||||||
@Mappings({
|
|
||||||
@Mapping(source = "refund.transactionId", target = "transactionId"),
|
|
||||||
@Mapping(source = "refund.orderId", target = "orderId"),
|
|
||||||
@Mapping(source = "refund.refundId", target = "refundId"),
|
|
||||||
})
|
|
||||||
PayRefundSuccessMessage convert2(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
|
||||||
|
|
||||||
@Mappings({})
|
|
||||||
PayRefundDO convert(PayRefundSubmitDTO payRefundSubmitDTO);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import cn.iocoder.mall.pay.biz.dataobject.PayNotifyLogDO;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PayTransactionNotifyLogMapper {
|
public interface PayNotifyLogMapper {
|
||||||
|
|
||||||
void insert(PayNotifyLogDO entity);
|
void insert(PayNotifyLogDO entity);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import org.springframework.stereotype.Repository;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface PayTransactionNotifyTaskMapper {
|
public interface PayNotifyTaskMapper {
|
||||||
|
|
||||||
void insert(PayNotifyTaskDO entity);
|
void insert(PayNotifyTaskDO entity);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package cn.iocoder.mall.pay.biz.scheduler;
|
package cn.iocoder.mall.pay.biz.job;
|
||||||
|
|
||||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
import cn.iocoder.mall.pay.biz.dao.PayNotifyTaskMapper;
|
||||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
|
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||||
|
import cn.iocoder.mall.pay.biz.service.PayNotifyServiceImpl;
|
||||||
import com.xxl.job.core.biz.model.ReturnT;
|
import com.xxl.job.core.biz.model.ReturnT;
|
||||||
import com.xxl.job.core.handler.IJobHandler;
|
import com.xxl.job.core.handler.IJobHandler;
|
||||||
import com.xxl.job.core.handler.annotation.JobHandler;
|
import com.xxl.job.core.handler.annotation.JobHandler;
|
||||||
|
@ -16,14 +15,17 @@ import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付交易成功通知 Job
|
* 支付通知重试 Job
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@JobHandler(value = "payTransactionNotifyJob")
|
@JobHandler(value = "payTransactionNotifyJob")
|
||||||
public class PayTransactionNotifyJob extends IJobHandler {
|
public class PayNotifyJob extends IJobHandler {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
|
private PayNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PayNotifyServiceImpl payNotifyService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RocketMQTemplate rocketMQTemplate;
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
@ -33,16 +35,15 @@ public class PayTransactionNotifyJob extends IJobHandler {
|
||||||
// 获得需要通知的任务
|
// 获得需要通知的任务
|
||||||
List<PayNotifyTaskDO> notifyTasks = payTransactionNotifyTaskMapper.selectByNotify();
|
List<PayNotifyTaskDO> notifyTasks = payTransactionNotifyTaskMapper.selectByNotify();
|
||||||
// 循环任务,发送通知
|
// 循环任务,发送通知
|
||||||
for (PayNotifyTaskDO payTransactionNotifyTask : notifyTasks) {
|
for (PayNotifyTaskDO notifyTask : notifyTasks) {
|
||||||
// 发送 MQ
|
// 发送 MQ
|
||||||
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
payNotifyService.sendNotifyMessage(notifyTask);
|
||||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
|
||||||
// 更新最后通知时间
|
// 更新最后通知时间
|
||||||
// 1. 这样操作,虽然可能会出现 MQ 消费快于下面 PayTransactionNotifyTaskDO 的更新语句。但是,因为更新字段不同,所以不会有问题。
|
// 1. 这样操作,虽然可能会出现 MQ 消费快于下面 PayTransactionNotifyTaskDO 的更新语句。但是,因为更新字段不同,所以不会有问题。
|
||||||
// 2. 换个视角,如果先更新 PayTransactionNotifyTaskDO ,再发送 MQ 消息。如果 MQ 消息发送失败,则 PayTransactionNotifyTaskDO 再也不会被轮询到了。
|
// 2. 换个视角,如果先更新 PayTransactionNotifyTaskDO ,再发送 MQ 消息。如果 MQ 消息发送失败,则 PayTransactionNotifyTaskDO 再也不会被轮询到了。
|
||||||
// 3. 当然,最最最完美的话,就是做事务消息,不过这样又过于复杂~
|
// 3. 当然,最最最完美的话,就是做事务消息,不过这样又过于复杂~
|
||||||
PayNotifyTaskDO updateNotifyTask = new PayNotifyTaskDO()
|
PayNotifyTaskDO updateNotifyTask = new PayNotifyTaskDO()
|
||||||
.setId(payTransactionNotifyTask.getId()).setLastExecuteTime(new Date());
|
.setId(notifyTask.getId()).setLastExecuteTime(new Date());
|
||||||
payTransactionNotifyTaskMapper.update(updateNotifyTask);
|
payTransactionNotifyTaskMapper.update(updateNotifyTask);
|
||||||
}
|
}
|
||||||
return new ReturnT<>("执行通知数:" + notifyTasks.size());
|
return new ReturnT<>("执行通知数:" + notifyTasks.size());
|
|
@ -0,0 +1,82 @@
|
||||||
|
package cn.iocoder.mall.pay.biz.mq;
|
||||||
|
|
||||||
|
import cn.iocoder.common.framework.util.DateUtil;
|
||||||
|
import cn.iocoder.common.framework.util.ExceptionUtil;
|
||||||
|
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
|
||||||
|
import cn.iocoder.mall.pay.api.message.AbstractPayNotifySuccessMessage;
|
||||||
|
import cn.iocoder.mall.pay.biz.component.DubboReferencePool;
|
||||||
|
import cn.iocoder.mall.pay.biz.dao.PayNotifyLogMapper;
|
||||||
|
import cn.iocoder.mall.pay.biz.dao.PayNotifyTaskMapper;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyLogDO;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public abstract class AbstractPayNotifySuccessConsumer<T extends AbstractPayNotifySuccessMessage> implements RocketMQListener<T> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private DubboReferencePool dubboReferencePool;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PayNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||||
|
@Autowired
|
||||||
|
private PayNotifyLogMapper payTransactionNotifyLogMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void onMessage(T message) {
|
||||||
|
// 获得 ReferenceMeta 对象
|
||||||
|
DubboReferencePool.ReferenceMeta referenceMeta = dubboReferencePool.getReferenceMeta(message.getNotifyUrl());
|
||||||
|
// 发起调用
|
||||||
|
String response = null; // RPC / HTTP 调用的响应
|
||||||
|
PayNotifyTaskDO updateTask = new PayNotifyTaskDO() // 更新 PayTransactionNotifyTaskDO 对象
|
||||||
|
.setId(message.getId())
|
||||||
|
.setLastExecuteTime(new Date())
|
||||||
|
.setNotifyTimes(message.getNotifyTimes() + 1);
|
||||||
|
try {
|
||||||
|
response = invoke(message, referenceMeta);
|
||||||
|
if ("success".equals(response)) { // 情况一,请求成功且返回成功
|
||||||
|
// 更新通知成功
|
||||||
|
updateTask.setStatus(PayTransactionNotifyStatusEnum.SUCCESS.getValue());
|
||||||
|
payTransactionNotifyTaskMapper.update(updateTask);
|
||||||
|
// 需要更新支付交易单通知应用成功
|
||||||
|
afterInvokeSuccess(message);
|
||||||
|
} else { // 情况二,请求成功且返回失败
|
||||||
|
// 更新通知请求成功,但是结果失败
|
||||||
|
handleFailure(updateTask, PayTransactionNotifyStatusEnum.REQUEST_SUCCESS.getValue());
|
||||||
|
payTransactionNotifyTaskMapper.update(updateTask);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) { // 请求失败
|
||||||
|
// 更新通知请求失败
|
||||||
|
response = ExceptionUtil.getRootCauseMessage(e);
|
||||||
|
handleFailure(updateTask, PayTransactionNotifyStatusEnum.REQUEST_FAILURE.getValue());
|
||||||
|
payTransactionNotifyTaskMapper.update(updateTask);
|
||||||
|
// 抛出异常,回滚事务
|
||||||
|
throw e; // TODO 芋艿,此处不能抛出异常。因为,会导致 MQ + 定时任务多重试。此处的目标是,事务回滚 + 吃掉事务。另外,最后的 finally 的日志,要插入成功。
|
||||||
|
} finally {
|
||||||
|
// 插入 PayTransactionNotifyLogDO 日志
|
||||||
|
PayNotifyLogDO notifyLog = new PayNotifyLogDO().setNotifyId(message.getId())
|
||||||
|
.setRequest(JSON.toJSONString(message)).setResponse(response).setStatus(updateTask.getStatus());
|
||||||
|
payTransactionNotifyLogMapper.insert(notifyLog);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFailure(PayNotifyTaskDO updateTask, Integer defaultStatus) {
|
||||||
|
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
||||||
|
updateTask.setStatus(PayTransactionNotifyStatusEnum.FAILURE.getValue());
|
||||||
|
} else {
|
||||||
|
updateTask.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||||
|
updateTask.setStatus(defaultStatus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String invoke(T message, DubboReferencePool.ReferenceMeta referenceMeta);
|
||||||
|
|
||||||
|
protected abstract void afterInvokeSuccess(T message);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package cn.iocoder.mall.pay.biz.mq;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.pay.api.message.PayRefundSuccessMessage;
|
||||||
|
import cn.iocoder.mall.pay.biz.component.DubboReferencePool;
|
||||||
|
import cn.iocoder.mall.pay.biz.dao.PayRefundMapper;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
||||||
|
import com.alibaba.dubbo.rpc.service.GenericService;
|
||||||
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RocketMQMessageListener(
|
||||||
|
topic = PayRefundSuccessMessage.TOPIC,
|
||||||
|
consumerGroup = "pay-consumer-group-" + PayRefundSuccessMessage.TOPIC
|
||||||
|
)
|
||||||
|
public class PayRefundSuccessConsumer extends AbstractPayNotifySuccessConsumer<PayRefundSuccessMessage>
|
||||||
|
implements RocketMQListener<PayRefundSuccessMessage> {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PayRefundMapper payRefundMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String invoke(PayRefundSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||||
|
// 查询支付交易
|
||||||
|
PayRefundDO refund = payRefundMapper.selectById(message.getRefundId());
|
||||||
|
Assert.notNull(refund, String.format("回调消息(%s) 退款单不能为空", message.toString()));
|
||||||
|
// 执行调用
|
||||||
|
GenericService genericService = referenceMeta.getService();
|
||||||
|
String methodName = referenceMeta.getMethodName();
|
||||||
|
return (String) genericService.$invoke(methodName, new String[]{String.class.getName(), Integer.class.getName()},
|
||||||
|
new Object[]{message.getOrderId(), refund.getPrice()});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterInvokeSuccess(PayRefundSuccessMessage message) {
|
||||||
|
PayRefundDO updateRefund = new PayRefundDO().setId(message.getRefundId()).setFinishTime(new Date());
|
||||||
|
payRefundMapper.update(updateRefund, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,32 +1,16 @@
|
||||||
package cn.iocoder.mall.pay.biz.mq;
|
package cn.iocoder.mall.pay.biz.mq;
|
||||||
|
|
||||||
import cn.iocoder.common.framework.util.DateUtil;
|
|
||||||
import cn.iocoder.common.framework.util.ExceptionUtil;
|
|
||||||
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
|
|
||||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
||||||
|
import cn.iocoder.mall.pay.biz.component.DubboReferencePool;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
|
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyLogMapper;
|
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
|
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyLogDO;
|
|
||||||
import com.alibaba.dubbo.config.ApplicationConfig;
|
|
||||||
import com.alibaba.dubbo.config.ReferenceConfig;
|
|
||||||
import com.alibaba.dubbo.config.RegistryConfig;
|
|
||||||
import com.alibaba.dubbo.rpc.service.GenericService;
|
import com.alibaba.dubbo.rpc.service.GenericService;
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import lombok.Data;
|
|
||||||
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
|
||||||
import org.apache.rocketmq.spring.core.RocketMQListener;
|
import org.apache.rocketmq.spring.core.RocketMQListener;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@ -34,119 +18,28 @@ import java.util.Date;
|
||||||
topic = PayTransactionSuccessMessage.TOPIC,
|
topic = PayTransactionSuccessMessage.TOPIC,
|
||||||
consumerGroup = "pay-consumer-group-" + PayTransactionSuccessMessage.TOPIC
|
consumerGroup = "pay-consumer-group-" + PayTransactionSuccessMessage.TOPIC
|
||||||
)
|
)
|
||||||
public class PayTransactionSuccessConsumer implements RocketMQListener<PayTransactionSuccessMessage> {
|
public class PayTransactionSuccessConsumer extends AbstractPayNotifySuccessConsumer<PayTransactionSuccessMessage>
|
||||||
|
implements RocketMQListener<PayTransactionSuccessMessage> {
|
||||||
|
|
||||||
@Data
|
|
||||||
private class ReferenceMeta {
|
|
||||||
|
|
||||||
private final ReferenceConfig config; // TODO 芋艿,后续需要做销毁
|
|
||||||
private final GenericService service;
|
|
||||||
private final String methodName;
|
|
||||||
|
|
||||||
private ReferenceMeta(ReferenceConfig config, GenericService service, String methodName) {
|
|
||||||
this.config = config;
|
|
||||||
this.service = service;
|
|
||||||
this.methodName = methodName;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Value("${dubbo.registry.address}")
|
|
||||||
private String dubboRegistryAddress;
|
|
||||||
@Value("${dubbo.application.name}")
|
|
||||||
private String dubboApplicationName;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
|
|
||||||
@Autowired
|
|
||||||
private PayTransactionNotifyLogMapper payTransactionNotifyLogMapper;
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PayTransactionMapper payTransactionMapper;
|
private PayTransactionMapper payTransactionMapper;
|
||||||
|
|
||||||
private LoadingCache<String, ReferenceMeta> referenceMetaCache = CacheBuilder.newBuilder()
|
|
||||||
.build(new CacheLoader<String, ReferenceMeta>() {
|
|
||||||
@Override
|
|
||||||
public ReferenceMeta load(String notifyUrl) {
|
|
||||||
return createGenericService(notifyUrl);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
private ReferenceMeta createGenericService(String notifyUrl) {
|
|
||||||
String[] notifyUrlParts = notifyUrl.split("#");
|
|
||||||
// 创建 ApplicationConfig 对象
|
|
||||||
ApplicationConfig application = new ApplicationConfig();
|
|
||||||
application.setName(dubboApplicationName);
|
|
||||||
// 创建 RegistryConfig 对象
|
|
||||||
RegistryConfig registry = new RegistryConfig();
|
|
||||||
// registry.setAddress("zookeeper://127.0.0.1:2181");
|
|
||||||
registry.setAddress(dubboRegistryAddress);
|
|
||||||
application.setRegistry(registry);
|
|
||||||
// 创建 ReferenceConfig 对象
|
|
||||||
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
|
|
||||||
reference.setInterface(notifyUrlParts[0]); // 弱类型接口名
|
|
||||||
reference.setGeneric(true); // 声明为泛化接口
|
|
||||||
reference.setApplication(application);
|
|
||||||
// 获得 GenericService 对象
|
|
||||||
GenericService genericService = reference.get();
|
|
||||||
// 构建最终的 ReferenceMeta 对象
|
|
||||||
return new ReferenceMeta(reference, genericService, notifyUrlParts[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
protected String invoke(PayTransactionSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||||
public void onMessage(PayTransactionSuccessMessage message) {
|
|
||||||
// 获得 ReferenceMeta 对象
|
|
||||||
ReferenceMeta referenceMeta = referenceMetaCache.getUnchecked(message.getNotifyUrl());
|
|
||||||
Assert.notNull(referenceMeta, String.format("notifyUrl(%s) 不存在对应的 ReferenceMeta 对象", message.getNotifyUrl()));
|
|
||||||
GenericService genericService = referenceMeta.getService();
|
|
||||||
String methodName = referenceMeta.getMethodName();
|
|
||||||
// 查询支付交易
|
// 查询支付交易
|
||||||
PayTransactionDO transaction = payTransactionMapper.selectById(message.getTransactionId());
|
PayTransactionDO transaction = payTransactionMapper.selectById(message.getTransactionId());
|
||||||
Assert.notNull(transaction, String.format("回调消息(%s) 订单交易不能为空", message.toString()));
|
Assert.notNull(transaction, String.format("回调消息(%s) 订单交易不能为空", message.toString()));
|
||||||
// 发起调用
|
// 执行调用
|
||||||
String response = null; // RPC / HTTP 调用的响应
|
GenericService genericService = referenceMeta.getService();
|
||||||
PayNotifyTaskDO updateTask = new PayNotifyTaskDO() // 更新 PayTransactionNotifyTaskDO 对象
|
String methodName = referenceMeta.getMethodName();
|
||||||
.setId(message.getId())
|
return (String) genericService.$invoke(methodName, new String[]{String.class.getName(), Integer.class.getName()},
|
||||||
.setLastExecuteTime(new Date())
|
new Object[]{message.getOrderId(), transaction.getPrice()});
|
||||||
.setNotifyTimes(message.getNotifyTimes() + 1);
|
|
||||||
try {
|
|
||||||
response = (String) genericService.$invoke(methodName, new String[]{String.class.getName(), Integer.class.getName()},
|
|
||||||
new Object[]{message.getOrderId(), transaction.getPrice()});
|
|
||||||
if ("success".equals(response)) { // 情况一,请求成功且返回成功
|
|
||||||
// 更新通知成功
|
|
||||||
updateTask.setStatus(PayTransactionNotifyStatusEnum.SUCCESS.getValue());
|
|
||||||
payTransactionNotifyTaskMapper.update(updateTask);
|
|
||||||
// 需要更新支付交易单通知应用成功
|
|
||||||
PayTransactionDO updateTransaction = new PayTransactionDO().setId(message.getTransactionId())
|
|
||||||
.setFinishTime(new Date());
|
|
||||||
payTransactionMapper.update(updateTransaction, null);
|
|
||||||
} else { // 情况二,请求成功且返回失败
|
|
||||||
// 更新通知请求成功,但是结果失败
|
|
||||||
handleFailure(updateTask, PayTransactionNotifyStatusEnum.REQUEST_SUCCESS.getValue());
|
|
||||||
payTransactionNotifyTaskMapper.update(updateTask);
|
|
||||||
}
|
|
||||||
} catch (Throwable e) { // 请求失败
|
|
||||||
// 更新通知请求失败
|
|
||||||
response = ExceptionUtil.getRootCauseMessage(e);
|
|
||||||
handleFailure(updateTask, PayTransactionNotifyStatusEnum.REQUEST_FAILURE.getValue());
|
|
||||||
payTransactionNotifyTaskMapper.update(updateTask);
|
|
||||||
// 抛出异常,回滚事务
|
|
||||||
throw e; // TODO 芋艿,此处不能抛出异常。因为,会导致 MQ + 定时任务多重试。此处的目标是,事务回滚 + 吃掉事务。另外,最后的 finally 的日志,要插入成功。
|
|
||||||
} finally {
|
|
||||||
// 插入 PayTransactionNotifyLogDO 日志
|
|
||||||
PayNotifyLogDO notifyLog = new PayNotifyLogDO().setNotifyId(message.getId())
|
|
||||||
.setRequest(message.getOrderId()).setResponse(response).setStatus(updateTask.getStatus());
|
|
||||||
payTransactionNotifyLogMapper.insert(notifyLog);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleFailure(PayNotifyTaskDO updateTask, Integer defaultStatus) {
|
@Override
|
||||||
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
protected void afterInvokeSuccess(PayTransactionSuccessMessage message) {
|
||||||
updateTask.setStatus(PayTransactionNotifyStatusEnum.FAILURE.getValue());
|
PayTransactionDO updateTransaction = new PayTransactionDO().setId(message.getTransactionId()).setFinishTime(new Date());
|
||||||
} else {
|
payTransactionMapper.update(updateTransaction, null);
|
||||||
updateTask.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
|
||||||
updateTask.setStatus(defaultStatus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ package cn.iocoder.mall.pay.biz.service;
|
||||||
import cn.iocoder.common.framework.util.DateUtil;
|
import cn.iocoder.common.framework.util.DateUtil;
|
||||||
import cn.iocoder.mall.pay.api.constant.PayNotifyType;
|
import cn.iocoder.mall.pay.api.constant.PayNotifyType;
|
||||||
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
|
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
|
||||||
|
import cn.iocoder.mall.pay.api.message.PayRefundSuccessMessage;
|
||||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
||||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
import cn.iocoder.mall.pay.biz.convert.PayNotifyConvert;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
|
import cn.iocoder.mall.pay.biz.dao.PayNotifyTaskMapper;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||||
|
@ -21,7 +22,7 @@ import java.util.Calendar;
|
||||||
public class PayNotifyServiceImpl {
|
public class PayNotifyServiceImpl {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
|
private PayNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RocketMQTemplate rocketMQTemplate;
|
private RocketMQTemplate rocketMQTemplate;
|
||||||
|
@ -35,8 +36,7 @@ public class PayNotifyServiceImpl {
|
||||||
// 保存到数据库
|
// 保存到数据库
|
||||||
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
||||||
// 发送 MQ 消息
|
// 发送 MQ 消息
|
||||||
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
sendNotifyMessage(payTransactionNotifyTask);
|
||||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTransactionNotifyTask(PayTransactionDO transaction, PayTransactionExtensionDO extension) {
|
public void addTransactionNotifyTask(PayTransactionDO transaction, PayTransactionExtensionDO extension) {
|
||||||
|
@ -47,8 +47,7 @@ public class PayNotifyServiceImpl {
|
||||||
.setTransactionId(extension.getTransactionId()).setTransactionExtensionId(extension.getId()));
|
.setTransactionId(extension.getTransactionId()).setTransactionExtensionId(extension.getId()));
|
||||||
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
||||||
// 3.2 发送 MQ
|
// 3.2 发送 MQ
|
||||||
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
sendNotifyMessage(payTransactionNotifyTask);
|
||||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PayNotifyTaskDO createBasePayNotifyTaskDO(String appId, String notifyUrl) {
|
private PayNotifyTaskDO createBasePayNotifyTaskDO(String appId, String notifyUrl) {
|
||||||
|
@ -60,4 +59,16 @@ public class PayNotifyServiceImpl {
|
||||||
.setNotifyUrl(notifyUrl);
|
.setNotifyUrl(notifyUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendNotifyMessage(PayNotifyTaskDO notifyTask) {
|
||||||
|
if (PayNotifyType.TRANSACTION.getValue().equals(notifyTask.getType())) {
|
||||||
|
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
||||||
|
PayNotifyConvert.INSTANCE.convertTransaction(notifyTask));
|
||||||
|
} else if (PayNotifyType.REFUND.getValue().equals(notifyTask.getType())) {
|
||||||
|
rocketMQTemplate.convertAndSend(PayRefundSuccessMessage.TOPIC,
|
||||||
|
PayNotifyConvert.INSTANCE.convertRefund(notifyTask));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(String.format("通知任务(%s) 无法发送通知消息", notifyTask.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,19 @@ import cn.iocoder.common.framework.util.ServiceExceptionUtil;
|
||||||
import cn.iocoder.common.framework.vo.CommonResult;
|
import cn.iocoder.common.framework.vo.CommonResult;
|
||||||
import cn.iocoder.mall.pay.api.PayRefundService;
|
import cn.iocoder.mall.pay.api.PayRefundService;
|
||||||
import cn.iocoder.mall.pay.api.bo.PayRefundSubmitBO;
|
import cn.iocoder.mall.pay.api.bo.PayRefundSubmitBO;
|
||||||
import cn.iocoder.mall.pay.api.constant.*;
|
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
|
||||||
|
import cn.iocoder.mall.pay.api.constant.PayRefundStatus;
|
||||||
|
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
|
||||||
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
||||||
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
|
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
|
||||||
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
|
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
|
||||||
import cn.iocoder.mall.pay.biz.client.RefundSuccessBO;
|
import cn.iocoder.mall.pay.biz.client.RefundSuccessBO;
|
||||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
import cn.iocoder.mall.pay.biz.convert.PayRefundConvert;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayRefundMapper;
|
import cn.iocoder.mall.pay.biz.dao.PayRefundMapper;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.*;
|
import cn.iocoder.mall.pay.biz.dataobject.PayAppDO;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||||
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
||||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -71,7 +76,7 @@ public class PayRefundServiceImpl implements PayRefundService {
|
||||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_SUCCESS.getCode());
|
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_SUCCESS.getCode());
|
||||||
}
|
}
|
||||||
// 插入 PayTransactionExtensionDO
|
// 插入 PayTransactionExtensionDO
|
||||||
PayRefundDO payRefundDO = PayTransactionConvert.INSTANCE.convert(payRefundSubmitDTO)
|
PayRefundDO payRefundDO = PayRefundConvert.INSTANCE.convert(payRefundSubmitDTO)
|
||||||
.setTransactionId(payTransaction.getId())
|
.setTransactionId(payTransaction.getId())
|
||||||
.setRefundCode(generateTransactionCode()) // TODO 芋艿,后续调整
|
.setRefundCode(generateTransactionCode()) // TODO 芋艿,后续调整
|
||||||
.setStatus(PayRefundStatus.WAITING.getValue())
|
.setStatus(PayRefundStatus.WAITING.getValue())
|
||||||
|
@ -120,7 +125,6 @@ public class PayRefundServiceImpl implements PayRefundService {
|
||||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_STATUS_NOT_WAITING.getCode());
|
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_STATUS_NOT_WAITING.getCode());
|
||||||
}
|
}
|
||||||
logger.info("[updateRefundSuccess][PayRefundDO({}) 更新为({})]", payRefund.getId(), status);
|
|
||||||
// 2.1 判断 PayTransactionDO ,增加已退款金额
|
// 2.1 判断 PayTransactionDO ,增加已退款金额
|
||||||
PayTransactionDO payTransaction = payTransactionService.getTransaction(payRefund.getTransactionId());
|
PayTransactionDO payTransaction = payTransactionService.getTransaction(payRefund.getTransactionId());
|
||||||
if (payTransaction == null) {
|
if (payTransaction == null) {
|
||||||
|
@ -137,7 +141,6 @@ public class PayRefundServiceImpl implements PayRefundService {
|
||||||
if (updateCounts == 0) { // 保证不超退 TODO 这种类型,需要思考下。需要返回错误,但是又要保证事务回滚
|
if (updateCounts == 0) { // 保证不超退 TODO 这种类型,需要思考下。需要返回错误,但是又要保证事务回滚
|
||||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_PRICE_EXCEED.getCode());
|
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_PRICE_EXCEED.getCode());
|
||||||
}
|
}
|
||||||
logger.info("[updateRefundSuccess][PayTransactionDO({}) 更新为已支付]", payTransaction.getId());
|
|
||||||
// 3 新增 PayNotifyTaskDO
|
// 3 新增 PayNotifyTaskDO
|
||||||
payNotifyService.addRefundNotifyTask(payRefund);
|
payNotifyService.addRefundNotifyTask(payRefund);
|
||||||
// 返回结果
|
// 返回结果
|
||||||
|
|
|
@ -17,7 +17,7 @@ import cn.iocoder.mall.pay.biz.client.TransactionSuccessBO;
|
||||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionExtensionMapper;
|
import cn.iocoder.mall.pay.biz.dao.PayTransactionExtensionMapper;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
|
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
|
||||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
|
import cn.iocoder.mall.pay.biz.dao.PayNotifyTaskMapper;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayAppDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayAppDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
||||||
|
@ -40,7 +40,7 @@ public class PayTransactionServiceImpl implements PayTransactionService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private PayTransactionExtensionMapper payTransactionExtensionMapper;
|
private PayTransactionExtensionMapper payTransactionExtensionMapper;
|
||||||
@Autowired
|
@Autowired
|
||||||
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
|
private PayNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PayAppServiceImpl payAppService;
|
private PayAppServiceImpl payAppService;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyLogMapper">
|
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayNotifyLogMapper">
|
||||||
|
|
||||||
<!--<sql id="FIELDS">-->
|
<!--<sql id="FIELDS">-->
|
||||||
<!--id, transaction_id, transaction_extension_id, app_id, order_id,-->
|
<!--id, transaction_id, transaction_extension_id, app_id, order_id,-->
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
<!--</sql>-->
|
<!--</sql>-->
|
||||||
|
|
||||||
<insert id="insert" parameterType="PayNotifyLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
<insert id="insert" parameterType="PayNotifyLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||||
INSERT INTO transaction_notify_log (
|
INSERT INTO notify_log (
|
||||||
notify_id, request, response, status
|
notify_id, request, response, status
|
||||||
) VALUES (
|
) VALUES (
|
||||||
#{notifyId}, #{request}, #{response}, #{status}
|
#{notifyId}, #{request}, #{response}, #{status}
|
|
@ -1,25 +1,37 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper">
|
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayNotifyTaskMapper">
|
||||||
|
|
||||||
<sql id="FIELDS">
|
<sql id="FIELDS">
|
||||||
id, transaction_id, transaction_extension_id, app_id, order_id,
|
id, app_id, type,
|
||||||
status, next_notify_time, last_execute_time, notify_times, max_notify_times,
|
status, next_notify_time, last_execute_time, notify_times, max_notify_times,
|
||||||
create_time
|
create_time
|
||||||
</sql>
|
</sql>
|
||||||
|
|
||||||
|
<resultMap id="PayNotifyTaskResultMap" type="PayNotifyTaskDO">
|
||||||
|
<result property="transaction" column="transaction"
|
||||||
|
javaType="cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO$Transaction"
|
||||||
|
typeHandler="cn.iocoder.common.framework.mybatis.JSONTypeHandler"/>
|
||||||
|
<result property="refund" column="refund"
|
||||||
|
javaType="cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO$Refund"
|
||||||
|
typeHandler="cn.iocoder.common.framework.mybatis.JSONTypeHandler"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<insert id="insert" parameterType="PayNotifyTaskDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
<insert id="insert" parameterType="PayNotifyTaskDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||||
INSERT INTO transaction_notify_task (
|
INSERT INTO notify_task (
|
||||||
transaction_id, transaction_extension_id, app_id, order_id,
|
app_id, type,
|
||||||
status, next_notify_time, notify_times, max_notify_times
|
status, next_notify_time, notify_times, max_notify_times,
|
||||||
|
`transaction`, refund
|
||||||
) VALUES (
|
) VALUES (
|
||||||
#{transactionId}, #{transactionExtensionId}, #{appId}, #{orderId},
|
#{appId}, #{type},
|
||||||
#{status}, #{nextNotifyTime}, #{notifyTimes}, #{maxNotifyTimes}
|
#{status}, #{nextNotifyTime}, #{notifyTimes}, #{maxNotifyTimes},
|
||||||
|
#{transaction, typeHandler=cn.iocoder.common.framework.mybatis.JSONTypeHandler},
|
||||||
|
#{refund, typeHandler=cn.iocoder.common.framework.mybatis.JSONTypeHandler}
|
||||||
)
|
)
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<update id="update" parameterType="PayNotifyTaskDO">
|
<update id="update" parameterType="PayNotifyTaskDO">
|
||||||
UPDATE transaction_notify_task
|
UPDATE notify_task
|
||||||
<set>
|
<set>
|
||||||
<if test="status != null">
|
<if test="status != null">
|
||||||
, status = #{status}
|
, status = #{status}
|
||||||
|
@ -37,10 +49,10 @@
|
||||||
WHERE id = #{id}
|
WHERE id = #{id}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="selectByNotify" resultType="PayNotifyTaskDO">
|
<select id="selectByNotify" resultMap="PayNotifyTaskResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
<include refid="FIELDS"/>
|
<include refid="FIELDS"/>
|
||||||
FROM transaction_notify_task
|
FROM notify_task
|
||||||
WHERE status IN (1, 4, 5)
|
WHERE status IN (1, 4, 5)
|
||||||
AND next_notify_time <![CDATA[ <= ]]> NOW()
|
AND next_notify_time <![CDATA[ <= ]]> NOW()
|
||||||
AND last_execute_time > next_notify_time
|
AND last_execute_time > next_notify_time
|
|
@ -3,7 +3,7 @@
|
||||||
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayRefundMapper">
|
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayRefundMapper">
|
||||||
|
|
||||||
<sql id="FIELDS">
|
<sql id="FIELDS">
|
||||||
id, transaction_id, refund_cod, app_id, create_ip, order_id,
|
id, transaction_id, refund_code, app_id, create_ip, order_id,
|
||||||
order_description, price, status,
|
order_description, price, status,
|
||||||
finish_time, notify_url, extension_data, refund_channel, refund_time, notify_time,
|
finish_time, notify_url, extension_data, refund_channel, refund_time, notify_time,
|
||||||
trade_no, create_time
|
trade_no, create_time
|
||||||
|
|
|
@ -56,8 +56,8 @@
|
||||||
|
|
||||||
<update id="updateForRefundTotal">
|
<update id="updateForRefundTotal">
|
||||||
UPDATE `transaction`
|
UPDATE `transaction`
|
||||||
SET refundTotal = refundTotal + ${refundTotalIncr}
|
SET refund_total = refund_total + ${refundTotalIncr}
|
||||||
WHERE price >= refundTotal + ${refundTotalIncr}
|
WHERE price >= refund_total + ${refundTotalIncr}
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
<select id="selectByAppIdAndOrderId" resultType="PayTransactionDO">
|
<select id="selectByAppIdAndOrderId" resultType="PayTransactionDO">
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package cn.iocoder.mall.pay.biz.service;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.pay.api.PayRefundService;
|
||||||
|
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||||
|
public class PayRefundServiceImplTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PayRefundService payRefundService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSubmitRefund() {
|
||||||
|
PayRefundSubmitDTO payRefundSubmitDTO = new PayRefundSubmitDTO()
|
||||||
|
.setAppId("POd4RC6a")
|
||||||
|
.setCreateIp("127.0.0.1")
|
||||||
|
.setOrderId("135")
|
||||||
|
.setOrderDescription("测试下退款")
|
||||||
|
.setPrice(1);
|
||||||
|
payRefundService.submitRefund(payRefundSubmitDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cn.iocoder.mall.pay.biz.service;
|
||||||
|
|
||||||
|
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||||
|
public class PayTransactionServiceImplTest {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import cn.iocoder.mall.promotion.api.bo.*;
|
||||||
import cn.iocoder.mall.promotion.api.constant.CouponTemplateStatusEnum;
|
import cn.iocoder.mall.promotion.api.constant.CouponTemplateStatusEnum;
|
||||||
import cn.iocoder.mall.promotion.api.dto.*;
|
import cn.iocoder.mall.promotion.api.dto.*;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotNull;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface CouponService {
|
public interface CouponService {
|
||||||
|
@ -79,7 +80,8 @@ public interface CouponService {
|
||||||
* @param couponCardId 优惠劵编号
|
* @param couponCardId 优惠劵编号
|
||||||
* @return 是否成功
|
* @return 是否成功
|
||||||
*/
|
*/
|
||||||
CommonResult<Boolean> useCouponCard(Integer userId, Integer couponCardId);
|
CommonResult<Boolean> useCouponCard(Integer userId,
|
||||||
|
@NotNull(message = "优惠劵编号不能为空") Integer couponCardId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取消优惠劵的使用
|
* 取消优惠劵的使用
|
||||||
|
|
Loading…
Reference in New Issue