后端:增加部分退款逻辑
parent
957b2eb893
commit
1be40cb195
|
@ -1,99 +0,0 @@
|
|||
package cn.iocoder.mall.pay.application.controller.users;
|
||||
|
||||
import cn.iocoder.common.framework.util.HttpUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.pay.api.PayTransactionService;
|
||||
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
|
||||
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
|
||||
import com.alibaba.dubbo.config.annotation.Reference;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 示例 Controller
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("users/demo")
|
||||
public class PayDemoController {
|
||||
|
||||
@Reference(validation = "true")
|
||||
private PayTransactionService payTransactionService;
|
||||
|
||||
@PostMapping("/create_order")
|
||||
public void createOrder(HttpServletRequest request,
|
||||
@RequestParam("orderId") String orderId) {
|
||||
// 创建业务订单
|
||||
// ...
|
||||
|
||||
// 调用【支付服务】,创建交易订单
|
||||
PayTransactionCreateDTO payTransactionCreateDTO = new PayTransactionCreateDTO()
|
||||
.setAppId("POd4RC6a")
|
||||
.setCreateIp(HttpUtil.getIp(request))
|
||||
.setOrderId(orderId)
|
||||
.setOrderSubject("商品名" )
|
||||
.setOrderDescription("商品描述")
|
||||
.setOrderMemo("商品备注")
|
||||
.setPrice(10)
|
||||
.setExpireTime(new Date());
|
||||
CommonResult<PayTransactionBO> result = payTransactionService.createTransaction(payTransactionCreateDTO);
|
||||
Assert.isTrue(result.isSuccess(), "一定会成功的");
|
||||
}
|
||||
|
||||
@PostMapping("/pingxx")
|
||||
public String pingxx() {
|
||||
return "{\n" +
|
||||
" \"id\": \"ch_n5COmHGG8iX5TWf5qDynvTaP\",\n" +
|
||||
" \"object\": \"charge\",\n" +
|
||||
" \"created\": 1552445643,\n" +
|
||||
" \"livemode\": false,\n" +
|
||||
" \"paid\": false,\n" +
|
||||
" \"refunded\": false,\n" +
|
||||
" \"reversed\": false,\n" +
|
||||
" \"app\": \"app_aTyfXDjrvzDSbLuz\",\n" +
|
||||
" \"channel\": \"wx_pub\",\n" +
|
||||
" \"order_no\": \"1552445643093\",\n" +
|
||||
" \"client_ip\": \"127.0.0.1\",\n" +
|
||||
" \"amount\": 1,\n" +
|
||||
" \"amount_settle\": 1,\n" +
|
||||
" \"currency\": \"cny\",\n" +
|
||||
" \"subject\": \"测试商品\",\n" +
|
||||
" \"body\": \"测试描述\",\n" +
|
||||
" \"time_paid\": null,\n" +
|
||||
" \"time_expire\": 1552452843,\n" +
|
||||
" \"time_settle\": null,\n" +
|
||||
" \"transaction_no\": null,\n" +
|
||||
" \"refunds\": {\n" +
|
||||
" \"object\": \"list\",\n" +
|
||||
" \"url\": \"/v1/charges/ch_n5COmHGG8iX5TWf5qDynvTaP/refunds\",\n" +
|
||||
" \"has_more\": false,\n" +
|
||||
" \"data\": []\n" +
|
||||
" },\n" +
|
||||
" \"amount_refunded\": 0,\n" +
|
||||
" \"failure_code\": null,\n" +
|
||||
" \"failure_msg\": null,\n" +
|
||||
" \"metadata\": {},\n" +
|
||||
" \"credential\": {\n" +
|
||||
" \"object\": \"credential\",\n" +
|
||||
" \"wx_pub\": {\n" +
|
||||
" \"appId\": \"wxytom5krtuf54qjff\",\n" +
|
||||
" \"timeStamp\": \"1552445643\",\n" +
|
||||
" \"nonceStr\": \"5cc0206f78d8bf931980f5132d5ce394\",\n" +
|
||||
" \"package\": \"prepay_id=1101000000190313rx9y5oahkkcsm5gg\",\n" +
|
||||
" \"signType\": \"MD5\",\n" +
|
||||
" \"paySign\": \"9F6E80E89439575B8414FA56ADB07228\"\n" +
|
||||
" }\n" +
|
||||
" },\n" +
|
||||
" \"extra\": {\n" +
|
||||
" \"open_id\": \"just_for_test\"\n" +
|
||||
" },\n" +
|
||||
" \"description\": null\n" +
|
||||
"}";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package cn.iocoder.mall.pay.application.controller.users;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.pay.api.PayRefundService;
|
||||
import cn.iocoder.mall.pay.api.constant.PayChannelEnum;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("users/refund") // TODO 芋艿,理论来说,是用户无关的。这里先酱紫先~
|
||||
public class PayRefundController {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Autowired
|
||||
private PayRefundService payRefundService;
|
||||
|
||||
@PostMapping(value = "pingxx_refund_success", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String pingxxRefundSuccess(HttpServletRequest request) throws IOException {
|
||||
logger.info("[pingxxRefundSuccess][被回调]");
|
||||
// 读取 webhook
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = request.getReader()) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line);
|
||||
}
|
||||
}
|
||||
|
||||
CommonResult<Boolean> result = payRefundService.updateRefundSuccess(PayChannelEnum.PINGXX.getId(), sb.toString());
|
||||
if (result.isError()) {
|
||||
logger.error("[pingxxRefundSuccess][message({}) result({})]", sb, result);
|
||||
return "failure";
|
||||
}
|
||||
return "success";
|
||||
}
|
||||
|
||||
}
|
|
@ -25,12 +25,12 @@ public class PayTransactionController {
|
|||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Reference(validation = "true")
|
||||
private PayTransactionService payService;
|
||||
private PayTransactionService payTransactionService;
|
||||
|
||||
@GetMapping("/get")
|
||||
public CommonResult<PayTransactionBO> get(@RequestParam("appId") String appId,
|
||||
@RequestParam("orderId") String orderId) {
|
||||
return payService.getTransaction(UserSecurityContextHolder.getContext().getUserId(), appId, orderId);
|
||||
return payTransactionService.getTransaction(UserSecurityContextHolder.getContext().getUserId(), appId, orderId);
|
||||
}
|
||||
|
||||
@PostMapping("/submit") // TODO api 注释
|
||||
|
@ -43,13 +43,13 @@ public class PayTransactionController {
|
|||
.setAppId(appId).setOrderId(orderId).setPayChannel(payChannel)
|
||||
.setCreateIp(HttpUtil.getIp(request));
|
||||
// 提交支付提交
|
||||
return payService.submitTransaction(payTransactionSubmitDTO);
|
||||
return payTransactionService.submitTransaction(payTransactionSubmitDTO);
|
||||
}
|
||||
|
||||
@PostMapping(value = "pingxx_pay_success", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
// @GetMapping(value = "pingxx_pay_success")
|
||||
public String pingxxSuccess(HttpServletRequest request) throws IOException {
|
||||
logger.info("[pingxxSuccess][被回调]");
|
||||
public String pingxxPaySuccess(HttpServletRequest request) throws IOException {
|
||||
logger.info("[pingxxPaySuccess][被回调]");
|
||||
// 读取 webhook
|
||||
StringBuilder sb = new StringBuilder();
|
||||
try (BufferedReader reader = request.getReader()) {
|
||||
|
@ -62,12 +62,12 @@ public class PayTransactionController {
|
|||
// JSONObject bodyObj = JSON.parseObject(sb.toString());
|
||||
// bodyObj.put("webhookId", bodyObj.remove("id"));
|
||||
// String body = bodyObj.toString();
|
||||
CommonResult<Boolean> result = payService.updateTransactionPaySuccess(PayChannelEnum.PINGXX.getId(), sb.toString());
|
||||
CommonResult<Boolean> result = payTransactionService.updateTransactionPaySuccess(PayChannelEnum.PINGXX.getId(), sb.toString());
|
||||
if (result.isError()) {
|
||||
logger.error("[pingxxSuccess][message({}) result({})]", sb, result);
|
||||
logger.error("[pingxxPaySuccess][message({}) result({})]", sb, result);
|
||||
return "failure";
|
||||
}
|
||||
return result.isSuccess() ? "success" : "failure";
|
||||
return "success";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package cn.iocoder.mall.pay.api;
|
||||
|
||||
public interface PayDemoService {
|
||||
|
||||
String updatePaySuccess(String orderId);
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cn.iocoder.mall.pay.api;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.pay.api.bo.PayRefundSubmitBO;
|
||||
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
||||
|
||||
public interface PayRefundService {
|
||||
|
||||
CommonResult<PayRefundSubmitBO> submitRefund(PayRefundSubmitDTO payRefundSubmitDTO);
|
||||
|
||||
/**
|
||||
* 更新退款支付成功
|
||||
*
|
||||
* 该接口用于不同支付平台,退款成功后,回调该接口
|
||||
*
|
||||
* @param payChannel 支付渠道
|
||||
* @param params 回调参数。
|
||||
* 因为不同平台,能够提供的参数不同,所以使用 String 类型统一接收,然后在使用不同的 AbstractPaySDK 进行处理。
|
||||
* @return 是否支付成功
|
||||
*/
|
||||
CommonResult<Boolean> updateRefundSuccess(Integer payChannel, String params);
|
||||
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
package cn.iocoder.mall.pay.api;
|
||||
|
||||
public interface RefundService {
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package cn.iocoder.mall.pay.api.bo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 退款单结果 BO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayRefundSubmitBO {
|
||||
|
||||
/**
|
||||
* 退款
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
}
|
|
@ -11,12 +11,20 @@ public enum PayErrorCodeEnum {
|
|||
PAY_APP_NOT_FOUND(1004000000, "App 不存在"),
|
||||
PAY_APP_IS_DISABLE(1004000001, "App 已经被禁用"),
|
||||
|
||||
// ========== TRANSACTION 模块 ==========
|
||||
// ========== TRANSACTION PAY 模块 ==========
|
||||
PAY_TRANSACTION_NOT_FOUND(100401000, "支付交易单不存在"),
|
||||
PAY_TRANSACTION_STATUS_IS_NOT_WAITING(100401001, "支付交易单不处于待支付"),
|
||||
PAY_TRANSACTION_EXTENSION_NOT_FOUND(100401002, "支付交易拓展单不存在"),
|
||||
PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING(100401003, "支付交易拓展单不处于待支付"),
|
||||
PAY_TRANSACTION_ERROR_USER(100401004, "支付交易单用户不正确"),
|
||||
PAY_TRANSACTION_STATUS_IS_NOT_SUCCESS(100401002, "支付交易单不处于已支付"),
|
||||
PAY_TRANSACTION_ERROR_USER(100401003, "支付交易单用户不正确"),
|
||||
|
||||
PAY_TRANSACTION_EXTENSION_NOT_FOUND(100401050, "支付交易拓展单不存在"),
|
||||
PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING(100401051, "支付交易拓展单不处于待支付"),
|
||||
PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_SUCCESS(100401052, "支付交易单不处于已支付"),
|
||||
|
||||
// ========== TRANSACTION REFUND 模块 ==========
|
||||
PAY_REFUND_PRICE_EXCEED(100402000, "退款金额超过支付交易单可退金额"),
|
||||
PAY_REFUND_NOT_FOUND(100402001, "退款单不存在"),
|
||||
PAY_REFUND_STATUS_NOT_WAITING(100402002, "退款单不处于待处理"),
|
||||
;
|
||||
|
||||
private final int code;
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package cn.iocoder.mall.pay.api.constant;
|
||||
|
||||
/**
|
||||
* 支付通知类型
|
||||
*/
|
||||
public enum PayNotifyType {
|
||||
|
||||
TRANSACTION(1, "支付"),
|
||||
REFUND(2, "退款"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private Integer value;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
|
||||
PayNotifyType(Integer value, String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public PayNotifyType setValue(Integer value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public PayNotifyType setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package cn.iocoder.mall.pay.api.constant;
|
||||
|
||||
/**
|
||||
* 支付退款状态枚举
|
||||
*/
|
||||
public enum PayRefundStatus {
|
||||
|
||||
WAITING(1, "处理中"),
|
||||
SUCCESS(2, "成功"),
|
||||
FAILURE(3, "失败"), // 例如说,支付单超时
|
||||
;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer value;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
|
||||
PayRefundStatus(Integer value, String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public PayRefundStatus setValue(Integer value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public PayRefundStatus setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@ package cn.iocoder.mall.pay.api.constant;
|
|||
*/
|
||||
public enum PayTransactionStatusEnum {
|
||||
|
||||
WAITTING(1, "等待支付"),
|
||||
WAITING(1, "等待支付"),
|
||||
SUCCESS(2, "支付成功"),
|
||||
CANCEL(3, "取消支付"), // 例如说,支付单超时
|
||||
;
|
||||
|
@ -42,4 +42,4 @@ public enum PayTransactionStatusEnum {
|
|||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package cn.iocoder.mall.pay.api.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付退款创建 DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayRefundSubmitDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotEmpty(message = "应用编号不能为空")
|
||||
private String appId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
@NotEmpty(message = "IP 不能为空")
|
||||
private String createIp;
|
||||
/**
|
||||
* 业务线的订单编号
|
||||
*/
|
||||
@NotEmpty(message = "订单号不能为空")
|
||||
private String orderId;
|
||||
/**
|
||||
* 退款描述
|
||||
*/
|
||||
@NotEmpty(message = "退款描述不能为空")
|
||||
@Length(max = 128, message = "退款描述长度不能超过128")
|
||||
private String orderDescription;
|
||||
/**
|
||||
* 支付金额,单位:分。
|
||||
*/
|
||||
@NotNull(message = "金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "金额必须大于零")
|
||||
private Integer price;
|
||||
|
||||
}
|
|
@ -3,9 +3,9 @@ package cn.iocoder.mall.pay.api.dto;
|
|||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.DecimalMin;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package cn.iocoder.mall.pay.api.message;
|
||||
|
||||
/**
|
||||
* 支付退款成功的消息对象
|
||||
*/
|
||||
public class PayRefundSuccessMessage {
|
||||
|
||||
public static final String TOPIC = "PAY_REFUND_SUCCESS";
|
||||
|
||||
/**
|
||||
* 任务编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 退款单编号
|
||||
*/
|
||||
private Integer refundId;
|
||||
/**
|
||||
* 交易编号
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 应用订单编号
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,12 +3,12 @@ package cn.iocoder.mall.pay.api.message;
|
|||
/**
|
||||
* 支付交易单支付成功的消息对象
|
||||
*/
|
||||
public class PayTransactionPaySuccessMessage {
|
||||
public class PayTransactionSuccessMessage {
|
||||
|
||||
public static final String TOPIC = "PAY_TRANSACTION_PAY_SUCCESS";
|
||||
public static final String TOPIC = "PAY_TRANSACTION_SUCCESS";
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
* 任务编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
|
@ -36,7 +36,7 @@ public class PayTransactionPaySuccessMessage {
|
|||
return id;
|
||||
}
|
||||
|
||||
public PayTransactionPaySuccessMessage setId(Integer id) {
|
||||
public PayTransactionSuccessMessage setId(Integer id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public class PayTransactionPaySuccessMessage {
|
|||
return appId;
|
||||
}
|
||||
|
||||
public PayTransactionPaySuccessMessage setAppId(String appId) {
|
||||
public PayTransactionSuccessMessage setAppId(String appId) {
|
||||
this.appId = appId;
|
||||
return this;
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ public class PayTransactionPaySuccessMessage {
|
|||
return orderId;
|
||||
}
|
||||
|
||||
public PayTransactionPaySuccessMessage setOrderId(String orderId) {
|
||||
public PayTransactionSuccessMessage setOrderId(String orderId) {
|
||||
this.orderId = orderId;
|
||||
return this;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ public class PayTransactionPaySuccessMessage {
|
|||
return notifyTimes;
|
||||
}
|
||||
|
||||
public PayTransactionPaySuccessMessage setNotifyTimes(Integer notifyTimes) {
|
||||
public PayTransactionSuccessMessage setNotifyTimes(Integer notifyTimes) {
|
||||
this.notifyTimes = notifyTimes;
|
||||
return this;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ public class PayTransactionPaySuccessMessage {
|
|||
return notifyUrl;
|
||||
}
|
||||
|
||||
public PayTransactionPaySuccessMessage setNotifyUrl(String notifyUrl) {
|
||||
public PayTransactionSuccessMessage setNotifyUrl(String notifyUrl) {
|
||||
this.notifyUrl = notifyUrl;
|
||||
return this;
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ public class PayTransactionPaySuccessMessage {
|
|||
return transactionId;
|
||||
}
|
||||
|
||||
public PayTransactionPaySuccessMessage setTransactionId(Integer transactionId) {
|
||||
public PayTransactionSuccessMessage setTransactionId(Integer transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
return this;
|
||||
}
|
|
@ -63,6 +63,24 @@
|
|||
<artifactId>rocketmq-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.boot</groupId> <!-- 引入该包,为了写单元测试用 -->
|
||||
<artifactId>dubbo-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.curator</groupId> <!-- 引入该包,为了写单元测试用 -->
|
||||
<artifactId>curator-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId> <!-- 引入该包,为了写单元测试用 -->
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.mall.pay.biz.client;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
||||
|
||||
|
@ -8,12 +9,46 @@ import java.util.Map;
|
|||
|
||||
public abstract class AbstractPaySDK {
|
||||
|
||||
// extra 属性,用于支持不同支付平台的拓展字段。例如说,微信公众号支付,需要多传递一个 openid
|
||||
/**
|
||||
* 提交支付请求给支付平台,并返回请求结果
|
||||
*
|
||||
* @param transaction 支付交易数据
|
||||
* @param transactionExtension 交易扩展数据
|
||||
* @param extra 额外参数。用于支持不同支付平台的拓展字段。例如说,微信公众号支付,需要多传递一个 openid
|
||||
* @return 请求结果
|
||||
*/
|
||||
public abstract CommonResult<String> submitTransaction(PayTransactionDO transaction,
|
||||
PayTransactionExtensionDO transactionExtension,
|
||||
Map<String, Object> extra);
|
||||
|
||||
/**
|
||||
* 解析支付成功回调的参数,返回 TransactionSuccessBO 对象
|
||||
*
|
||||
* @param params 回调的参数
|
||||
* @return 解析结果
|
||||
*/
|
||||
// TODO 芋艿,理论来说不会出现解析失败的情况,先返回这个参数列。等后面封装支付宝和微信支付的时候,在看看。
|
||||
public abstract CommonResult<TransactionPaySuccessBO> parseTransactionPaySuccessParams(String params);
|
||||
public abstract CommonResult<TransactionSuccessBO> parseTransactionSuccessParams(String params);
|
||||
|
||||
}
|
||||
/**
|
||||
* 提交退款请求给支付平台,并返回请求结果
|
||||
*
|
||||
* @param refund 退款数据
|
||||
* @param transactionExtension 交易扩展数据
|
||||
* @param extra 额外参数。用于支持不同支付平台的拓展字段。
|
||||
* @return 请求结果
|
||||
*/
|
||||
public abstract CommonResult<String> submitRefund(PayRefundDO refund,
|
||||
PayTransactionExtensionDO transactionExtension,
|
||||
Map<String, Object> extra);
|
||||
|
||||
/**
|
||||
* 解析退款成功回调的参数,返回 RefundSuccessBO 对象
|
||||
*
|
||||
* @param params 回调的参数
|
||||
* @return 解析结果
|
||||
*/
|
||||
// TODO 芋艿,理论来说不会出现解析失败的情况,先返回这个参数列。等后面封装支付宝和微信支付的时候,在看看。
|
||||
public abstract CommonResult<RefundSuccessBO> parseRefundSuccessParams(String params);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cn.iocoder.mall.pay.biz.client;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
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 com.alibaba.fastjson.JSON;
|
||||
|
@ -9,6 +10,7 @@ import com.google.common.collect.ImmutableMap;
|
|||
import com.pingplusplus.Pingpp;
|
||||
import com.pingplusplus.exception.*;
|
||||
import com.pingplusplus.model.Charge;
|
||||
import com.pingplusplus.model.Refund;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
@ -23,44 +25,29 @@ public class PingxxPaySDK extends AbstractPaySDK {
|
|||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<String> submitTransaction(PayTransactionDO transaction, PayTransactionExtensionDO transactionExtension, Map<String, Object> extra) {
|
||||
public CommonResult<String> submitTransaction(PayTransactionDO transaction,
|
||||
PayTransactionExtensionDO transactionExtension,
|
||||
Map<String, Object> extra) {
|
||||
Map<String, Object> reqObj = createChargeRequest(transaction, transactionExtension, extra);
|
||||
// 请求ping++
|
||||
try {
|
||||
Charge charge = Charge.create(reqObj);
|
||||
System.out.println(charge.toString());
|
||||
return CommonResult.success(charge.toString());
|
||||
} catch (AuthenticationException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvalidRequestException e) {
|
||||
e.printStackTrace();
|
||||
} catch (APIConnectionException e) {
|
||||
e.printStackTrace();
|
||||
} catch (APIException e) {
|
||||
e.printStackTrace();
|
||||
} catch (ChannelException e) {
|
||||
e.printStackTrace();
|
||||
} catch (RateLimitException e) {
|
||||
} catch (AuthenticationException | InvalidRequestException |
|
||||
APIConnectionException | APIException |
|
||||
ChannelException | RateLimitException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e); // TODO 芋艿,后续优化
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<TransactionPaySuccessBO> parseTransactionPaySuccessParams(String params) {
|
||||
JSONObject paramsObj = JSON.parseObject(params);
|
||||
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||
TransactionPaySuccessBO transactionPaySuccessBO = new TransactionPaySuccessBO()
|
||||
.setTransactionCode(chargeObj.getString("order_no"))
|
||||
.setPaymentTime(new Date(chargeObj.getLong("time_paid") * 1000))
|
||||
.setTradeNo(chargeObj.getString("transaction_no"));
|
||||
return CommonResult.success(transactionPaySuccessBO);
|
||||
}
|
||||
|
||||
private Map<String, Object> createChargeRequest(PayTransactionDO transaction, PayTransactionExtensionDO transactionExtension, Map<String, Object> extra) {
|
||||
private static Map<String, Object> createChargeRequest(PayTransactionDO transaction,
|
||||
PayTransactionExtensionDO transactionExtension,
|
||||
Map<String, Object> extra) {
|
||||
// 计算支付渠道和支付额外参数
|
||||
String channel = "wx_pub"; // 因为 ping++ 是用来做模拟支付的渠道,所以这里强制就选择了 wx_pub 微信公众号支付
|
||||
extra = new HashMap<>(); // TODO 临时
|
||||
extra = new HashMap<>(); // TODO 临时,后面用 extra
|
||||
extra.put("open_id", "just_for_test");
|
||||
// 生成支付对象
|
||||
Map<String, Object> reqObj = new HashMap<>();
|
||||
|
@ -77,17 +64,72 @@ public class PingxxPaySDK extends AbstractPaySDK {
|
|||
return reqObj;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
PayTransactionDO transaction = new PayTransactionDO();
|
||||
transaction.setOrderSubject("测试商品");
|
||||
transaction.setOrderDescription("测试描述");
|
||||
transaction.setPrice(1);
|
||||
|
||||
PayTransactionExtensionDO extension = new PayTransactionExtensionDO();
|
||||
extension.setTransactionCode(System.currentTimeMillis() + "");
|
||||
extension.setCreateIp("127.0.0.1");
|
||||
|
||||
new PingxxPaySDK().submitTransaction(transaction, extension, null);
|
||||
@Override
|
||||
public CommonResult<TransactionSuccessBO> parseTransactionSuccessParams(String params) {
|
||||
JSONObject paramsObj = JSON.parseObject(params);
|
||||
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||
TransactionSuccessBO transactionPaySuccessBO = new TransactionSuccessBO()
|
||||
.setTransactionCode(chargeObj.getString("order_no"))
|
||||
.setPaymentTime(new Date(chargeObj.getLong("time_paid") * 1000))
|
||||
.setTradeNo(chargeObj.getString("transaction_no"));
|
||||
return CommonResult.success(transactionPaySuccessBO);
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public CommonResult<String> submitRefund(PayRefundDO refund,
|
||||
PayTransactionExtensionDO transactionExtension,
|
||||
Map<String, Object> extra) {
|
||||
// 解析出 chargeId
|
||||
JSONObject paramsObj = JSON.parseObject(transactionExtension.getExtensionData());
|
||||
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||
String chargeId = chargeObj.getString("id");
|
||||
// 请求ping++
|
||||
Map<String, Object> reqObj = createRefundRequest(chargeId, refund.getOrderDescription(), refund.getPrice());
|
||||
try {
|
||||
Refund pingxxRefund = Refund.create(chargeId, reqObj);
|
||||
System.out.println(pingxxRefund.toString());
|
||||
return CommonResult.success(pingxxRefund.toString());
|
||||
} catch (AuthenticationException | InvalidRequestException |
|
||||
APIConnectionException | APIException |
|
||||
ChannelException | RateLimitException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e); // TODO 芋艿,后续优化
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<RefundSuccessBO> parseRefundSuccessParams(String params) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, Object> createRefundRequest(String chargeId, String orderDescription, Integer price) {
|
||||
Map<String, Object> reqObj = new HashMap<>();
|
||||
// reqObj.put("CHARGE_ID", chargeId);
|
||||
reqObj.put("description", orderDescription);
|
||||
reqObj.put("amount", price);
|
||||
return reqObj;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (false) { // 测试支付请求
|
||||
PayTransactionDO transaction = new PayTransactionDO();
|
||||
transaction.setOrderSubject("测试商品");
|
||||
transaction.setOrderDescription("测试描述");
|
||||
transaction.setPrice(1);
|
||||
|
||||
PayTransactionExtensionDO extension = new PayTransactionExtensionDO();
|
||||
extension.setTransactionCode(System.currentTimeMillis() + "");
|
||||
extension.setCreateIp("127.0.0.1");
|
||||
|
||||
new PingxxPaySDK().submitTransaction(transaction, extension, null);
|
||||
}
|
||||
if (true) { // 测试退款请求
|
||||
PayRefundDO refund = new PayRefundDO().setPrice(1).setOrderDescription("测试描述");
|
||||
PayTransactionExtensionDO transactionExtension = new PayTransactionExtensionDO()
|
||||
.setExtensionData("{\"id\":\"evt_400190423100354205607502\",\"created\":1555985033,\"livemode\":false,\"type\":\"charge.succeeded\",\"data\":{\"object\":{\"id\":\"ch_DCGyXTmDGuHKb1C0yTzjPOGC\",\"object\":\"charge\",\"created\":1555985032,\"livemode\":false,\"paid\":true,\"refunded\":false,\"reversed\":false,\"app\":\"app_aTyfXDjrvzDSbLuz\",\"channel\":\"wx_pub\",\"order_no\":\"20190423100352158401\",\"client_ip\":\"114.87.158.59\",\"amount\":10,\"amount_settle\":10,\"currency\":\"cny\",\"subject\":\"kafka 实战\",\"body\":\"测试描述\",\"extra\":{\"open_id\":\"just_for_test\",\"bank_type\":\"your bank type\"},\"time_paid\":1555985033,\"time_expire\":1555992232,\"time_settle\":null,\"transaction_no\":\"1244341374201904238178164740\",\"refunds\":{\"object\":\"list\",\"url\":\"/v1/charges/ch_DCGyXTmDGuHKb1C0yTzjPOGC/refunds\",\"has_more\":false,\"data\":[]},\"amount_refunded\":0,\"failure_code\":null,\"failure_msg\":null,\"metadata\":{},\"credential\":{},\"description\":\"测试备注\"}},\"object\":\"event\",\"request\":\"iar_4e9mPODW5ujPqLen5OOmvL8S\",\"pending_webhooks\":0}");
|
||||
|
||||
new PingxxPaySDK().submitRefund(refund, transactionExtension, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package cn.iocoder.mall.pay.biz.client;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 交易退款成功结果
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class RefundSuccessBO {
|
||||
|
||||
/**
|
||||
* 生成传输给第三方的订单号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String refundCode;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 第三方退款成功的时间
|
||||
*/
|
||||
private Date refundTime;
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package cn.iocoder.mall.pay.biz.client;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 交易支付成功结果
|
||||
*/
|
||||
public class TransactionPaySuccessBO {
|
||||
|
||||
/**
|
||||
* 生成传输给第三方的订单号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String transactionCode;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 第三方支付成功的时间
|
||||
*/
|
||||
private Date paymentTime;
|
||||
|
||||
public String getTransactionCode() {
|
||||
return transactionCode;
|
||||
}
|
||||
|
||||
public TransactionPaySuccessBO setTransactionCode(String transactionCode) {
|
||||
this.transactionCode = transactionCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getTradeNo() {
|
||||
return tradeNo;
|
||||
}
|
||||
|
||||
public TransactionPaySuccessBO setTradeNo(String tradeNo) {
|
||||
this.tradeNo = tradeNo;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Date getPaymentTime() {
|
||||
return paymentTime;
|
||||
}
|
||||
|
||||
public TransactionPaySuccessBO setPaymentTime(Date paymentTime) {
|
||||
this.paymentTime = paymentTime;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package cn.iocoder.mall.pay.biz.client;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 交易支付成功结果
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class TransactionSuccessBO {
|
||||
|
||||
/**
|
||||
* 生成传输给第三方的订单号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String transactionCode;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 第三方支付成功的时间
|
||||
*/
|
||||
private Date paymentTime;
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package cn.iocoder.mall.pay.biz.component;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class DubboReferencePool {
|
||||
}
|
|
@ -6,8 +6,10 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
@Configuration
|
||||
@Profile("dev")
|
||||
public class XxlJobConfiguration {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(XxlJobConfiguration.class);
|
||||
|
@ -42,4 +44,4 @@ public class XxlJobConfiguration {
|
|||
return xxlJobSpringExecutor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
package cn.iocoder.mall.pay.biz.convert;
|
||||
|
||||
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.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.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
|
||||
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
|
||||
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;
|
||||
|
||||
|
@ -25,7 +29,20 @@ public interface PayTransactionConvert {
|
|||
@Mappings({})
|
||||
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({})
|
||||
PayTransactionPaySuccessMessage convert(PayTransactionNotifyTaskDO payTransactionNotifyTaskDO);
|
||||
PayRefundDO convert(PayRefundSubmitDTO payRefundSubmitDTO);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package cn.iocoder.mall.pay.biz.dao;
|
||||
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayRefundDO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayRefundMapper {
|
||||
|
||||
void insert(PayRefundDO entity);
|
||||
|
||||
int update(@Param("entity") PayRefundDO entity,
|
||||
@Param("whereStatus") Integer whereStatus);
|
||||
|
||||
PayRefundDO selectById(@Param("id") Integer id);
|
||||
|
||||
PayRefundDO selectByRefundCode(@Param("refundCode") String refundCode);
|
||||
|
||||
}
|
|
@ -14,4 +14,6 @@ public interface PayTransactionExtensionMapper {
|
|||
|
||||
PayTransactionExtensionDO selectByTransactionCode(@Param("transactionCode") String transactionCode);
|
||||
|
||||
}
|
||||
PayTransactionExtensionDO selectById(@Param("id") Integer id);
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,9 @@ public interface PayTransactionMapper {
|
|||
int update(@Param("entity") PayTransactionDO entity,
|
||||
@Param("whereStatus") Integer whereStatus);
|
||||
|
||||
int updateForRefundTotal(@Param("id") Integer id,
|
||||
@Param("refundTotalIncr") Integer refundTotalIncr);
|
||||
|
||||
PayTransactionDO selectByAppIdAndOrderId(@Param("appId") String appId,
|
||||
@Param("orderId") String orderId);
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package cn.iocoder.mall.pay.biz.dao;
|
||||
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyLogDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyLogDO;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayTransactionNotifyLogMapper {
|
||||
|
||||
void insert(PayTransactionNotifyLogDO entity);
|
||||
void insert(PayNotifyLogDO entity);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package cn.iocoder.mall.pay.biz.dao;
|
||||
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -8,9 +8,9 @@ import java.util.List;
|
|||
@Repository
|
||||
public interface PayTransactionNotifyTaskMapper {
|
||||
|
||||
void insert(PayTransactionNotifyTaskDO entity);
|
||||
void insert(PayNotifyTaskDO entity);
|
||||
|
||||
int update(PayTransactionNotifyTaskDO entity);
|
||||
int update(PayNotifyTaskDO entity);
|
||||
|
||||
/**
|
||||
* 获得需要通知的 PayTransactionNotifyTaskDO 记录。需要满足如下条件:
|
||||
|
@ -21,6 +21,6 @@ public interface PayTransactionNotifyTaskMapper {
|
|||
*
|
||||
* @return PayTransactionNotifyTaskDO 数组
|
||||
*/
|
||||
List<PayTransactionNotifyTaskDO> selectByNotify();
|
||||
List<PayNotifyTaskDO> selectByNotify();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,13 @@ public class PayAppDO extends DeletableDO {
|
|||
private String name;
|
||||
/**
|
||||
* 异步通知地址
|
||||
* TODO 芋艿,修改成 payNotifyUrl
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 退款异步通知地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
|
|
|
@ -5,13 +5,13 @@ import lombok.Data;
|
|||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 支付交易通知 App 的日志 DO
|
||||
* 支付通知 App 的日志 DO
|
||||
*
|
||||
* 通过该表,记录通知 App 时,产生的日志
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionNotifyLogDO extends DeletableDO {
|
||||
public class PayNotifyLogDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 日志编号,自增
|
|
@ -1,18 +1,20 @@
|
|||
package cn.iocoder.mall.pay.biz.dataobject;
|
||||
|
||||
import cn.iocoder.common.framework.dataobject.DeletableDO;
|
||||
import cn.iocoder.mall.pay.biz.service.PayServiceImpl;
|
||||
import cn.iocoder.mall.pay.biz.service.PayTransactionServiceImpl;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付交易通知 App 的任务 DO
|
||||
* 支付通知 App 的任务 DO
|
||||
*
|
||||
* 目前包括支付通知、退款通知。
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionNotifyTaskDO extends DeletableDO {
|
||||
public class PayNotifyTaskDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 通知频率,单位为秒。
|
||||
|
@ -28,26 +30,16 @@ public class PayTransactionNotifyTaskDO extends DeletableDO {
|
|||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 交易编号
|
||||
*
|
||||
* {@link PayTransactionDO#getId()}
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 交易拓展编号
|
||||
*
|
||||
* {@link PayTransactionExtensionDO#getId()}
|
||||
*/
|
||||
private Integer transactionExtensionId;
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 应用订单编号
|
||||
* 类型
|
||||
*
|
||||
* @see cn.iocoder.mall.pay.api.constant.PayNotifyType
|
||||
*/
|
||||
private String orderId;
|
||||
private Integer type;
|
||||
/**
|
||||
* 通知状态
|
||||
*
|
||||
|
@ -63,7 +55,7 @@ public class PayTransactionNotifyTaskDO extends DeletableDO {
|
|||
*
|
||||
* 这个字段,需要结合 {@link #nextNotifyTime} 一起使用。
|
||||
*
|
||||
* 1. 初始时,{@link PayServiceImpl#updateTransactionPaySuccess(Integer, String)}
|
||||
* 1. 初始时,{@link PayTransactionServiceImpl#updateTransactionPaySuccess(Integer, String)}
|
||||
* nextNotifyTime 为当前时间 + 15 秒
|
||||
* lastExecuteTime 为空
|
||||
* 并发送给 MQ ,执行执行
|
||||
|
@ -87,5 +79,58 @@ public class PayTransactionNotifyTaskDO extends DeletableDO {
|
|||
* 通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
// TODO 芋艿,未来把 transaction 和 refund 优化成一个字段。现在为了方便。
|
||||
/**
|
||||
* 支付数据
|
||||
*/
|
||||
private Transaction transaction;
|
||||
/**
|
||||
* 退款数据
|
||||
*/
|
||||
private Refund refund;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Transaction {
|
||||
|
||||
/**
|
||||
* 应用订单编号
|
||||
*/
|
||||
private String orderId;
|
||||
/**
|
||||
* 交易编号
|
||||
*
|
||||
* {@link PayTransactionDO#getId()}
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 交易拓展编号
|
||||
*
|
||||
* {@link PayTransactionExtensionDO#getId()}
|
||||
*/
|
||||
private Integer transactionExtensionId;
|
||||
|
||||
}
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public static class Refund {
|
||||
|
||||
/**
|
||||
* 应用订单编号
|
||||
*/
|
||||
private String orderId;
|
||||
/**
|
||||
* 交易编号
|
||||
*
|
||||
* {@link PayTransactionDO#getId()}
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 退款单编号
|
||||
*/
|
||||
private Integer refundId;
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,99 @@
|
|||
package cn.iocoder.mall.pay.biz.dataobject;
|
||||
|
||||
import cn.iocoder.common.framework.dataobject.BaseDO;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 退款单 DO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayRefundDO {
|
||||
public class PayRefundDO extends BaseDO {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 支付交易编号
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 生成传输给第三方的退款号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String refundCode;
|
||||
/**
|
||||
* 应用编号
|
||||
*
|
||||
* 不同业务线分配不同的 appId
|
||||
* 举个例子,
|
||||
* 1. 电商系统的订单,appId = 1024
|
||||
* 2. 活动系统的订单,appId = 2048
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 业务线的订单编号
|
||||
*
|
||||
* 1. 使用 String 的原因是,业务线可能使用 String 做为编号
|
||||
* 2. 每个 appId 下,orderId 唯一
|
||||
*/
|
||||
private String orderId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
private String createIp;
|
||||
/**
|
||||
* 业务退款描述
|
||||
*/
|
||||
private String orderDescription;
|
||||
/**
|
||||
* 退款金额,单位:分。
|
||||
*
|
||||
* TODO 暂时不考虑货币类型。
|
||||
*/
|
||||
private Integer price;
|
||||
/**
|
||||
* 退款状态
|
||||
*
|
||||
* @see cn.iocoder.mall.pay.api.constant.PayRefundStatus
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 回调业务线完成时间
|
||||
*/
|
||||
private Date finishTime;
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 扩展内容
|
||||
*
|
||||
* 异步通知的时候填充回调的数据
|
||||
*/
|
||||
private String extensionData;
|
||||
/**
|
||||
* 退款渠道
|
||||
*/
|
||||
private Integer refundChannel;
|
||||
/**
|
||||
* 第三方退款成功的时间
|
||||
*/
|
||||
private Date refundTime;
|
||||
/**
|
||||
* 收到第三方系统通知的时间
|
||||
*
|
||||
* 一般情况下,即第三方系统的异步通知
|
||||
*/
|
||||
private Date notifyTime;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
|
||||
}
|
||||
|
|
|
@ -10,5 +10,6 @@ import lombok.experimental.Accessors;
|
|||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Deprecated
|
||||
public class PayRepeatTransactionDO {
|
||||
}
|
||||
|
|
|
@ -26,10 +26,6 @@ public class PayTransactionDO extends DeletableDO {
|
|||
* 2. 活动系统的订单,appId = 2048
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
private String createIp;
|
||||
/**
|
||||
* 业务线的订单编号
|
||||
*
|
||||
|
@ -37,6 +33,10 @@ public class PayTransactionDO extends DeletableDO {
|
|||
* 2. 每个 appId 下,orderId 唯一
|
||||
*/
|
||||
private String orderId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
private String createIp;
|
||||
/**
|
||||
* 订单商品名
|
||||
*/
|
||||
|
@ -56,7 +56,7 @@ public class PayTransactionDO extends DeletableDO {
|
|||
*/
|
||||
private Integer price;
|
||||
/**
|
||||
* 订单状态
|
||||
* 支付状态
|
||||
*
|
||||
* @see cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum
|
||||
*/
|
||||
|
@ -69,13 +69,11 @@ public class PayTransactionDO extends DeletableDO {
|
|||
* 回调业务线完成时间
|
||||
*/
|
||||
private Date finishTime;
|
||||
|
||||
|
||||
// TODO return url
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
// TODO return url
|
||||
|
||||
/**
|
||||
* 成功支付的交易拓展编号
|
||||
|
@ -104,4 +102,11 @@ public class PayTransactionDO extends DeletableDO {
|
|||
*/
|
||||
private String tradeNo;
|
||||
|
||||
// ========== 退款相关 ==========
|
||||
|
||||
/**
|
||||
* 退款总金额
|
||||
*/
|
||||
private Integer refundTotal;
|
||||
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public class PayTransactionExtensionDO extends DeletableDO {
|
|||
* 状态
|
||||
*
|
||||
* @see cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum
|
||||
* 注意,只包含上述枚举的 WAITTING 和 SUCCESS
|
||||
* 注意,只包含上述枚举的 WAITING 和 SUCCESS
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@ 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.PayTransactionPaySuccessMessage;
|
||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
||||
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.PayTransactionNotifyLogDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
|
||||
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;
|
||||
|
@ -31,10 +31,10 @@ import java.util.Date;
|
|||
|
||||
@Service
|
||||
@RocketMQMessageListener(
|
||||
topic = PayTransactionPaySuccessMessage.TOPIC,
|
||||
consumerGroup = "pay-consumer-group-" + PayTransactionPaySuccessMessage.TOPIC
|
||||
topic = PayTransactionSuccessMessage.TOPIC,
|
||||
consumerGroup = "pay-consumer-group-" + PayTransactionSuccessMessage.TOPIC
|
||||
)
|
||||
public class PayTransactionPaySuccessConsumer implements RocketMQListener<PayTransactionPaySuccessMessage> {
|
||||
public class PayTransactionSuccessConsumer implements RocketMQListener<PayTransactionSuccessMessage> {
|
||||
|
||||
@Data
|
||||
private class ReferenceMeta {
|
||||
|
@ -94,7 +94,7 @@ public class PayTransactionPaySuccessConsumer implements RocketMQListener<PayTra
|
|||
|
||||
@Override
|
||||
@Transactional
|
||||
public void onMessage(PayTransactionPaySuccessMessage message) {
|
||||
public void onMessage(PayTransactionSuccessMessage message) {
|
||||
// 获得 ReferenceMeta 对象
|
||||
ReferenceMeta referenceMeta = referenceMetaCache.getUnchecked(message.getNotifyUrl());
|
||||
Assert.notNull(referenceMeta, String.format("notifyUrl(%s) 不存在对应的 ReferenceMeta 对象", message.getNotifyUrl()));
|
||||
|
@ -105,7 +105,7 @@ public class PayTransactionPaySuccessConsumer implements RocketMQListener<PayTra
|
|||
Assert.notNull(transaction, String.format("回调消息(%s) 订单交易不能为空", message.toString()));
|
||||
// 发起调用
|
||||
String response = null; // RPC / HTTP 调用的响应
|
||||
PayTransactionNotifyTaskDO updateTask = new PayTransactionNotifyTaskDO() // 更新 PayTransactionNotifyTaskDO 对象
|
||||
PayNotifyTaskDO updateTask = new PayNotifyTaskDO() // 更新 PayTransactionNotifyTaskDO 对象
|
||||
.setId(message.getId())
|
||||
.setLastExecuteTime(new Date())
|
||||
.setNotifyTimes(message.getNotifyTimes() + 1);
|
||||
|
@ -134,17 +134,17 @@ public class PayTransactionPaySuccessConsumer implements RocketMQListener<PayTra
|
|||
throw e; // TODO 芋艿,此处不能抛出异常。因为,会导致 MQ + 定时任务多重试。此处的目标是,事务回滚 + 吃掉事务。另外,最后的 finally 的日志,要插入成功。
|
||||
} finally {
|
||||
// 插入 PayTransactionNotifyLogDO 日志
|
||||
PayTransactionNotifyLogDO notifyLog = new PayTransactionNotifyLogDO().setNotifyId(message.getId())
|
||||
PayNotifyLogDO notifyLog = new PayNotifyLogDO().setNotifyId(message.getId())
|
||||
.setRequest(message.getOrderId()).setResponse(response).setStatus(updateTask.getStatus());
|
||||
payTransactionNotifyLogMapper.insert(notifyLog);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFailure(PayTransactionNotifyTaskDO updateTask, Integer defaultStatus) {
|
||||
if (updateTask.getNotifyTimes() >= PayTransactionNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
||||
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, PayTransactionNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||
updateTask.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||
updateTask.setStatus(defaultStatus);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package cn.iocoder.mall.pay.biz.scheduler;
|
||||
|
||||
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
|
||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayNotifyTaskDO;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.JobHandler;
|
||||
|
@ -31,17 +31,17 @@ public class PayTransactionNotifyJob extends IJobHandler {
|
|||
@Override
|
||||
public ReturnT<String> execute(String param) {
|
||||
// 获得需要通知的任务
|
||||
List<PayTransactionNotifyTaskDO> notifyTasks = payTransactionNotifyTaskMapper.selectByNotify();
|
||||
List<PayNotifyTaskDO> notifyTasks = payTransactionNotifyTaskMapper.selectByNotify();
|
||||
// 循环任务,发送通知
|
||||
for (PayTransactionNotifyTaskDO payTransactionNotifyTask : notifyTasks) {
|
||||
for (PayNotifyTaskDO payTransactionNotifyTask : notifyTasks) {
|
||||
// 发送 MQ
|
||||
rocketMQTemplate.convertAndSend(PayTransactionPaySuccessMessage.TOPIC,
|
||||
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
||||
// 更新最后通知时间
|
||||
// 1. 这样操作,虽然可能会出现 MQ 消费快于下面 PayTransactionNotifyTaskDO 的更新语句。但是,因为更新字段不同,所以不会有问题。
|
||||
// 2. 换个视角,如果先更新 PayTransactionNotifyTaskDO ,再发送 MQ 消息。如果 MQ 消息发送失败,则 PayTransactionNotifyTaskDO 再也不会被轮询到了。
|
||||
// 3. 当然,最最最完美的话,就是做事务消息,不过这样又过于复杂~
|
||||
PayTransactionNotifyTaskDO updateNotifyTask = new PayTransactionNotifyTaskDO()
|
||||
PayNotifyTaskDO updateNotifyTask = new PayNotifyTaskDO()
|
||||
.setId(payTransactionNotifyTask.getId()).setLastExecuteTime(new Date());
|
||||
payTransactionNotifyTaskMapper.update(updateNotifyTask);
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
package cn.iocoder.mall.pay.biz.service;
|
||||
|
||||
import cn.iocoder.mall.pay.api.PayDemoService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service
|
||||
public class PayDemoServiceImpl implements PayDemoService {
|
||||
|
||||
@Override
|
||||
public String updatePaySuccess(String orderId) {
|
||||
// return "你好呀";
|
||||
return "success";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package cn.iocoder.mall.pay.biz.service;
|
||||
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.mall.pay.api.constant.PayNotifyType;
|
||||
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
|
||||
import cn.iocoder.mall.pay.api.message.PayTransactionSuccessMessage;
|
||||
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.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.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Calendar;
|
||||
|
||||
@Service
|
||||
public class PayNotifyServiceImpl {
|
||||
|
||||
@Autowired
|
||||
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||
|
||||
@Resource
|
||||
private RocketMQTemplate rocketMQTemplate;
|
||||
|
||||
public void addRefundNotifyTask(PayRefundDO refund) {
|
||||
PayNotifyTaskDO payTransactionNotifyTask = this.createBasePayNotifyTaskDO(refund.getAppId(), refund.getNotifyUrl())
|
||||
.setType(PayNotifyType.REFUND.getValue());
|
||||
// 设置 Refund 属性
|
||||
payTransactionNotifyTask.setRefund(new PayNotifyTaskDO.Refund().setRefundId(refund.getId())
|
||||
.setTransactionId(refund.getTransactionId()).setOrderId(refund.getOrderId()));
|
||||
// 保存到数据库
|
||||
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
||||
// 发送 MQ 消息
|
||||
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
||||
}
|
||||
|
||||
public void addTransactionNotifyTask(PayTransactionDO transaction, PayTransactionExtensionDO extension) {
|
||||
PayNotifyTaskDO payTransactionNotifyTask = this.createBasePayNotifyTaskDO(transaction.getAppId(), transaction.getNotifyUrl())
|
||||
.setType(PayNotifyType.TRANSACTION.getValue());
|
||||
// 设置 Transaction 属性
|
||||
payTransactionNotifyTask.setTransaction(new PayNotifyTaskDO.Transaction().setOrderId(transaction.getOrderId())
|
||||
.setTransactionId(extension.getTransactionId()).setTransactionExtensionId(extension.getId()));
|
||||
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
||||
// 3.2 发送 MQ
|
||||
rocketMQTemplate.convertAndSend(PayTransactionSuccessMessage.TOPIC,
|
||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
||||
}
|
||||
|
||||
private PayNotifyTaskDO createBasePayNotifyTaskDO(String appId, String notifyUrl) {
|
||||
return new PayNotifyTaskDO()
|
||||
.setAppId(appId)
|
||||
.setStatus(PayTransactionNotifyStatusEnum.WAITING.getValue())
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1)
|
||||
.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[0]))
|
||||
.setNotifyUrl(notifyUrl);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package cn.iocoder.mall.pay.biz.service;
|
||||
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.common.framework.util.MathUtil;
|
||||
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.pay.api.PayRefundService;
|
||||
import cn.iocoder.mall.pay.api.bo.PayRefundSubmitBO;
|
||||
import cn.iocoder.mall.pay.api.constant.*;
|
||||
import cn.iocoder.mall.pay.api.dto.PayRefundSubmitDTO;
|
||||
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
|
||||
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
|
||||
import cn.iocoder.mall.pay.biz.client.RefundSuccessBO;
|
||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
||||
import cn.iocoder.mall.pay.biz.dao.PayRefundMapper;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.*;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
|
||||
public class PayRefundServiceImpl implements PayRefundService {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Autowired
|
||||
private PayRefundMapper payRefundMapper;
|
||||
|
||||
@Autowired
|
||||
private PayAppServiceImpl payAppService;
|
||||
@Autowired
|
||||
private PayNotifyServiceImpl payNotifyService;
|
||||
@Autowired
|
||||
private PayTransactionServiceImpl payTransactionService;
|
||||
|
||||
@Resource
|
||||
private RocketMQTemplate rocketMQTemplate;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("Duplicates")
|
||||
public CommonResult<PayRefundSubmitBO> submitRefund(PayRefundSubmitDTO payRefundSubmitDTO) {
|
||||
// 校验 App 是否有效
|
||||
CommonResult<PayAppDO> appResult = payAppService.validPayApp(payRefundSubmitDTO.getAppId());
|
||||
if (appResult.isError()) {
|
||||
return CommonResult.error(appResult);
|
||||
}
|
||||
// 获得 PayTransactionDO ,并校验其是否存在
|
||||
PayTransactionDO payTransaction = payTransactionService.getTransaction(payRefundSubmitDTO.getAppId(), payRefundSubmitDTO.getOrderId());
|
||||
if (payTransaction == null) { // 是否存在
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayTransactionStatusEnum.SUCCESS.getValue().equals(payTransaction.getStatus())) { // 校验状态,必须是待支付
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_SUCCESS.getCode());
|
||||
}
|
||||
if (payRefundSubmitDTO.getPrice() > payTransaction.getPrice() - payTransaction.getRefundTotal()) { // 金额校验
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_REFUND_PRICE_EXCEED.getCode());
|
||||
}
|
||||
// 获得 PayTransactionExtensionDO ,并校验其是否存在
|
||||
PayTransactionExtensionDO payTransactionExtension = payTransactionService.getPayTransactionExtension(payTransaction.getExtensionId());
|
||||
if (payTransactionExtension == null) { // 是否存在
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayTransactionStatusEnum.SUCCESS.getValue().equals(payTransactionExtension.getStatus())) { // 校验状态,必须是待支付
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_SUCCESS.getCode());
|
||||
}
|
||||
// 插入 PayTransactionExtensionDO
|
||||
PayRefundDO payRefundDO = PayTransactionConvert.INSTANCE.convert(payRefundSubmitDTO)
|
||||
.setTransactionId(payTransaction.getId())
|
||||
.setRefundCode(generateTransactionCode()) // TODO 芋艿,后续调整
|
||||
.setStatus(PayRefundStatus.WAITING.getValue())
|
||||
.setNotifyUrl(appResult.getData().getRefundNotifyUrl())
|
||||
.setRefundChannel(payTransaction.getPayChannel());
|
||||
payRefundDO.setCreateTime(new Date());
|
||||
payRefundMapper.insert(payRefundDO);
|
||||
// 调用三方接口
|
||||
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payTransaction.getPayChannel());
|
||||
CommonResult<String> invokeResult = paySDK.submitRefund(payRefundDO, payTransactionExtension, null); // TODO 暂时传入 extra = null
|
||||
if (invokeResult.isError()) {
|
||||
return CommonResult.error(invokeResult);
|
||||
}
|
||||
// 返回成功
|
||||
PayRefundSubmitBO payRefundSubmitBO = new PayRefundSubmitBO()
|
||||
.setId(payRefundDO.getId());
|
||||
return CommonResult.success(payRefundSubmitBO);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CommonResult<Boolean> updateRefundSuccess(Integer payChannel, String params) {
|
||||
// TODO 芋艿,记录回调日志
|
||||
// 解析传入的参数,成 TransactionSuccessBO 对象
|
||||
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payChannel);
|
||||
CommonResult<RefundSuccessBO> paySuccessResult = paySDK.parseRefundSuccessParams(params);
|
||||
if (paySuccessResult.isError()) {
|
||||
return CommonResult.error(paySuccessResult);
|
||||
}
|
||||
// TODO 芋艿,先最严格的校验。即使调用方重复调用,实际哪个订单已经被重复回调的支付,也返回 false 。也没问题,因为实际已经回调成功了。
|
||||
// 1.1 查询 PayRefundDO
|
||||
PayRefundDO payRefund = payRefundMapper.selectByRefundCode(paySuccessResult.getData().getRefundCode());
|
||||
if (payRefund == null) {
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_REFUND_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayRefundStatus.WAITING.getValue().equals(payRefund.getStatus())) { // 校验状态,必须是待支付
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_REFUND_STATUS_NOT_WAITING.getCode());
|
||||
}
|
||||
// 1.2 更新 PayRefundDO
|
||||
Integer status = paySuccessResult.getData().getSuccess() ? PayRefundStatus.SUCCESS.getValue() : PayRefundStatus.FAILURE.getValue();
|
||||
PayRefundDO updatePayRefundDO = new PayRefundDO()
|
||||
.setId(payRefund.getId())
|
||||
.setStatus(status)
|
||||
.setExtensionData(params);
|
||||
int updateCounts = payRefundMapper.update(updatePayRefundDO, PayRefundStatus.WAITING.getValue());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_STATUS_NOT_WAITING.getCode());
|
||||
}
|
||||
logger.info("[updateRefundSuccess][PayRefundDO({}) 更新为({})]", payRefund.getId(), status);
|
||||
// 2.1 判断 PayTransactionDO ,增加已退款金额
|
||||
PayTransactionDO payTransaction = payTransactionService.getTransaction(payRefund.getTransactionId());
|
||||
if (payTransaction == null) {
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayTransactionStatusEnum.SUCCESS.getValue().equals(payTransaction.getStatus())) { // 校验状态,必须是已支付
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_SUCCESS.getCode());
|
||||
}
|
||||
if (payRefund.getPrice() + payTransaction.getRefundTotal() > payTransaction.getPrice()) {
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_PRICE_EXCEED.getCode());
|
||||
}
|
||||
// 2.2 更新 PayTransactionDO
|
||||
updateCounts = payTransactionService.updateTransactionPriceTotalIncr(payRefund.getTransactionId(), payRefund.getPrice());
|
||||
if (updateCounts == 0) { // 保证不超退 TODO 这种类型,需要思考下。需要返回错误,但是又要保证事务回滚
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_REFUND_PRICE_EXCEED.getCode());
|
||||
}
|
||||
logger.info("[updateRefundSuccess][PayTransactionDO({}) 更新为已支付]", payTransaction.getId());
|
||||
// 3 新增 PayNotifyTaskDO
|
||||
payNotifyService.addRefundNotifyTask(payRefund);
|
||||
// 返回结果
|
||||
return CommonResult.success(true);
|
||||
}
|
||||
|
||||
private String generateTransactionCode() {
|
||||
// wx
|
||||
// 2014
|
||||
// 10
|
||||
// 27
|
||||
// 20
|
||||
// 09
|
||||
// 39
|
||||
// 5522657
|
||||
// a690389285100
|
||||
// 目前的算法
|
||||
// 时间序列,年月日时分秒 14 位
|
||||
// 纯随机,6 位 TODO 此处估计是会有问题的,后续在调整
|
||||
return DateUtil.format(new Date(), "yyyyMMddHHmmss") + // 时间序列
|
||||
MathUtil.random(100000, 999999) // 随机。为什么是这个范围,因为偷懒
|
||||
;
|
||||
}
|
||||
|
||||
}
|
|
@ -8,14 +8,12 @@ import cn.iocoder.mall.pay.api.PayTransactionService;
|
|||
import cn.iocoder.mall.pay.api.bo.PayTransactionBO;
|
||||
import cn.iocoder.mall.pay.api.bo.PayTransactionSubmitBO;
|
||||
import cn.iocoder.mall.pay.api.constant.PayErrorCodeEnum;
|
||||
import cn.iocoder.mall.pay.api.constant.PayTransactionNotifyStatusEnum;
|
||||
import cn.iocoder.mall.pay.api.constant.PayTransactionStatusEnum;
|
||||
import cn.iocoder.mall.pay.api.dto.PayTransactionCreateDTO;
|
||||
import cn.iocoder.mall.pay.api.dto.PayTransactionSubmitDTO;
|
||||
import cn.iocoder.mall.pay.api.message.PayTransactionPaySuccessMessage;
|
||||
import cn.iocoder.mall.pay.biz.client.AbstractPaySDK;
|
||||
import cn.iocoder.mall.pay.biz.client.PaySDKFactory;
|
||||
import cn.iocoder.mall.pay.biz.client.TransactionPaySuccessBO;
|
||||
import cn.iocoder.mall.pay.biz.client.TransactionSuccessBO;
|
||||
import cn.iocoder.mall.pay.biz.convert.PayTransactionConvert;
|
||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionExtensionMapper;
|
||||
import cn.iocoder.mall.pay.biz.dao.PayTransactionMapper;
|
||||
|
@ -23,21 +21,17 @@ import cn.iocoder.mall.pay.biz.dao.PayTransactionNotifyTaskMapper;
|
|||
import cn.iocoder.mall.pay.biz.dataobject.PayAppDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.pay.biz.dataobject.PayTransactionNotifyTaskDO;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
@Service
|
||||
@com.alibaba.dubbo.config.annotation.Service(validation = "true")
|
||||
public class PayServiceImpl implements PayTransactionService {
|
||||
public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
|
@ -47,11 +41,27 @@ public class PayServiceImpl implements PayTransactionService {
|
|||
private PayTransactionExtensionMapper payTransactionExtensionMapper;
|
||||
@Autowired
|
||||
private PayTransactionNotifyTaskMapper payTransactionNotifyTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PayAppServiceImpl payAppService;
|
||||
@Autowired
|
||||
private PayNotifyServiceImpl payNotifyService;
|
||||
|
||||
@Resource
|
||||
private RocketMQTemplate rocketMQTemplate;
|
||||
public PayTransactionDO getTransaction(Integer id) {
|
||||
return payTransactionMapper.selectById(id);
|
||||
}
|
||||
|
||||
public PayTransactionDO getTransaction(String appId, String orderId) {
|
||||
return payTransactionMapper.selectByAppIdAndOrderId(appId, orderId);
|
||||
}
|
||||
|
||||
public int updateTransactionPriceTotalIncr(Integer id, Integer incr) {
|
||||
return payTransactionMapper.updateForRefundTotal(id, incr);
|
||||
}
|
||||
|
||||
public PayTransactionExtensionDO getPayTransactionExtension(Integer id) {
|
||||
return payTransactionExtensionMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<PayTransactionBO> getTransaction(Integer userId, String appId, String orderId) {
|
||||
|
@ -80,7 +90,7 @@ public class PayServiceImpl implements PayTransactionService {
|
|||
// TODO 芋艿 可能要考虑,更新订单。例如说,业务线订单可以修改价格
|
||||
} else {
|
||||
payTransaction = PayTransactionConvert.INSTANCE.convert(payTransactionCreateDTO);
|
||||
payTransaction.setStatus(PayTransactionStatusEnum.WAITTING.getValue())
|
||||
payTransaction.setStatus(PayTransactionStatusEnum.WAITING.getValue())
|
||||
.setNotifyUrl(appResult.getData().getNotifyUrl());
|
||||
payTransaction.setCreateTime(new Date());
|
||||
payTransactionMapper.insert(payTransaction);
|
||||
|
@ -104,14 +114,14 @@ public class PayServiceImpl implements PayTransactionService {
|
|||
if (payTransaction == null) { // 是否存在
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITTING.getValue().equals(payTransaction.getStatus())) { // 校验状态,必须是待支付
|
||||
if (!PayTransactionStatusEnum.WAITING.getValue().equals(payTransaction.getStatus())) { // 校验状态,必须是待支付
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
|
||||
}
|
||||
// 插入 PayTransactionExtensionDO
|
||||
PayTransactionExtensionDO payTransactionExtensionDO = PayTransactionConvert.INSTANCE.convert(payTransactionSubmitDTO)
|
||||
.setTransactionId(payTransaction.getId())
|
||||
.setTransactionCode(generateTransactionCode())
|
||||
.setStatus(PayTransactionStatusEnum.WAITTING.getValue());
|
||||
.setStatus(PayTransactionStatusEnum.WAITING.getValue());
|
||||
payTransactionExtensionMapper.insert(payTransactionExtensionDO);
|
||||
// 调用三方接口
|
||||
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payTransactionSubmitDTO.getPayChannel());
|
||||
|
@ -130,67 +140,55 @@ public class PayServiceImpl implements PayTransactionService {
|
|||
@Transactional
|
||||
public CommonResult<Boolean> updateTransactionPaySuccess(Integer payChannel, String params) {
|
||||
// TODO 芋艿,记录回调日志
|
||||
// 解析传入的参数,成 TransactionPaySuccessBO 对象
|
||||
// 解析传入的参数,成 TransactionSuccessBO 对象
|
||||
AbstractPaySDK paySDK = PaySDKFactory.getSDK(payChannel);
|
||||
CommonResult<TransactionPaySuccessBO> paySuccessResult = paySDK.parseTransactionPaySuccessParams(params);
|
||||
CommonResult<TransactionSuccessBO> paySuccessResult = paySDK.parseTransactionSuccessParams(params);
|
||||
if (paySuccessResult.isError()) {
|
||||
return CommonResult.error(paySuccessResult);
|
||||
}
|
||||
// TODO 芋艿,先最严格的校验。即使调用方重复调用,实际哪个订单已经被重复回调的支付,也返回 false 。也没问题,因为实际已经回调成功了。
|
||||
// 1.1 查询 PayTransactionExtensionDO
|
||||
PayTransactionExtensionDO payTransactionExtension = payTransactionExtensionMapper.selectByTransactionCode(paySuccessResult.getData().getTransactionCode());
|
||||
if (payTransactionExtension == null) {
|
||||
PayTransactionExtensionDO extension = payTransactionExtensionMapper.selectByTransactionCode(paySuccessResult.getData().getTransactionCode());
|
||||
if (extension == null) {
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITTING.getValue().equals(payTransactionExtension.getStatus())) { // 校验状态,必须是待支付
|
||||
if (!PayTransactionStatusEnum.WAITING.getValue().equals(extension.getStatus())) { // 校验状态,必须是待支付
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING.getCode());
|
||||
}
|
||||
// 1.2 更新 PayTransactionExtensionDO
|
||||
PayTransactionExtensionDO updatePayTransactionExtension = new PayTransactionExtensionDO()
|
||||
.setId(payTransactionExtension.getId())
|
||||
.setId(extension.getId())
|
||||
.setStatus(PayTransactionStatusEnum.SUCCESS.getValue())
|
||||
.setExtensionData(params);
|
||||
int updateCounts = payTransactionExtensionMapper.update(updatePayTransactionExtension, PayTransactionStatusEnum.WAITTING.getValue());
|
||||
int updateCounts = payTransactionExtensionMapper.update(updatePayTransactionExtension, PayTransactionStatusEnum.WAITING.getValue());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING.getCode());
|
||||
}
|
||||
logger.info("[updateTransactionPaySuccess][PayTransactionExtensionDO({}) 更新为已支付]", payTransactionExtension.getId());
|
||||
logger.info("[updateTransactionPaySuccess][PayTransactionExtensionDO({}) 更新为已支付]", extension.getId());
|
||||
// 2.1 判断 PayTransactionDO 是否处于待支付
|
||||
PayTransactionDO payTransactionDO = payTransactionMapper.selectById(payTransactionExtension.getTransactionId());
|
||||
if (payTransactionDO == null) {
|
||||
PayTransactionDO transaction = payTransactionMapper.selectById(extension.getTransactionId());
|
||||
if (transaction == null) {
|
||||
return ServiceExceptionUtil.error(PayErrorCodeEnum.PAY_TRANSACTION_NOT_FOUND.getCode());
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITTING.getValue().equals(payTransactionDO.getStatus())) { // 校验状态,必须是待支付
|
||||
if (!PayTransactionStatusEnum.WAITING.getValue().equals(transaction.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
|
||||
}
|
||||
// 2.2 更新 PayTransactionDO
|
||||
PayTransactionDO updatePayTransaction = new PayTransactionDO()
|
||||
.setId(payTransactionDO.getId())
|
||||
.setId(transaction.getId())
|
||||
.setStatus(PayTransactionStatusEnum.SUCCESS.getValue())
|
||||
.setExtensionId(payTransactionExtension.getId())
|
||||
.setExtensionId(extension.getId())
|
||||
.setPayChannel(payChannel)
|
||||
.setPaymentTime(paySuccessResult.getData().getPaymentTime())
|
||||
.setNotifyTime(new Date())
|
||||
.setTradeNo(paySuccessResult.getData().getTradeNo());
|
||||
updateCounts = payTransactionMapper.update(updatePayTransaction, PayTransactionStatusEnum.WAITTING.getValue());
|
||||
updateCounts = payTransactionMapper.update(updatePayTransaction, PayTransactionStatusEnum.WAITING.getValue());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付 TODO 这种类型,需要思考下。需要返回错误,但是又要保证事务回滚
|
||||
throw ServiceExceptionUtil.exception(PayErrorCodeEnum.PAY_TRANSACTION_STATUS_IS_NOT_WAITING.getCode());
|
||||
}
|
||||
logger.info("[updateTransactionPaySuccess][PayTransactionDO({}) 更新为已支付]", payTransactionDO.getId());
|
||||
// 3.1 插入
|
||||
PayTransactionNotifyTaskDO payTransactionNotifyTask = new PayTransactionNotifyTaskDO()
|
||||
.setTransactionId(payTransactionExtension.getTransactionId()).setTransactionExtensionId(payTransactionExtension.getId())
|
||||
.setAppId(payTransactionDO.getAppId()).setOrderId(payTransactionDO.getOrderId())
|
||||
.setStatus(PayTransactionNotifyStatusEnum.WAITING.getValue())
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayTransactionNotifyTaskDO.NOTIFY_FREQUENCY.length + 1)
|
||||
.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayTransactionNotifyTaskDO.NOTIFY_FREQUENCY[0]))
|
||||
.setNotifyUrl(payTransactionDO.getNotifyUrl());
|
||||
payTransactionNotifyTaskMapper.insert(payTransactionNotifyTask);
|
||||
logger.info("[updateTransactionPaySuccess][PayTransactionNotifyTaskDO({}) 新增一个任务]", payTransactionNotifyTask.getId());
|
||||
// 3.2 发送 MQ
|
||||
rocketMQTemplate.convertAndSend(PayTransactionPaySuccessMessage.TOPIC,
|
||||
PayTransactionConvert.INSTANCE.convert(payTransactionNotifyTask));
|
||||
logger.info("[updateTransactionPaySuccess][PayTransactionNotifyTaskDO({}) 发送 MQ 任务]", payTransactionNotifyTask.getId());
|
||||
logger.info("[updateTransactionPaySuccess][PayTransactionDO({}) 更新为已支付]", transaction.getId());
|
||||
// 3 新增 PayNotifyTaskDO
|
||||
payNotifyService.addTransactionNotifyTask(transaction, extension);
|
||||
// 返回结果
|
||||
return CommonResult.success(true);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
# xxl-job
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:18079/
|
||||
executor:
|
||||
appname: pay-job-executor
|
||||
ip:
|
||||
port: 0
|
||||
logpath: /Users/yunai/logs/xxl-job/
|
||||
logretentiondays: 1
|
||||
accessToken:
|
|
@ -24,19 +24,6 @@ dubbo:
|
|||
scan:
|
||||
base-packages: cn.iocoder.mall.pay.biz.service
|
||||
|
||||
# xxl-job
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:18079/
|
||||
executor:
|
||||
appname: pay-job-executor
|
||||
ip:
|
||||
port: 0
|
||||
logpath: /Users/yunai/logs/xxl-job/
|
||||
logretentiondays: 1
|
||||
accessToken:
|
||||
|
||||
# rocketmq
|
||||
rocketmq:
|
||||
name-server: 127.0.0.1:9876
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayAppMapper">
|
||||
|
||||
<sql id="FIELDS">
|
||||
id, name, notify_url, status, create_time
|
||||
id, name, notify_url, refund_notify_url, status, create_time
|
||||
</sql>
|
||||
|
||||
<!--<insert id="insert" parameterType="RoleDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">-->
|
||||
|
@ -34,4 +34,4 @@
|
|||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?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">
|
||||
<mapper namespace="cn.iocoder.mall.pay.biz.dao.PayRefundMapper">
|
||||
|
||||
<sql id="FIELDS">
|
||||
id, transaction_id, refund_cod, app_id, create_ip, order_id,
|
||||
order_description, price, status,
|
||||
finish_time, notify_url, extension_data, refund_channel, refund_time, notify_time,
|
||||
trade_no, create_time
|
||||
</sql>
|
||||
|
||||
<insert id="insert" parameterType="PayRefundDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||
INSERT INTO refund (
|
||||
transaction_id, refund_code, app_id, create_ip, order_id,
|
||||
order_description, price, status,
|
||||
finish_time, notify_url, extension_data, refund_channel, refund_time, notify_time,
|
||||
trade_no, create_time
|
||||
) VALUES (
|
||||
#{transactionId}, #{refundCode}, #{appId}, #{createIp}, #{orderId},
|
||||
#{orderDescription}, #{price}, #{status},
|
||||
#{finishTime}, #{notifyUrl}, #{extensionData}, #{refundChannel}, #{refundTime}, #{notifyTime},
|
||||
#{tradeNo}, #{createTime}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<update id="update">
|
||||
UPDATE refund
|
||||
<set>
|
||||
<if test="entity.status != null">
|
||||
, status = #{entity.status}
|
||||
</if>
|
||||
<if test="entity.finishTime != null">
|
||||
, finish_time = #{entity.finishTime}
|
||||
</if>
|
||||
<if test="entity.extensionData != null">
|
||||
, extension_data = #{entity.extensionData}
|
||||
</if>
|
||||
<if test="entity.refundTime != null">
|
||||
, refund_time = #{entity.refundTime}
|
||||
</if>
|
||||
<if test="entity.notifyTime != null">
|
||||
, notify_time = #{entity.notifyTime}
|
||||
</if>
|
||||
<if test="entity.tradeNo != null">
|
||||
, trade_no = #{entity.tradeNo}
|
||||
</if>
|
||||
</set>
|
||||
WHERE id = #{entity.id}
|
||||
<if test="whereStatus != null">
|
||||
AND status = #{whereStatus}
|
||||
</if>
|
||||
</update>
|
||||
|
||||
<select id="selectByRefundCode" parameterType="String" resultType="PayRefundDO">
|
||||
SELECT
|
||||
<include refid="FIELDS"/>
|
||||
FROM refund
|
||||
WHERE refund_code = #{refundCode}
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<select id="selectById" parameterType="Integer" resultType="PayRefundDO">
|
||||
SELECT
|
||||
<include refid="FIELDS"/>
|
||||
FROM refund
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -41,4 +41,11 @@
|
|||
LIMIT 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
<select id="selectById" parameterType="Integer" resultType="PayTransactionExtensionDO">
|
||||
SELECT
|
||||
<include refid="FIELDS"/>
|
||||
FROM transaction_extension
|
||||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
id, app_id, create_ip, order_id, order_subject,
|
||||
order_description, order_memo, price, status, expire_time,
|
||||
finish_time, notify_url, extension_id, pay_channel, payment_time,
|
||||
notify_time, trade_no, create_time
|
||||
notify_time, trade_no, refund_total, create_time
|
||||
</sql>
|
||||
|
||||
<insert id="insert" parameterType="PayTransactionDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||
|
@ -54,6 +54,12 @@
|
|||
</if>
|
||||
</update>
|
||||
|
||||
<update id="updateForRefundTotal">
|
||||
UPDATE `transaction`
|
||||
SET refundTotal = refundTotal + ${refundTotalIncr}
|
||||
WHERE price >= refundTotal + ${refundTotalIncr}
|
||||
</update>
|
||||
|
||||
<select id="selectByAppIdAndOrderId" resultType="PayTransactionDO">
|
||||
SELECT
|
||||
<include refid="FIELDS"/>
|
||||
|
@ -69,4 +75,4 @@
|
|||
WHERE id = #{id}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<!--create_time-->
|
||||
<!--</sql>-->
|
||||
|
||||
<insert id="insert" parameterType="PayTransactionNotifyLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||
<insert id="insert" parameterType="PayNotifyLogDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||
INSERT INTO transaction_notify_log (
|
||||
notify_id, request, response, status
|
||||
) VALUES (
|
||||
|
@ -43,4 +43,4 @@
|
|||
<!--LIMIT 1-->
|
||||
<!--</select>-->
|
||||
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
create_time
|
||||
</sql>
|
||||
|
||||
<insert id="insert" parameterType="PayTransactionNotifyTaskDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||
<insert id="insert" parameterType="PayNotifyTaskDO" useGeneratedKeys="true" keyColumn="id" keyProperty="id">
|
||||
INSERT INTO transaction_notify_task (
|
||||
transaction_id, transaction_extension_id, app_id, order_id,
|
||||
status, next_notify_time, notify_times, max_notify_times
|
||||
|
@ -18,7 +18,7 @@
|
|||
)
|
||||
</insert>
|
||||
|
||||
<update id="update" parameterType="PayTransactionNotifyTaskDO">
|
||||
<update id="update" parameterType="PayNotifyTaskDO">
|
||||
UPDATE transaction_notify_task
|
||||
<set>
|
||||
<if test="status != null">
|
||||
|
@ -37,13 +37,13 @@
|
|||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="selectByNotify" resultType="PayTransactionNotifyTaskDO">
|
||||
<select id="selectByNotify" resultType="PayNotifyTaskDO">
|
||||
SELECT
|
||||
<include refid="FIELDS"/>
|
||||
FROM transaction_notify_task
|
||||
WHERE status IN (1, 3, 4, 5)
|
||||
WHERE status IN (1, 4, 5)
|
||||
AND next_notify_time <![CDATA[ <= ]]> NOW()
|
||||
AND last_execute_time > next_notify_time
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
</mapper>
|
||||
|
|
Loading…
Reference in New Issue