将 onemall 老代码,统一到归档目录,后续不断迁移移除
This commit is contained in:
40
归档/pay-service-project/pay-service-api/pom.xml
Normal file
40
归档/pay-service-project/pay-service-api/pom.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>pay-service-project</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>pay-service-api</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
<version>RELEASE</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.iocoder.mall.payservice.enums;
|
||||
|
||||
import cn.iocoder.common.framework.core.IntArrayValuable;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 支付通道
|
||||
*/
|
||||
public enum PayChannelEnum implements IntArrayValuable {
|
||||
|
||||
WEIXIN_APP(100, "wx", "微信 App 支付"),
|
||||
WEIXIN_PUB(101, "wxjs", "微信 JS API 支付"),
|
||||
|
||||
ALIPAY(200, "alipay", "支付宝 App 支付"),
|
||||
|
||||
PINGXX(9999, "ping++", "ping++ 支付"),
|
||||
;
|
||||
|
||||
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(PayChannelEnum::getId).toArray();
|
||||
|
||||
/**
|
||||
* 渠道编号
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private String code;
|
||||
/**
|
||||
* 渠道名
|
||||
*/
|
||||
private String name;
|
||||
|
||||
PayChannelEnum(Integer id, String code, String name) {
|
||||
this.id = id;
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] array() {
|
||||
return ARRAYS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.iocoder.mall.payservice.enums;
|
||||
|
||||
import cn.iocoder.common.framework.exception.ErrorCode;
|
||||
|
||||
/**
|
||||
* 错误码枚举类
|
||||
*
|
||||
* 管理员系统,使用 1-004-000-000 段
|
||||
*/
|
||||
public interface PayErrorCodeConstants {
|
||||
|
||||
// ========== APP 模块 ==========
|
||||
ErrorCode PAY_APP_NOT_FOUND = new ErrorCode(1004000000, "App 不存在");
|
||||
ErrorCode PAY_APP_IS_DISABLE = new ErrorCode(1004000001, "App 已经被禁用");
|
||||
|
||||
// ========== TRANSACTION PAY 模块 ==========
|
||||
ErrorCode PAY_TRANSACTION_NOT_FOUND = new ErrorCode(100401000, "支付交易单不存在");
|
||||
ErrorCode PAY_TRANSACTION_STATUS_IS_NOT_WAITING = new ErrorCode(100401001, "支付交易单不处于待支付");
|
||||
ErrorCode PAY_TRANSACTION_STATUS_IS_NOT_SUCCESS = new ErrorCode(100401002, "支付交易单不处于已支付");
|
||||
ErrorCode PAY_TRANSACTION_ERROR_USER = new ErrorCode(100401003, "支付交易单用户不正确");
|
||||
|
||||
ErrorCode PAY_TRANSACTION_EXTENSION_NOT_FOUND = new ErrorCode(100401050, "支付交易拓展单不存在");
|
||||
ErrorCode PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING = new ErrorCode(100401051, "支付交易拓展单不处于待支付");
|
||||
ErrorCode PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_SUCCESS = new ErrorCode(100401052, "支付交易单不处于已支付");
|
||||
|
||||
// ========== TRANSACTION REFUND 模块 ==========
|
||||
ErrorCode PAY_REFUND_PRICE_EXCEED = new ErrorCode(100402000, "退款金额超过支付交易单可退金额");
|
||||
ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(100402001, "退款单不存在");
|
||||
ErrorCode PAY_REFUND_STATUS_NOT_WAITING = new ErrorCode(100402002, "退款单不处于待处理");
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cn.iocoder.mall.payservice.enums.notify;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付通知状态枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum PayNotifyStatusEnum {
|
||||
|
||||
WAITING(1, "等待通知"),
|
||||
SUCCESS(2, "通知成功"),
|
||||
FAILURE(3, "通知失败"), // 多次尝试,彻底失败
|
||||
REQUEST_SUCCESS(4, "请求成功,但是结果失败"),
|
||||
REQUEST_FAILURE(5, "请求失败"),
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
PayNotifyStatusEnum(Integer status, String name) {
|
||||
this.status = status;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.mall.payservice.enums.notify;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付通知类型
|
||||
*/
|
||||
@Getter
|
||||
public enum PayNotifyType {
|
||||
|
||||
TRANSACTION(1, "支付"),
|
||||
REFUND(2, "退款"),
|
||||
;
|
||||
|
||||
/**
|
||||
* 类型
|
||||
*/
|
||||
private final Integer type;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
PayNotifyType(Integer type, String name) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.payservice.enums.refund;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付退款状态枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum PayRefundStatus {
|
||||
|
||||
WAITING(1, "处理中"),
|
||||
SUCCESS(2, "成功"),
|
||||
FAILURE(3, "失败"), // 例如说,支付单超时
|
||||
;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer value;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
PayRefundStatus(Integer value, String name) {
|
||||
this.value = value;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.payservice.enums.transaction;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付交易状态枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum PayTransactionStatusEnum {
|
||||
|
||||
WAITING(1, "等待支付"),
|
||||
SUCCESS(2, "支付成功"),
|
||||
CANCEL(3, "取消支付"), // 例如说,支付单超时
|
||||
;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
PayTransactionStatusEnum(Integer status, String name) {
|
||||
this.status = status;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package cn.iocoder.mall.payservice.rpc.app;
|
||||
|
||||
/**
|
||||
* 支付应用 RPC 接口
|
||||
*/
|
||||
public interface PayAppRpc {
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.iocoder.mall.payservice.rpc.app.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付应用 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayAppRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String payNotifyUrl;
|
||||
/**
|
||||
* 退款异步通知地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.*;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
/**
|
||||
* Title:
|
||||
* Description:
|
||||
*
|
||||
* @author zhuyang
|
||||
* @version 1.0 2021/10/9
|
||||
*/
|
||||
@FeignClient(value = "pay-service")
|
||||
public interface PayTransactionFeign {
|
||||
/**
|
||||
* 创建支付交易单
|
||||
*
|
||||
* @param createReqDTO 创建信息
|
||||
* @return 支付交易单号
|
||||
*/
|
||||
@PostMapping("/pay/transaction/createPayTransaction")
|
||||
CommonResult<Integer> createPayTransaction(@RequestBody PayTransactionCreateReqDTO createReqDTO);
|
||||
|
||||
/**
|
||||
* 提交支付交易单
|
||||
*
|
||||
* @param submitReqDTO 提交信息
|
||||
* @return 提交响应,包含三方支付的响应
|
||||
*/
|
||||
@PostMapping("/pay/transaction/submitPayTransaction")
|
||||
CommonResult<PayTransactionSubmitRespDTO> submitPayTransaction(@RequestBody PayTransactionSubmitReqDTO submitReqDTO);
|
||||
|
||||
/**
|
||||
* 获得当支付交易单
|
||||
*
|
||||
* @param getReqDTO 获得条件
|
||||
* @return 支付交易单
|
||||
*/
|
||||
@PostMapping("/pay/transaction/getPayTransaction")
|
||||
CommonResult<PayTransactionRespDTO> getPayTransaction(@RequestBody PayTransactionGetReqDTO getReqDTO);
|
||||
|
||||
/**
|
||||
* 更新交易支付成功
|
||||
*
|
||||
* @param successReqDTO 支付成功信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
@PostMapping("/pay/transaction/updatePayTransactionSuccess")
|
||||
CommonResult<Boolean> updatePayTransactionSuccess(@RequestBody PayTransactionSuccessReqDTO successReqDTO);
|
||||
|
||||
/**
|
||||
* 获得交易支付单分页
|
||||
*
|
||||
* @param pageReqDTO 分页条件
|
||||
* @return 交易支付单分页
|
||||
*/
|
||||
@PostMapping("/pay/transaction/pagePayTransaction")
|
||||
CommonResult<PageResult<PayTransactionRespDTO>> pagePayTransaction(@RequestBody PayTransactionPageReqDTO pageReqDTO);
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.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;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付交易单创建 DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionCreateReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
@NotNull(message = "用户编号不能为空")
|
||||
private Integer userId;
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotEmpty(message = "应用编号不能为空")
|
||||
private String appId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
@NotEmpty(message = "IP 不能为空")
|
||||
private String createIp;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
@NotEmpty(message = "订单号不能为空")
|
||||
private String orderId;
|
||||
/**
|
||||
* 商品名
|
||||
*/
|
||||
@NotEmpty(message = "商品名不能为空")
|
||||
@Length(max = 32, message = "商品名不能超过32")
|
||||
private String orderSubject;
|
||||
/**
|
||||
* 订单商品描述
|
||||
*/
|
||||
@NotEmpty(message = "商品描述不能为空")
|
||||
@Length(max = 128, message = "商品描述长度不能超过128")
|
||||
private String orderDescription;
|
||||
/**
|
||||
* 订单商品备注
|
||||
*/
|
||||
@Length(max = 256, message = "商品备注长度不能超过256")
|
||||
private String orderMemo;
|
||||
/**
|
||||
* 支付金额,单位:分
|
||||
*/
|
||||
@NotNull(message = "金额不能为空")
|
||||
@DecimalMin(value = "0", inclusive = false, message = "金额必须大于零")
|
||||
private Integer price;
|
||||
|
||||
/**
|
||||
* 交易过期时间
|
||||
*/
|
||||
@NotNull(message = "交易过期时间不能为空")
|
||||
private Date expireTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付交易获得 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionGetReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotEmpty(message = "应用编号不能为空")
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
@NotEmpty(message = "订单号不能为空")
|
||||
private String orderId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
|
||||
import cn.iocoder.common.framework.vo.PageParam;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付交易分页 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionPageReqDTO extends PageParam {
|
||||
|
||||
/**
|
||||
* 创建时间(开始)
|
||||
*/
|
||||
private Date createBeginTime;
|
||||
/**
|
||||
* 创建时间(结束)
|
||||
*/
|
||||
private Date createEndTime;
|
||||
/**
|
||||
* 支付时间(开始)
|
||||
*/
|
||||
private Date paymentBeginTime;
|
||||
/**
|
||||
* 支付时间(结束)
|
||||
*/
|
||||
private Date paymentEndTime;
|
||||
/**
|
||||
* 支付状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 是否有退款
|
||||
*/
|
||||
private Boolean hasRefund;
|
||||
/**
|
||||
* 支付渠道
|
||||
*/
|
||||
private Integer payChannel;
|
||||
/**
|
||||
* 商品标题
|
||||
*
|
||||
* 模糊匹配
|
||||
*/
|
||||
private String orderSubject;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付交易 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
private String createIp;
|
||||
/**
|
||||
* 业务线的订单编号
|
||||
*/
|
||||
private String orderId;
|
||||
/**
|
||||
* 订单商品名
|
||||
*/
|
||||
private String orderSubject;
|
||||
/**
|
||||
* 订单商品描述
|
||||
*/
|
||||
private String orderDescription;
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String orderMemo;
|
||||
/**
|
||||
* 支付金额,单位:分。
|
||||
*/
|
||||
private Integer price;
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 交易过期时间
|
||||
*/
|
||||
private Date expireTime;
|
||||
/**
|
||||
* 回调业务线完成时间
|
||||
*/
|
||||
private Date finishTime;
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 成功支付的交易拓展编号
|
||||
*/
|
||||
private Integer extensionId;
|
||||
/**
|
||||
* 支付成功的支付渠道
|
||||
*/
|
||||
private Integer payChannel;
|
||||
/**
|
||||
* 第三方支付成功的时间
|
||||
*/
|
||||
private Date paymentTime;
|
||||
/**
|
||||
* 收到第三方系统通知的时间
|
||||
*/
|
||||
private Date notifyTime;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 退款总金额
|
||||
*/
|
||||
private Integer refundTotal;
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
|
||||
import cn.iocoder.common.framework.validator.InEnum;
|
||||
import cn.iocoder.mall.payservice.enums.PayChannelEnum;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付交易提交 Request VO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionSubmitReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
@NotEmpty(message = "应用编号不能为空")
|
||||
private String appId;
|
||||
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
@NotEmpty(message = "IP 不能为空")
|
||||
private String createIp;
|
||||
|
||||
/**
|
||||
* 订单号
|
||||
*/
|
||||
@NotEmpty(message = "订单号不能为空")
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 支付渠道
|
||||
*/
|
||||
@InEnum(value = PayChannelEnum.class, message = "支付渠道必须是 {value}")
|
||||
@NotNull(message = "支付渠道")
|
||||
private Integer payChannel;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 支付交易提交 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionSubmitRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 支付交易拓展单编号
|
||||
*/
|
||||
private Integer id;
|
||||
|
||||
/**
|
||||
* 调用三方平台的响应结果
|
||||
*/
|
||||
private String invokeResponse;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 交易支付成功 Request DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionSuccessReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 支付渠道
|
||||
*/
|
||||
private Integer payChannel;
|
||||
/**
|
||||
* 支付渠道的回调参数
|
||||
*/
|
||||
private String params;
|
||||
|
||||
}
|
||||
139
归档/pay-service-project/pay-service-app/pom.xml
Normal file
139
归档/pay-service-project/pay-service-app/pom.xml
Normal file
@@ -0,0 +1,139 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>pay-service-project</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>pay-service-app</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- RPC 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-dubbo</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!-- 支付服务 -->
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>pay-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId> <!-- 需要开启 Web 容器,因为 Actuator 需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<!-- MQ 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-rocketmq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Job 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-xxl-job</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Registry 和 Config 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- DB 相关 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 监控相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 云服务相关 -->
|
||||
<dependency>
|
||||
<groupId>Pingplusplus</groupId>
|
||||
<artifactId>pingpp-java</artifactId>
|
||||
<version>2.2.4</version>
|
||||
<type>jar</type>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- 工具类相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-jdk8</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.aspectj</groupId>
|
||||
<artifactId>aspectjweaver</artifactId>
|
||||
<version>1.9.6</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<!-- 设置构建的 jar 包名 -->
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
<!-- 使用 spring-boot-maven-plugin 插件打包 -->
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>bintray</name>
|
||||
<url>http://jcenter.bintray.com</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.iocoder.mall.payservice;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
public class PayServiceApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(PayServiceApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.client;
|
||||
@@ -0,0 +1,59 @@
|
||||
package cn.iocoder.mall.payservice.client.thirdpay;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayRefundSuccessRespDTO;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayTransactionSuccessRespDTO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 三方支付平台的 Client 抽象类
|
||||
*/
|
||||
public abstract class AbstractThirdPayClient {
|
||||
|
||||
/**
|
||||
* 提交支付请求给支付平台,并返回请求结果
|
||||
*
|
||||
* @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<ThirdPayTransactionSuccessRespDTO> 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<ThirdPayRefundSuccessRespDTO> parseRefundSuccessParams(String params);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package cn.iocoder.mall.payservice.client.thirdpay;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayRefundSuccessRespDTO;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayTransactionSuccessRespDTO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
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;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO 代码略乱,后面重构下
|
||||
public class PingxxThirdPayClient extends AbstractThirdPayClient {
|
||||
|
||||
static {
|
||||
Pingpp.privateKeyPath = "/Users/yunai/Downloads/pingxx.pem";
|
||||
Pingpp.apiKey = "sk_test_8a9SGSXLKqX1ennjX9DenvbT";
|
||||
}
|
||||
|
||||
@Override
|
||||
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 | InvalidRequestException |
|
||||
APIConnectionException | APIException |
|
||||
ChannelException | RateLimitException e) {
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e); // TODO 芋艿,后续优化
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
extra.put("open_id", "just_for_test");
|
||||
// 生成支付对象
|
||||
Map<String, Object> reqObj = new HashMap<>();
|
||||
reqObj.put("subject", transaction.getOrderSubject());
|
||||
reqObj.put("body", transaction.getOrderDescription());
|
||||
reqObj.put("description", transaction.getOrderMemo());
|
||||
reqObj.put("amount", transaction.getPrice());
|
||||
reqObj.put("order_no", transactionExtension.getTransactionCode());
|
||||
reqObj.put("channel", channel);
|
||||
reqObj.put("currency", "cny");
|
||||
reqObj.put("client_ip", transactionExtension.getCreateIp());
|
||||
reqObj.put("app", ImmutableMap.of("id", "app_aTyfXDjrvzDSbLuz")); // TODO 写死先
|
||||
reqObj.put("extra", extra);
|
||||
return reqObj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<ThirdPayTransactionSuccessRespDTO> parseTransactionSuccessParams(String params) {
|
||||
JSONObject paramsObj = JSON.parseObject(params);
|
||||
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||
ThirdPayTransactionSuccessRespDTO successRespDTO = new ThirdPayTransactionSuccessRespDTO()
|
||||
.setTransactionCode(chargeObj.getString("order_no"))
|
||||
.setPaymentTime(new Date(chargeObj.getLong("time_paid") * 1000))
|
||||
.setTradeNo(chargeObj.getString("transaction_no"));
|
||||
return CommonResult.success(successRespDTO);
|
||||
}
|
||||
|
||||
@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(refund, 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 芋艿,后续优化
|
||||
}
|
||||
}
|
||||
|
||||
// {"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
|
||||
public CommonResult<ThirdPayRefundSuccessRespDTO> parseRefundSuccessParams(String params) {
|
||||
JSONObject paramsObj = JSON.parseObject(params);
|
||||
JSONObject chargeObj = paramsObj.getJSONObject("data").getJSONObject("object");
|
||||
ThirdPayRefundSuccessRespDTO successRespDTO = new ThirdPayRefundSuccessRespDTO()
|
||||
.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(successRespDTO);
|
||||
}
|
||||
|
||||
private Map<String, Object> createRefundRequest(PayRefundDO refund, 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);
|
||||
Map<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("refundCode", refund.getRefundCode());
|
||||
reqObj.put("metadata", metadata);
|
||||
return reqObj;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (true) { // 测试支付请求
|
||||
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 PingxxThirdPayClient().submitTransaction(transaction, extension, null);
|
||||
}
|
||||
if (false) { // 测试退款请求
|
||||
PayRefundDO refund = new PayRefundDO().setPrice(9999999).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 PingxxThirdPayClient().submitRefund(refund, transactionExtension, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.iocoder.mall.payservice.client.thirdpay;
|
||||
|
||||
import cn.iocoder.mall.payservice.enums.PayChannelEnum;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class ThirdPayClientFactory {
|
||||
|
||||
private static Map<Integer, AbstractThirdPayClient> CLIENTS = new HashMap<>();
|
||||
|
||||
static {
|
||||
CLIENTS.put(PayChannelEnum.PINGXX.getId(), new PingxxThirdPayClient());
|
||||
}
|
||||
|
||||
public static AbstractThirdPayClient getThirdPayClient(Integer payChannel) {
|
||||
AbstractThirdPayClient client = CLIENTS.get(payChannel);
|
||||
if (client == null) {
|
||||
throw new NullPointerException("找不到合适的 ThirdPayClient :" + payChannel);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package cn.iocoder.mall.payservice.client.thirdpay.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 三方平台的交易退款成功 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ThirdPayRefundSuccessRespDTO {
|
||||
|
||||
/**
|
||||
* 生成传输给第三方的订单号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String refundCode;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 第三方退款成功的时间
|
||||
*/
|
||||
private Date refundTime;
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.payservice.client.thirdpay.dto;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 三方平台的交易支付成功 Response DTO
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class ThirdPayTransactionSuccessRespDTO {
|
||||
|
||||
/**
|
||||
* 生成传输给第三方的订单号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String transactionCode;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
/**
|
||||
* 第三方支付成功的时间
|
||||
*/
|
||||
private Date paymentTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package cn.iocoder.mall.payservice.common.dubbo;
|
||||
|
||||
import cn.iocoder.common.framework.util.StringUtils;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
import lombok.Data;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ReferenceConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
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;
|
||||
|
||||
public ReferenceMeta getReferenceMeta(String notifyUrl) {
|
||||
DubboReferencePool.ReferenceMeta referenceMeta = referenceMetaCache.getUnchecked(notifyUrl);
|
||||
Assert.notNull(referenceMeta, String.format("notifyUrl(%s) 不存在对应的 ReferenceMeta 对象", notifyUrl));
|
||||
return referenceMeta;
|
||||
}
|
||||
|
||||
private ReferenceMeta createGenericService(String notifyUrl) {
|
||||
// 使用 # 号分隔,格式为 服务名#方法名#版本号
|
||||
List<String> notifyUrlParts = this.parseNotifyUrl(notifyUrl);
|
||||
// 创建 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.get(0)); // 弱类型接口名
|
||||
reference.setGeneric(true); // 声明为泛化接口
|
||||
reference.setApplication(application);
|
||||
reference.setVersion(notifyUrlParts.size() > 2 ? notifyUrlParts.get(2) : "1.0.0"); // 如果未配置服务的版本号,则默认使用 1.0.0
|
||||
// 获得 GenericService 对象
|
||||
GenericService genericService = reference.get();
|
||||
// 构建最终的 ReferenceMeta 对象
|
||||
return new ReferenceMeta(reference, genericService, notifyUrlParts.get(1));
|
||||
}
|
||||
|
||||
// TODO 芋艿,后续重构成一个对象
|
||||
private List<String> parseNotifyUrl(String notifyUrl) {
|
||||
return StringUtils.split(notifyUrl, "#");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.common;
|
||||
@@ -0,0 +1,12 @@
|
||||
package cn.iocoder.mall.payservice.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
|
||||
/**
|
||||
* Spring Aop 配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
|
||||
public class AopConfiguration {
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.mall.payservice.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
||||
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
@Configuration
|
||||
@MapperScan("cn.iocoder.mall.payservice.dal.mysql.mapper") // 扫描对应的 Mapper 接口
|
||||
@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理。
|
||||
public class DatabaseConfiguration {
|
||||
|
||||
// 数据库连接池 Druid
|
||||
|
||||
@Bean
|
||||
public ISqlInjector sqlInjector() {
|
||||
return new DefaultSqlInjector(); // MyBatis Plus 逻辑删除
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PaginationInterceptor paginationInterceptor() {
|
||||
return new PaginationInterceptor(); // MyBatis Plus 分页插件
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package cn.iocoder.mall.payservice.convert.app;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.mall.payservice.rpc.app.dto.PayAppRespDTO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayAppConvert {
|
||||
|
||||
PayAppConvert INSTANCE = Mappers.getMapper(PayAppConvert.class);
|
||||
|
||||
PayAppRespDTO convert(PayAppDO bean);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.payservice.convert.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayRefundSuccessMessage;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayTransactionSuccessMessage;
|
||||
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 entity);
|
||||
|
||||
@Mappings({
|
||||
@Mapping(source = "refund.transactionId", target = "transactionId"),
|
||||
@Mapping(source = "refund.orderId", target = "orderId"),
|
||||
@Mapping(source = "refund.refundId", target = "refundId"),
|
||||
})
|
||||
PayRefundSuccessMessage convertRefund(PayNotifyTaskDO entity);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package cn.iocoder.mall.payservice.convert.transaction;
|
||||
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionCreateReqDTO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionRespDTO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionSubmitReqDTO;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayTransactionConvert {
|
||||
|
||||
PayTransactionConvert INSTANCE = Mappers.getMapper(PayTransactionConvert.class);
|
||||
|
||||
PayTransactionDO convert(PayTransactionCreateReqDTO bean);
|
||||
|
||||
PayTransactionExtensionDO convert(PayTransactionSubmitReqDTO bean);
|
||||
|
||||
PayTransactionRespDTO convert(PayTransactionDO bean);
|
||||
|
||||
@Mapping(source = "records", target = "list")
|
||||
PageResult<PayTransactionRespDTO> convertPage(IPage<PayTransactionDO> bean);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.app;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 支付应用
|
||||
*
|
||||
* 每个接入的业务都是一个应用,进行个性化的配置
|
||||
*/
|
||||
@TableName("pay_app")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayAppDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String id;
|
||||
/**
|
||||
* 应用名
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String payNotifyUrl;
|
||||
/**
|
||||
* 退款异步通知地址
|
||||
*/
|
||||
private String refundNotifyUrl;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 枚举 {@link cn.iocoder.common.framework.enums.CommonStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.log;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 交易日志 DO
|
||||
*
|
||||
* 通过该日志,我们可以追溯整个执行过程
|
||||
*
|
||||
* TODO 芋艿,后面在捉摸
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayLogDO {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 业务线订单编号
|
||||
*/
|
||||
private Integer orderId;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.notify;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 支付通知 App 的日志 DO
|
||||
*
|
||||
* 通过该表,记录通知 App 时,产生的日志
|
||||
*/
|
||||
@TableName("pay_notify_log")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayNotifyLogDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 日志编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 通知编号
|
||||
*/
|
||||
private Integer notifyId;
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private String request;
|
||||
/**
|
||||
* 响应结果
|
||||
*/
|
||||
private String response;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.notify;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付通知 App 的任务 DO
|
||||
*
|
||||
* 目前包括支付通知、退款通知。
|
||||
*/
|
||||
@TableName("pay_notify_task")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayNotifyTaskDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 通知频率,单位为秒。
|
||||
*
|
||||
* 算上首次的通知,实际是一共 1 + 8 = 9 次。
|
||||
*/
|
||||
public static final Integer[] NOTIFY_FREQUENCY = new Integer[]{
|
||||
15, 15, 30, 180,
|
||||
1800, 1800, 1800, 3600
|
||||
};
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 类型
|
||||
*
|
||||
* 外键 {@link PayNotifyType}
|
||||
*/
|
||||
private Integer type;
|
||||
/**
|
||||
* 通知状态
|
||||
*
|
||||
* 外键 {@link PayNotifyStatusEnum}
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 是否激活中,即处于正在 MQ 异步通知中
|
||||
*
|
||||
* @see cn.iocoder.mall.payservice.job.notify.PayNotifyRetryJob
|
||||
*/
|
||||
private Boolean active;
|
||||
/**
|
||||
* 下一次通知时间
|
||||
*/
|
||||
private Date nextNotifyTime;
|
||||
/**
|
||||
* 最后一次执行时间
|
||||
*/
|
||||
private Date lastExecuteTime;
|
||||
/**
|
||||
* 当前通知次数
|
||||
*/
|
||||
private Integer notifyTimes;
|
||||
/**
|
||||
* 最大可通知次数
|
||||
*/
|
||||
private Integer maxNotifyTimes;
|
||||
/**
|
||||
* 通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
// TODO 芋艿,未来把 transaction 和 refund 优化成一个字段。现在为了方便。
|
||||
/**
|
||||
* 支付数据
|
||||
*/
|
||||
@TableField(typeHandler = FastjsonTypeHandler.class)
|
||||
private Transaction transaction;
|
||||
/**
|
||||
* 退款数据
|
||||
*/
|
||||
@TableField(typeHandler = FastjsonTypeHandler.class)
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.refund;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
import cn.iocoder.mall.payservice.enums.refund.PayRefundStatus;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 退款单 DO
|
||||
*/
|
||||
@TableName("pay_refund")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayRefundDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 支付交易编号
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 退款状态
|
||||
*
|
||||
* 外键 {@link 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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* TODO 重复支付的交易
|
||||
*
|
||||
* 可能不靠这个表,而是差错处理。
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@Deprecated
|
||||
public class PayRepeatTransactionDO {
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 支付交易表
|
||||
*/
|
||||
@TableName("pay_transaction")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
@TableId
|
||||
private Integer id;
|
||||
/**
|
||||
* 用户编号
|
||||
*/
|
||||
private Integer userId;
|
||||
/**
|
||||
* 应用编号
|
||||
*/
|
||||
private String appId;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
private String createIp;
|
||||
/**
|
||||
* 业务线的订单编号
|
||||
*/
|
||||
private String orderId;
|
||||
/**
|
||||
* 订单商品名
|
||||
*/
|
||||
private String orderSubject;
|
||||
/**
|
||||
* 订单商品描述
|
||||
*/
|
||||
private String orderDescription;
|
||||
/**
|
||||
* 订单备注
|
||||
*/
|
||||
private String orderMemo;
|
||||
/**
|
||||
* 支付金额,单位:分。
|
||||
*/
|
||||
private Integer price;
|
||||
/**
|
||||
* 订单状态
|
||||
*/
|
||||
private Integer status;
|
||||
/**
|
||||
* 交易过期时间
|
||||
*/
|
||||
private Date expireTime;
|
||||
/**
|
||||
* 回调业务线完成时间
|
||||
*/
|
||||
private Date finishTime;
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
private String notifyUrl;
|
||||
/**
|
||||
* 成功支付的交易拓展编号
|
||||
*/
|
||||
private Integer extensionId;
|
||||
/**
|
||||
* 支付成功的支付渠道
|
||||
*/
|
||||
private Integer payChannel;
|
||||
|
||||
/**
|
||||
* 第三方支付成功的时间
|
||||
*/
|
||||
private Date paymentTime;
|
||||
/**
|
||||
* 收到第三方系统通知的时间
|
||||
*/
|
||||
private Date notifyTime;
|
||||
/**
|
||||
* 第三方的流水号
|
||||
*/
|
||||
private String tradeNo;
|
||||
|
||||
// ========== 退款相关 ==========
|
||||
|
||||
/**
|
||||
* 退款总金额
|
||||
*/
|
||||
private Integer refundTotal;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.dataobject.DeletableDO;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 交易扩展表
|
||||
*/
|
||||
@TableName("pay_transaction_extension")
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionExtensionDO extends DeletableDO {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 交易编号 {@link PayTransactionDO#getId()}
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 选择的支付渠道
|
||||
*/
|
||||
private Integer payChannel;
|
||||
/**
|
||||
* 生成传输给第三方的订单号
|
||||
*
|
||||
* 唯一索引
|
||||
*/
|
||||
private String transactionCode;
|
||||
/**
|
||||
* 扩展内容
|
||||
*
|
||||
* 异步通知的时候填充回调的数据
|
||||
*/
|
||||
private String extensionData;
|
||||
/**
|
||||
* 发起交易的 IP
|
||||
*/
|
||||
private String createIp;
|
||||
/**
|
||||
* 状态
|
||||
*
|
||||
* @see cn.iocoder.mall.payservice.enums.transaction.PayTransactionStatusEnum
|
||||
* 注意,只包含上述枚举的 WAITING 和 SUCCESS
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.mapper.app;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.app.PayAppDO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayAppMapper extends BaseMapper<PayAppDO> {
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.mapper.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyLogDO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayNotifyLogMapper extends BaseMapper<PayNotifyLogDO> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.mapper.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public interface PayNotifyTaskMapper extends BaseMapper<PayNotifyTaskDO> {
|
||||
|
||||
/**
|
||||
* 获得需要通知的 PayTransactionNotifyTaskDO 记录。需要满足如下条件:
|
||||
*
|
||||
* 1. status 非成功
|
||||
* 2. nextNotifyTime 小于当前时间
|
||||
* 3. active 为 false 并未正在执行中
|
||||
*
|
||||
* @return PayTransactionNotifyTaskDO 数组
|
||||
*/
|
||||
default List<PayNotifyTaskDO> selectListByNotify() {
|
||||
return selectList(new QueryWrapper<PayNotifyTaskDO>()
|
||||
.in("status", PayNotifyStatusEnum.WAITING.getStatus(), PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus(),
|
||||
PayNotifyStatusEnum.REQUEST_FAILURE.getStatus())
|
||||
.le("next_notify_time", "NOW()")
|
||||
.eq("active", Boolean.FALSE));
|
||||
}
|
||||
|
||||
default int update(PayNotifyTaskDO update, Integer whereNotifyTimes) {
|
||||
return update(update, new QueryWrapper<PayNotifyTaskDO>()
|
||||
.eq("id", update.getId()).eq("notify_times", whereNotifyTimes));
|
||||
}
|
||||
|
||||
//
|
||||
// <select id="selectByNotify" resultMap="PayNotifyTaskResultMap">
|
||||
// SELECT
|
||||
// <include refid="FIELDS"/>
|
||||
// FROM notify_task
|
||||
// WHERE status IN (1, 4, 5)
|
||||
// AND next_notify_time <![CDATA[ <= ]]> NOW()
|
||||
// AND last_execute_time > next_notify_time
|
||||
// </select>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.mapper.refund;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayRefundMapper extends BaseMapper<PayRefundDO> {
|
||||
|
||||
default int update(PayRefundDO entity, Integer whereStatus) {
|
||||
return update(entity, new QueryWrapper<PayRefundDO>()
|
||||
.eq("id", entity.getId()).eq("status", whereStatus));
|
||||
}
|
||||
|
||||
default PayRefundDO selectByRefundCode(String refundCode) {
|
||||
return selectOne(new QueryWrapper<PayRefundDO>()
|
||||
.eq("refund_code", refundCode));
|
||||
}
|
||||
|
||||
|
||||
// <if test="createBeginTime != null">
|
||||
// AND create_time >= #{createBeginTime}
|
||||
// </if>
|
||||
// <if test="createEndTime != null">
|
||||
// AND #{createEndTime} >= create_time
|
||||
// </if>
|
||||
// <if test="finishBeginTime != null">
|
||||
// AND finish_time >= #{finishBeginTime}
|
||||
// </if>
|
||||
// <if test="finishEndTime != null">
|
||||
// AND #{finishEndTime} >= finish_time
|
||||
// </if>
|
||||
// <if test="status != null">
|
||||
// AND status = #{status}
|
||||
// </if>
|
||||
// <if test="payChannel != null">
|
||||
// AND pay_channel = #{payChannel}
|
||||
// </if>
|
||||
|
||||
// List<PayRefundDO> selectListByPage(@Param("createBeginTime") Date createBeginTime,
|
||||
// @Param("createEndTime") Date createEndTime,
|
||||
// @Param("finishBeginTime") Date finishBeginTime,
|
||||
// @Param("finishEndTime") Date finishEndTime,
|
||||
// @Param("status") Integer status,
|
||||
// @Param("payChannel") Integer payChannel,
|
||||
// @Param("offset") Integer offset,
|
||||
// @Param("limit") Integer limit);
|
||||
//
|
||||
// Integer selectCountByPage(@Param("createBeginTime") Date createBeginTime,
|
||||
// @Param("createEndTime") Date createEndTime,
|
||||
// @Param("finishBeginTime") Date finishBeginTime,
|
||||
// @Param("finishEndTime") Date finishEndTime,
|
||||
// @Param("status") Integer status,
|
||||
// @Param("payChannel") Integer payChannel);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.mapper.transaction;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayTransactionExtensionMapper extends BaseMapper<PayTransactionExtensionDO> {
|
||||
|
||||
default int update(PayTransactionExtensionDO entity, Integer whereStatus) {
|
||||
return update(entity, new QueryWrapper<PayTransactionExtensionDO>()
|
||||
.eq("id", entity.getId()).eq("status", whereStatus));
|
||||
}
|
||||
|
||||
default PayTransactionExtensionDO selectByTransactionCode(String transactionCode) {
|
||||
return selectOne(new QueryWrapper<PayTransactionExtensionDO>()
|
||||
.eq("transaction_code", transactionCode));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package cn.iocoder.mall.payservice.dal.mysql.mapper.transaction;
|
||||
|
||||
import cn.iocoder.mall.mybatis.core.query.QueryWrapperX;
|
||||
import cn.iocoder.mall.mybatis.core.util.PageUtil;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionPageReqDTO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface PayTransactionMapper extends BaseMapper<PayTransactionDO> {
|
||||
|
||||
|
||||
|
||||
default IPage<PayTransactionDO> selectPage(PayTransactionPageReqDTO pageReqDTO) {
|
||||
QueryWrapperX<PayTransactionDO> query = new QueryWrapperX<PayTransactionDO>()
|
||||
.betweenIfPresent("create_time", pageReqDTO.getCreateBeginTime(), pageReqDTO.getPaymentEndTime())
|
||||
.betweenIfPresent("payment_time", pageReqDTO.getPaymentBeginTime(), pageReqDTO.getPaymentEndTime())
|
||||
.eqIfPresent("status", pageReqDTO.getStatus())
|
||||
.eqIfPresent("payChannel", pageReqDTO.getPayChannel())
|
||||
.likeIfPresent("order_subject", pageReqDTO.getOrderSubject());
|
||||
if (pageReqDTO.getHasRefund() != null) {
|
||||
if (pageReqDTO.getHasRefund()) {
|
||||
query.gt("refund_total", 0);
|
||||
} else {
|
||||
query.eq("refund_total", 0);
|
||||
}
|
||||
}
|
||||
return selectPage(PageUtil.build(pageReqDTO), query);
|
||||
}
|
||||
|
||||
default int update(PayTransactionDO entity, Integer whereStatus) {
|
||||
return update(entity, new QueryWrapper<PayTransactionDO>()
|
||||
.eq("id", entity.getId()).eq("status", whereStatus));
|
||||
}
|
||||
|
||||
default PayTransactionDO selectByAppIdAndOrderId(String appId, String orderId) {
|
||||
return selectOne(new QueryWrapper<PayTransactionDO>().eq("app_id", appId)
|
||||
.eq("order_id", orderId));
|
||||
}
|
||||
|
||||
int updatePriceTotalIncr(@Param("id") Integer id, @Param("refundTotalIncr") Integer refundTotalIncr);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package cn.iocoder.mall.payservice.job.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.notify.PayNotifyTaskMapper;
|
||||
import cn.iocoder.mall.payservice.service.notify.PayNotifyService;
|
||||
import com.xxl.job.core.biz.model.ReturnT;
|
||||
import com.xxl.job.core.handler.IJobHandler;
|
||||
import com.xxl.job.core.handler.annotation.XxlJob;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 支付通知重试 Job
|
||||
*
|
||||
* 由于 RocketMQ 不支持指定时间的延迟消息,所以我们需要通过 Job 扫描到达 {@link PayNotifyTaskDO#getNextNotifyTime()} 时间的任务。
|
||||
* 扫描到后,通过发送 MQ 去异步通知,提高通知效率。
|
||||
*
|
||||
* 考虑到 MQ 执行可能存在延迟的情况,导致一个 {@link PayNotifyTaskDO} 同时触发多个通知,通过 {@link PayNotifyTaskDO#getActive()} 标记解决。
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PayNotifyRetryJob extends IJobHandler {
|
||||
|
||||
@Autowired
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PayNotifyService payNotifyService;
|
||||
|
||||
@Override
|
||||
@XxlJob("payNotifyRetryJob")
|
||||
public ReturnT<String> execute(String param) {
|
||||
// 获得需要通知的任务
|
||||
List<PayNotifyTaskDO> notifyTasks = payNotifyTaskMapper.selectListByNotify();
|
||||
|
||||
// 循环任务,发送通知
|
||||
for (PayNotifyTaskDO notifyTask : notifyTasks) {
|
||||
// 发送 MQ
|
||||
payNotifyService.sendNotifyMessage(notifyTask);
|
||||
|
||||
// 标记任务执行中。考虑到 MQ 可能会存在先于该操作执行完,所以更新时,增加一个 notifyTimes 作为额外条件,避免覆盖更新的问题。
|
||||
PayNotifyTaskDO updateNotifyTask = new PayNotifyTaskDO().setId(notifyTask.getId()).setActive(true);
|
||||
payNotifyTaskMapper.update(updateNotifyTask, notifyTask.getNotifyTimes());
|
||||
}
|
||||
return new ReturnT<>("执行通知数:" + notifyTasks.size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.job;
|
||||
@@ -0,0 +1,107 @@
|
||||
package cn.iocoder.mall.payservice.mq.consumer;
|
||||
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.common.framework.util.ExceptionUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.common.dubbo.DubboReferencePool;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyLogDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.notify.PayNotifyLogMapper;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.notify.PayNotifyTaskMapper;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.AbstractPayNotifySuccessMessage;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class AbstractPayNotifySuccessMQConsumer<T extends AbstractPayNotifySuccessMessage> {
|
||||
// implements RocketMQListener<T> TODO 芋艿,理论来说,可以实现 RocketMQListener 接口,然后 execute 作为 onMessage 的具体实现。但是新版本貌似不行,后续在排查下;
|
||||
|
||||
@Autowired
|
||||
private DubboReferencePool dubboReferencePool;
|
||||
|
||||
@Autowired
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
@Autowired
|
||||
private PayNotifyLogMapper payTransactionNotifyLogMapper;
|
||||
|
||||
@Transactional
|
||||
public void execute(T message) {
|
||||
// 发起调用
|
||||
CommonResult<Boolean> invokeResult = null; // RPC / HTTP 调用的响应
|
||||
Throwable invokeException = null; //
|
||||
PayNotifyTaskDO updateTask = new PayNotifyTaskDO() // 更新 PayTransactionNotifyTaskDO 对象
|
||||
.setId(message.getId())
|
||||
.setActive(false) // 标记本地通知已经完成
|
||||
.setLastExecuteTime(new Date())
|
||||
.setNotifyTimes(message.getNotifyTimes() + 1);
|
||||
try {
|
||||
// 获得 ReferenceMeta 对象
|
||||
DubboReferencePool.ReferenceMeta referenceMeta = dubboReferencePool.getReferenceMeta(message.getNotifyUrl());
|
||||
// TODO 芋艿,这里要优化下,不要在事务里,进行 RPC 调用
|
||||
invokeResult = invoke(message, referenceMeta);
|
||||
if (invokeResult.isSuccess()) { // 情况一,请求成功且返回成功
|
||||
// 更新通知成功
|
||||
updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
// 需要更新支付交易单通知应用成功
|
||||
afterInvokeSuccess(message);
|
||||
} else { // 情况二,请求成功且返回失败
|
||||
// 更新通知请求成功,但是结果失败
|
||||
handleFailure(updateTask, PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
}
|
||||
} catch (Throwable e) { // 请求失败
|
||||
invokeException = e;
|
||||
// 更新通知请求失败
|
||||
handleFailure(updateTask, PayNotifyStatusEnum.REQUEST_FAILURE.getStatus());
|
||||
payNotifyTaskMapper.updateById(updateTask);
|
||||
// 抛出异常,回滚事务
|
||||
// TODO 芋艿,此处不能抛出异常。因为,会导致 MQ + 定时任务多重试。此处的目标是,事务回滚 + 吃掉事务。另外,最后的 finally 的日志,要插入成功。
|
||||
// throw e;
|
||||
} finally {
|
||||
// 插入 PayTransactionNotifyLogDO 日志
|
||||
PayNotifyLogDO notifyLog = new PayNotifyLogDO().setNotifyId(message.getId())
|
||||
.setStatus(updateTask.getStatus())
|
||||
.setRequest(JSON.toJSONString(message))
|
||||
.setResponse(invokeResult != null ? JSON.toJSONString(invokeResult) : ExceptionUtil.getRootCauseMessage(invokeException));
|
||||
payTransactionNotifyLogMapper.insert(notifyLog);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFailure(PayNotifyTaskDO updateTask, Integer defaultStatus) {
|
||||
if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
|
||||
updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
|
||||
} else {
|
||||
updateTask.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
|
||||
updateTask.setStatus(defaultStatus);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract CommonResult<Boolean> invoke(T message, DubboReferencePool.ReferenceMeta referenceMeta);
|
||||
|
||||
protected abstract void afterInvokeSuccess(T message);
|
||||
|
||||
/**
|
||||
* 将 Dubbo 泛化调用的结果,解析成 CommonResult
|
||||
*
|
||||
* 目前,约定 Dubbo 返回的结果为 CommonResult<Boolean>
|
||||
*
|
||||
* @param dubboResult Dubbo 调用结果
|
||||
* @return CommonResult 结果
|
||||
*/
|
||||
protected static CommonResult<Boolean> parseDubboGenericResult(Object dubboResult) {
|
||||
// TODO 芋艿,目前暂时这么实现,未来找下更合适的
|
||||
Map<String, Object> dubboResultMap = (Map<String, Object>) dubboResult;
|
||||
CommonResult<Boolean> commonResult = new CommonResult<>();
|
||||
commonResult.setCode((Integer) dubboResultMap.get("code"));
|
||||
commonResult.setMessage((String) dubboResultMap.get("message"));
|
||||
commonResult.setData((Boolean) dubboResultMap.get("data"));
|
||||
return commonResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.mall.payservice.mq.consumer;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.common.dubbo.DubboReferencePool;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.refund.PayRefundMapper;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayRefundSuccessMessage;
|
||||
import org.apache.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 PayRefundSuccessMQConsumer extends AbstractPayNotifySuccessMQConsumer<PayRefundSuccessMessage>
|
||||
implements RocketMQListener<PayRefundSuccessMessage> {
|
||||
|
||||
@Autowired
|
||||
private PayRefundMapper payRefundMapper;
|
||||
|
||||
@Override
|
||||
public void onMessage(PayRefundSuccessMessage message) {
|
||||
super.execute(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommonResult<Boolean> 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();
|
||||
Object dubboResult = genericService.$invoke(methodName,
|
||||
new String[]{String.class.getName(), Integer.class.getName()},
|
||||
new Object[]{message.getOrderId(), refund.getPrice()});
|
||||
return parseDubboGenericResult(dubboResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterInvokeSuccess(PayRefundSuccessMessage message) {
|
||||
PayRefundDO updateRefund = new PayRefundDO().setId(message.getRefundId()).setFinishTime(new Date());
|
||||
payRefundMapper.updateById(updateRefund);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package cn.iocoder.mall.payservice.mq.consumer;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.mall.payservice.common.dubbo.DubboReferencePool;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.transaction.PayTransactionMapper;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayTransactionSuccessMessage;
|
||||
import org.apache.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 = PayTransactionSuccessMessage.TOPIC,
|
||||
consumerGroup = "pay-consumer-group-" + PayTransactionSuccessMessage.TOPIC
|
||||
)
|
||||
public class PayTransactionSuccessMQConsumer extends AbstractPayNotifySuccessMQConsumer<PayTransactionSuccessMessage>
|
||||
implements RocketMQListener<PayTransactionSuccessMessage> {
|
||||
|
||||
@Autowired
|
||||
private PayTransactionMapper payTransactionMapper;
|
||||
|
||||
@Override
|
||||
public void onMessage(PayTransactionSuccessMessage message) {
|
||||
super.execute(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CommonResult<Boolean> invoke(PayTransactionSuccessMessage message, DubboReferencePool.ReferenceMeta referenceMeta) {
|
||||
// 查询支付交易
|
||||
PayTransactionDO transaction = payTransactionMapper.selectById(message.getTransactionId());
|
||||
Assert.notNull(transaction, String.format("回调消息(%s) 订单交易不能为空", message.toString()));
|
||||
// 执行调用
|
||||
GenericService genericService = referenceMeta.getService();
|
||||
String methodName = referenceMeta.getMethodName();
|
||||
Object dubboResult = genericService.$invoke(methodName,
|
||||
new String[]{String.class.getName(), Integer.class.getName()},
|
||||
new Object[]{message.getOrderId(), transaction.getPrice()});
|
||||
return parseDubboGenericResult(dubboResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterInvokeSuccess(PayTransactionSuccessMessage message) {
|
||||
PayTransactionDO updateTransaction = new PayTransactionDO().setId(message.getTransactionId()).setFinishTime(new Date());
|
||||
payTransactionMapper.updateById(updateTransaction);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cn.iocoder.mall.payservice.mq.producer;
|
||||
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayRefundSuccessMessage;
|
||||
import cn.iocoder.mall.payservice.mq.producer.message.PayTransactionSuccessMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.rocketmq.client.producer.SendResult;
|
||||
import org.apache.rocketmq.client.producer.SendStatus;
|
||||
import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@Slf4j
|
||||
// TODO 芋艿:后续优化下,考虑下一致性
|
||||
public class PayMQProducer {
|
||||
|
||||
@Autowired
|
||||
private RocketMQTemplate template;
|
||||
|
||||
public void sendPayRefundNotifyTaskMessage(PayRefundSuccessMessage message) {
|
||||
try {
|
||||
SendResult sendResult = template.syncSend(PayTransactionSuccessMessage.TOPIC, message);
|
||||
if (!SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
|
||||
log.error("[sendPayRefundNotifyTaskMessage][消息({}) 发送更新消息失败,结果为({})]", message, sendResult);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
log.error("[sendPayRefundNotifyTaskMessage][消息({}) 发送更新消息失败,发生异常]", message, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendPayTransactionNotifyTaskMessage(PayTransactionSuccessMessage message) {
|
||||
try {
|
||||
SendResult sendResult = template.syncSend(PayTransactionSuccessMessage.TOPIC, message);
|
||||
if (!SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
|
||||
log.error("[sendPayTransactionNotifyTaskMessage][消息({}) 发送更新消息失败,结果为({})]", message, sendResult);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
log.error("[sendPayTransactionNotifyTaskMessage][消息({}) 发送更新消息失败,发生异常]", message, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.mall.payservice.mq.producer.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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package cn.iocoder.mall.payservice.mq.producer.message;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 支付退款成功的消息对象
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PayRefundSuccessMessage extends AbstractPayNotifySuccessMessage {
|
||||
|
||||
public static final String TOPIC = "PAY_REFUND_SUCCESS";
|
||||
|
||||
/**
|
||||
* 退款单编号
|
||||
*/
|
||||
private Integer refundId;
|
||||
/**
|
||||
* 交易编号
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 应用订单编号
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package cn.iocoder.mall.payservice.mq.producer.message;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* 支付交易单支付成功的消息对象
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PayTransactionSuccessMessage extends AbstractPayNotifySuccessMessage {
|
||||
|
||||
public static final String TOPIC = "PAY_TRANSACTION_SUCCESS";
|
||||
|
||||
/**
|
||||
* 交易编号
|
||||
*/
|
||||
private Integer transactionId;
|
||||
/**
|
||||
* 应用订单编号
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.iocoder.mall.payservice.service.app;
|
||||
|
||||
import cn.iocoder.mall.payservice.rpc.app.dto.PayAppRespDTO;
|
||||
|
||||
/**
|
||||
* 支付应用 Service 接口
|
||||
*/
|
||||
public interface PayAppService {
|
||||
|
||||
/**
|
||||
* 交易支付应用的合法性
|
||||
*
|
||||
* 如果不合法,抛出 {@link cn.iocoder.common.framework.exception.ServiceException} 业务异常
|
||||
*
|
||||
* @param payAppId 应用编号
|
||||
* @return 应用信息
|
||||
*/
|
||||
PayAppRespDTO validPayApp(String payAppId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package cn.iocoder.mall.payservice.service.app.impl;
|
||||
|
||||
import cn.iocoder.common.framework.enums.CommonStatusEnum;
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.mall.payservice.convert.app.PayAppConvert;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.app.PayAppDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.app.PayAppMapper;
|
||||
import cn.iocoder.mall.payservice.rpc.app.dto.PayAppRespDTO;
|
||||
import cn.iocoder.mall.payservice.service.app.PayAppService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static cn.iocoder.mall.payservice.enums.PayErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 支付应用 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class PayAppServiceImpl implements PayAppService {
|
||||
|
||||
@Autowired
|
||||
private PayAppMapper payAppMapper;
|
||||
|
||||
@Override
|
||||
public PayAppRespDTO validPayApp(String payAppId) {
|
||||
PayAppDO payAppDO = payAppMapper.selectById(payAppId);
|
||||
// 校验是否存在
|
||||
if (payAppDO == null) {
|
||||
throw ServiceExceptionUtil.exception(PAY_APP_NOT_FOUND);
|
||||
}
|
||||
// 校验是否禁用
|
||||
if (CommonStatusEnum.DISABLE.getValue().equals(payAppDO.getStatus())) {
|
||||
throw ServiceExceptionUtil.exception(PAY_APP_IS_DISABLE);
|
||||
}
|
||||
return PayAppConvert.INSTANCE.convert(payAppDO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package cn.iocoder.mall.payservice.service.notify;
|
||||
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
|
||||
/**
|
||||
* 支付通知 Service 接口
|
||||
*/
|
||||
public interface PayNotifyService {
|
||||
|
||||
// TODO 芋艿:后续优化下,不要暴露 entity 出来
|
||||
void addPayRefundNotifyTask(PayRefundDO refund);
|
||||
|
||||
// TODO 芋艿:后续优化下,不要暴露 entity 出来
|
||||
void addPayTransactionNotifyTask(PayTransactionDO transaction, PayTransactionExtensionDO extension);
|
||||
|
||||
// TODO 芋艿:后续优化下,不要暴露 entity 出来
|
||||
void sendNotifyMessage(PayNotifyTaskDO notifyTask);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package cn.iocoder.mall.payservice.service.notify.impl;
|
||||
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.mall.payservice.convert.notify.PayNotifyConvert;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.notify.PayNotifyTaskDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.refund.PayRefundDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.notify.PayNotifyTaskMapper;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyStatusEnum;
|
||||
import cn.iocoder.mall.payservice.enums.notify.PayNotifyType;
|
||||
import cn.iocoder.mall.payservice.mq.producer.PayMQProducer;
|
||||
import cn.iocoder.mall.payservice.service.notify.PayNotifyService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
/**
|
||||
* 支付通知 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
public class PayNotifyServiceImpl implements PayNotifyService {
|
||||
|
||||
@Autowired
|
||||
private PayNotifyTaskMapper payNotifyTaskMapper;
|
||||
|
||||
@Autowired
|
||||
private PayMQProducer payMQProducer;
|
||||
|
||||
@Override
|
||||
public void addPayRefundNotifyTask(PayRefundDO refund) {
|
||||
PayNotifyTaskDO payNotifyTaskDO = this.createBasePayNotifyTaskDO(refund.getAppId(), refund.getNotifyUrl())
|
||||
.setType(PayNotifyType.REFUND.getType());
|
||||
// 设置 Refund 属性
|
||||
payNotifyTaskDO.setRefund(new PayNotifyTaskDO.Refund().setRefundId(refund.getId())
|
||||
.setTransactionId(refund.getTransactionId()).setOrderId(refund.getOrderId()));
|
||||
// 保存到数据库
|
||||
payNotifyTaskMapper.insert(payNotifyTaskDO);
|
||||
|
||||
// 发送 MQ 消息
|
||||
sendNotifyMessage(payNotifyTaskDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPayTransactionNotifyTask(PayTransactionDO transaction, PayTransactionExtensionDO extension) {
|
||||
PayNotifyTaskDO payNotifyTaskDO = this.createBasePayNotifyTaskDO(transaction.getAppId(), transaction.getNotifyUrl())
|
||||
.setType(PayNotifyType.TRANSACTION.getType());
|
||||
// 设置 Transaction 属性
|
||||
payNotifyTaskDO.setTransaction(new PayNotifyTaskDO.Transaction().setOrderId(transaction.getOrderId())
|
||||
.setTransactionId(extension.getTransactionId()).setTransactionExtensionId(extension.getId()));
|
||||
// 保存到数据库
|
||||
payNotifyTaskMapper.insert(payNotifyTaskDO);
|
||||
|
||||
// 发送 MQ 消息
|
||||
sendNotifyMessage(payNotifyTaskDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendNotifyMessage(PayNotifyTaskDO notifyTask) {
|
||||
if (PayNotifyType.TRANSACTION.getType().equals(notifyTask.getType())) {
|
||||
payMQProducer.sendPayTransactionNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertTransaction(notifyTask));
|
||||
} else if (PayNotifyType.REFUND.getType().equals(notifyTask.getType())) {
|
||||
payMQProducer.sendPayRefundNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertRefund(notifyTask));
|
||||
} else {
|
||||
throw new IllegalArgumentException(String.format("通知任务(%s) 无法发送通知消息", notifyTask.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
private PayNotifyTaskDO createBasePayNotifyTaskDO(String appId, String notifyUrl) {
|
||||
return new PayNotifyTaskDO()
|
||||
.setAppId(appId)
|
||||
.setStatus(PayNotifyStatusEnum.WAITING.getStatus()).setActive(true)
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1)
|
||||
.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[0]))
|
||||
.setNotifyUrl(notifyUrl);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package cn.iocoder.mall.payservice.service.transaction;
|
||||
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.*;
|
||||
|
||||
/**
|
||||
* 支付交易单 Service 接口
|
||||
*/
|
||||
public interface PayTransactionService {
|
||||
|
||||
/**
|
||||
* 创建支付交易单
|
||||
*
|
||||
* @param createReqDTO 创建信息
|
||||
* @return 支付交易单号
|
||||
*/
|
||||
Integer createPayTransaction(PayTransactionCreateReqDTO createReqDTO);
|
||||
|
||||
/**
|
||||
* 提交支付交易单
|
||||
*
|
||||
* @param submitReqDTO 提交信息
|
||||
* @return 提交响应,包含三方支付的响应
|
||||
*/
|
||||
PayTransactionSubmitRespDTO submitPayTransaction(PayTransactionSubmitReqDTO submitReqDTO);
|
||||
|
||||
/**
|
||||
* 获得当支付交易单
|
||||
*
|
||||
* @param getReqDTO 获得条件
|
||||
* @return 支付交易单
|
||||
*/
|
||||
PayTransactionRespDTO getPayTransaction(PayTransactionGetReqDTO getReqDTO);
|
||||
|
||||
/**
|
||||
* 更新交易支付成功
|
||||
*
|
||||
* 该接口用于不同支付平台,支付成功后,回调该接口
|
||||
*
|
||||
* @param payChannel 支付渠道
|
||||
* @param params 回调参数。
|
||||
* 因为不同平台,能够提供的参数不同,所以使用 String 类型统一接收,然后在使用不同的 AbstractThirdPayClient 进行处理。
|
||||
* @return 是否支付成功
|
||||
*/
|
||||
Boolean updateTransactionPaySuccess(Integer payChannel, String params);
|
||||
|
||||
/**
|
||||
* 获得交易支付单分页
|
||||
*
|
||||
* @param pageReqDTO 分页条件
|
||||
* @return 交易支付单分页
|
||||
*/
|
||||
PageResult<PayTransactionRespDTO> pagePayTransaction(PayTransactionPageReqDTO pageReqDTO);
|
||||
|
||||
/**
|
||||
* 增加交易支付单的退款总金额
|
||||
*
|
||||
* @param payTransactionId 支付交易单
|
||||
* @param incr 新增的退款金额
|
||||
* @return 是否增加成功
|
||||
*/
|
||||
boolean updateTransactionPriceTotalIncr(Integer payTransactionId, Integer incr);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
package cn.iocoder.mall.payservice.service.transaction.impl;
|
||||
|
||||
import cn.iocoder.common.framework.exception.util.ServiceExceptionUtil;
|
||||
import cn.iocoder.common.framework.util.DateUtil;
|
||||
import cn.iocoder.common.framework.util.MathUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.AbstractThirdPayClient;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.ThirdPayClientFactory;
|
||||
import cn.iocoder.mall.payservice.client.thirdpay.dto.ThirdPayTransactionSuccessRespDTO;
|
||||
import cn.iocoder.mall.payservice.convert.transaction.PayTransactionConvert;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.dataobject.transaction.PayTransactionExtensionDO;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.transaction.PayTransactionExtensionMapper;
|
||||
import cn.iocoder.mall.payservice.dal.mysql.mapper.transaction.PayTransactionMapper;
|
||||
import cn.iocoder.mall.payservice.enums.transaction.PayTransactionStatusEnum;
|
||||
import cn.iocoder.mall.payservice.rpc.app.dto.PayAppRespDTO;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.*;
|
||||
import cn.iocoder.mall.payservice.service.app.PayAppService;
|
||||
import cn.iocoder.mall.payservice.service.notify.PayNotifyService;
|
||||
import cn.iocoder.mall.payservice.service.transaction.PayTransactionService;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import static cn.iocoder.mall.payservice.enums.PayErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 支付交易单 Service 实现类
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
|
||||
@Autowired
|
||||
private PayTransactionMapper payTransactionMapper;
|
||||
@Autowired
|
||||
private PayTransactionExtensionMapper payTransactionExtensionMapper;
|
||||
|
||||
@Autowired
|
||||
private PayAppService payAppService;
|
||||
@Autowired
|
||||
private PayNotifyService payNotifyService;
|
||||
|
||||
@Override
|
||||
public Integer createPayTransaction(PayTransactionCreateReqDTO createReqDTO) {
|
||||
// 校验 App
|
||||
PayAppRespDTO payAppRespDTO = payAppService.validPayApp(createReqDTO.getAppId());
|
||||
|
||||
// 查询对应的支付交易单是否已经存在。如果是,则直接返回
|
||||
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(
|
||||
createReqDTO.getAppId(), createReqDTO.getOrderId());
|
||||
if (payTransaction != null) {
|
||||
log.warn("[createTransaction][appId({}) orderId({}) 已经存在对应的支付交易单({})]", createReqDTO.getAppId(),
|
||||
createReqDTO.getOrderId(), payTransaction.toString()); // 理论来说,不会出现这个情况
|
||||
return payTransaction.getId();
|
||||
}
|
||||
|
||||
// 创建支付交易单
|
||||
payTransaction = PayTransactionConvert.INSTANCE.convert(createReqDTO)
|
||||
.setStatus(PayTransactionStatusEnum.WAITING.getStatus())
|
||||
.setNotifyUrl(payAppRespDTO.getPayNotifyUrl());
|
||||
payTransactionMapper.insert(payTransaction);
|
||||
// 最终返回
|
||||
return payTransaction.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransactionSubmitRespDTO submitPayTransaction(PayTransactionSubmitReqDTO submitReqDTO) {
|
||||
// TODO 校验支付渠道是否有效
|
||||
// 校验 App 是否有效
|
||||
payAppService.validPayApp(submitReqDTO.getAppId());
|
||||
|
||||
// 获得 PayTransactionDO ,并校验其是否存在
|
||||
PayTransactionDO payTransaction = payTransactionMapper.selectByAppIdAndOrderId(
|
||||
submitReqDTO.getAppId(), submitReqDTO.getOrderId());
|
||||
if (payTransaction == null) { // 是否存在
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITING.getStatus().equals(payTransaction.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
|
||||
// 插入 PayTransactionExtensionDO
|
||||
PayTransactionExtensionDO payTransactionExtensionDO = PayTransactionConvert.INSTANCE.convert(submitReqDTO)
|
||||
.setTransactionId(payTransaction.getId()).setTransactionCode(generateTransactionCode())
|
||||
.setStatus(PayTransactionStatusEnum.WAITING.getStatus());
|
||||
payTransactionExtensionMapper.insert(payTransactionExtensionDO);
|
||||
|
||||
// 调用三方接口
|
||||
AbstractThirdPayClient thirdPayClient = ThirdPayClientFactory.getThirdPayClient(submitReqDTO.getPayChannel());
|
||||
CommonResult<String> invokeResult = thirdPayClient.submitTransaction(payTransaction, payTransactionExtensionDO, null); // TODO 暂时传入 extra = null
|
||||
invokeResult.checkError();
|
||||
|
||||
// TODO 轮询三方接口,是否已经支付的任务
|
||||
// 返回成功
|
||||
return new PayTransactionSubmitRespDTO().setId(payTransactionExtensionDO.getId()).setInvokeResponse(invokeResult.getData());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayTransactionRespDTO getPayTransaction(PayTransactionGetReqDTO getReqDTO) {
|
||||
return PayTransactionConvert.INSTANCE.convert(payTransactionMapper.selectByAppIdAndOrderId(
|
||||
getReqDTO.getAppId(), getReqDTO.getOrderId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Boolean updateTransactionPaySuccess(Integer payChannel, String params) {
|
||||
// TODO 芋艿,记录回调日志
|
||||
// 解析传入的参数,成 ThirdPayTransactionSuccessRespDTO 对象
|
||||
AbstractThirdPayClient thirdPayClient = ThirdPayClientFactory.getThirdPayClient(payChannel);
|
||||
CommonResult<ThirdPayTransactionSuccessRespDTO> paySuccessResult = thirdPayClient.parseTransactionSuccessParams(params);
|
||||
paySuccessResult.checkError();
|
||||
|
||||
// TODO 芋艿,先最严格的校验。即使调用方重复调用,实际哪个订单已经被重复回调的支付,也返回 false 。也没问题,因为实际已经回调成功了。
|
||||
// 1.1 查询 PayTransactionExtensionDO
|
||||
PayTransactionExtensionDO extension = payTransactionExtensionMapper.selectByTransactionCode(paySuccessResult.getData().getTransactionCode());
|
||||
if (extension == null) {
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_EXTENSION_NOT_FOUND);
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITING.getStatus().equals(extension.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 1.2 更新 PayTransactionExtensionDO
|
||||
PayTransactionExtensionDO updatePayTransactionExtension = new PayTransactionExtensionDO()
|
||||
.setId(extension.getId())
|
||||
.setStatus(PayTransactionStatusEnum.SUCCESS.getStatus())
|
||||
.setExtensionData(params);
|
||||
int updateCounts = payTransactionExtensionMapper.update(updatePayTransactionExtension, PayTransactionStatusEnum.WAITING.getStatus());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_EXTENSION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updateTransactionPaySuccess][PayTransactionExtensionDO({}) 更新为已支付]", extension.getId());
|
||||
|
||||
// 2.1 判断 PayTransactionDO 是否处于待支付
|
||||
PayTransactionDO transaction = payTransactionMapper.selectById(extension.getTransactionId());
|
||||
if (transaction == null) {
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITING.getStatus().equals(transaction.getStatus())) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
// 2.2 更新 PayTransactionDO
|
||||
PayTransactionDO updatePayTransaction = new PayTransactionDO()
|
||||
.setId(transaction.getId())
|
||||
.setStatus(PayTransactionStatusEnum.SUCCESS.getStatus())
|
||||
.setExtensionId(extension.getId())
|
||||
.setPayChannel(payChannel)
|
||||
.setPaymentTime(paySuccessResult.getData().getPaymentTime())
|
||||
.setNotifyTime(new Date())
|
||||
.setTradeNo(paySuccessResult.getData().getTradeNo());
|
||||
updateCounts = payTransactionMapper.update(updatePayTransaction, PayTransactionStatusEnum.WAITING.getStatus());
|
||||
if (updateCounts == 0) { // 校验状态,必须是待支付
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.info("[updateTransactionPaySuccess][PayTransactionDO({}) 更新为已支付]", transaction.getId());
|
||||
|
||||
// 3 新增 PayNotifyTaskDO 注释原因,参见 PayRefundSuccessConsumer 类。
|
||||
payNotifyService.addPayTransactionNotifyTask(transaction, extension);
|
||||
// 返回结果
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<PayTransactionRespDTO> pagePayTransaction(PayTransactionPageReqDTO pageReqDTO) {
|
||||
IPage<PayTransactionDO> payTransactionDOPage = payTransactionMapper.selectPage(pageReqDTO);
|
||||
return PayTransactionConvert.INSTANCE.convertPage(payTransactionDOPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateTransactionPriceTotalIncr(Integer payTransactionId, Integer incr) {
|
||||
return payTransactionMapper.updatePriceTotalIncr(payTransactionId, incr) > 0;
|
||||
}
|
||||
|
||||
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) // 随机。为什么是这个范围,因为偷懒
|
||||
;
|
||||
}
|
||||
|
||||
// CommonResult cancelTransaction(); // TODO 1. params 2. result
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package cn.iocoder.mall.systemservice.controller;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import cn.iocoder.common.framework.vo.PageResult;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.*;
|
||||
import cn.iocoder.mall.payservice.service.transaction.PayTransactionService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static cn.iocoder.common.framework.vo.CommonResult.success;
|
||||
|
||||
/**
|
||||
* Title:
|
||||
* Description:
|
||||
*
|
||||
* @author zhuyang
|
||||
* @version 1.0 2021/10/9
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/pay/transaction")
|
||||
public class PayTransactionController {
|
||||
@Autowired
|
||||
private PayTransactionService payTransactionService;
|
||||
|
||||
/**
|
||||
* 创建支付交易单
|
||||
*
|
||||
* @param createReqDTO 创建信息
|
||||
* @return 支付交易单号
|
||||
*/
|
||||
@PostMapping("createPayTransaction")
|
||||
CommonResult<Integer> createPayTransaction(@RequestBody PayTransactionCreateReqDTO createReqDTO){
|
||||
return success(payTransactionService.createPayTransaction(createReqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交支付交易单
|
||||
*
|
||||
* @param submitReqDTO 提交信息
|
||||
* @return 提交响应,包含三方支付的响应
|
||||
*/
|
||||
@PostMapping("submitPayTransaction")
|
||||
CommonResult<PayTransactionSubmitRespDTO> submitPayTransaction(@RequestBody PayTransactionSubmitReqDTO submitReqDTO){
|
||||
return success(payTransactionService.submitPayTransaction(submitReqDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得当支付交易单
|
||||
*
|
||||
* @param getReqDTO 获得条件
|
||||
* @return 支付交易单
|
||||
*/
|
||||
@PostMapping("getPayTransaction")
|
||||
CommonResult<PayTransactionRespDTO> getPayTransaction(@RequestBody PayTransactionGetReqDTO getReqDTO){
|
||||
return success(payTransactionService.getPayTransaction(getReqDTO));}
|
||||
|
||||
/**
|
||||
* 更新交易支付成功
|
||||
*
|
||||
* @param successReqDTO 支付成功信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
@PostMapping("updatePayTransactionSuccess")
|
||||
CommonResult<Boolean> updatePayTransactionSuccess(@RequestBody PayTransactionSuccessReqDTO successReqDTO){
|
||||
return success(payTransactionService.updateTransactionPaySuccess(successReqDTO.getPayChannel(),
|
||||
successReqDTO.getParams()));}
|
||||
|
||||
/**
|
||||
* 获得交易支付单分页
|
||||
*
|
||||
* @param pageReqDTO 分页条件
|
||||
* @return 交易支付单分页
|
||||
*/
|
||||
@PostMapping("pagePayTransaction")
|
||||
CommonResult<PageResult<PayTransactionRespDTO>> pagePayTransaction(@RequestBody PayTransactionPageReqDTO pageReqDTO){
|
||||
return success(payTransactionService.pagePayTransaction(pageReqDTO));}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/mall_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: zhuyang
|
||||
# Spring Cloud 配置项
|
||||
cloud:
|
||||
nacos:
|
||||
# Spring Cloud Nacos Discovery 配置项
|
||||
discovery:
|
||||
server-addr: localhost:8848 # Nacos 服务器地址
|
||||
namespace: dev # Nacos 命名空间
|
||||
|
||||
# Dubbo 配置项
|
||||
dubbo:
|
||||
# Dubbo 注册中心
|
||||
registry:
|
||||
# address: spring-cloud://localhost:8848 # 指定 Dubbo 服务注册中心的地址
|
||||
address: nacos://localhost:8848?namespace=dev # 指定 Dubbo 服务注册中心的地址
|
||||
|
||||
# XXL-Job 配置项
|
||||
xxl:
|
||||
job:
|
||||
admin:
|
||||
addresses: http://127.0.0.1:9099/
|
||||
executor:
|
||||
appname: ${spring.application.name}
|
||||
logpath: /data/applogs/xxl-job/
|
||||
accessToken:
|
||||
@@ -0,0 +1,40 @@
|
||||
spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
url: jdbc:mysql://localhost:3306/mall_pay?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=CTT
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
username: root
|
||||
password: zhuyang
|
||||
# Spring Cloud 配置项
|
||||
cloud:
|
||||
nacos:
|
||||
# Spring Cloud Nacos Discovery 配置项
|
||||
discovery:
|
||||
server-addr: localhost:8848 # Nacos 服务器地址
|
||||
namespace: dev # Nacos 命名空间
|
||||
|
||||
# Dubbo 配置项
|
||||
dubbo:
|
||||
# Dubbo 注册中心
|
||||
registry:
|
||||
# address: spring-cloud://localhost:8848 # 指定 Dubbo 服务注册中心的地址
|
||||
address: nacos://localhost:8848?namespace=dev # 指定 Dubbo 服务注册中心的地址
|
||||
# Dubbo 服务提供者的配置
|
||||
provider:
|
||||
tag: ${DUBBO_TAG} # Dubbo 路由分组
|
||||
|
||||
# XXL-Job 配置项
|
||||
xxl:
|
||||
job:
|
||||
enabled: false # 本地开发时,关闭 XXL-Job
|
||||
admin:
|
||||
addresses: http://localhost:9099
|
||||
executor:
|
||||
appname: ${spring.application.name}
|
||||
accessToken:
|
||||
|
||||
|
||||
# MyBatis Plus 配置
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 本地开发环境下,多打印 SQL 到控制台
|
||||
@@ -0,0 +1,64 @@
|
||||
spring:
|
||||
# Application 的配置项
|
||||
application:
|
||||
name: pay-service
|
||||
# Profile 的配置项
|
||||
profiles:
|
||||
active: local
|
||||
|
||||
# MyBatis Plus 配置项
|
||||
mybatis-plus:
|
||||
configuration:
|
||||
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: auto
|
||||
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
|
||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||
mapper-locations: classpath*:mapper/*.xml
|
||||
type-aliases-package: cn.iocoder.mall.payservice.dal.mysql.dataobject
|
||||
|
||||
# Dubbo 配置项
|
||||
dubbo:
|
||||
# Spring Cloud Alibaba Dubbo 专属配置
|
||||
cloud:
|
||||
subscribed-services: '' # 设置订阅的应用列表,默认为 * 订阅所有应用
|
||||
# Dubbo 提供者的协议
|
||||
protocol:
|
||||
name: dubbo
|
||||
port: -1
|
||||
# Dubbo 提供服务的扫描基础包
|
||||
scan:
|
||||
base-packages: cn.iocoder.mall.payservice.rpc
|
||||
# Dubbo 服务提供者的配置
|
||||
provider:
|
||||
filter: -exception
|
||||
validation: true # 开启 Provider 参数校验
|
||||
version: 1.0.0 # 服务的版本号
|
||||
# Dubbo 服务消费者的配置
|
||||
consumer:
|
||||
ErrorCodeRpc:
|
||||
version: 1.0.0
|
||||
ProductSkuRpc:
|
||||
version: 1.0.0
|
||||
ProductSpuRpc:
|
||||
version: 1.0.0
|
||||
|
||||
# RocketMQ 配置项
|
||||
rocketmq:
|
||||
name-server: localhost:9876
|
||||
producer:
|
||||
group: ${spring.application.name}-producer-group
|
||||
|
||||
# Actuator 监控配置项
|
||||
management:
|
||||
server.port: 38089 # 独立端口,避免被暴露出去
|
||||
endpoints.web.exposure.include: '*' # 暴露所有监控端点
|
||||
server.port: ${management.server.port} # 设置使用 Actuator 的服务器端口,因为 RPC 服务不需要 Web 端口
|
||||
|
||||
# Mall 配置项
|
||||
mall:
|
||||
# 错误码配置项对应 ErrorCodeProperties 配置类
|
||||
error-code:
|
||||
group: ${spring.application.name}
|
||||
constants-class: cn.iocoder.mall.payservice.enums.PayErrorCodeConstants
|
||||
@@ -0,0 +1,12 @@
|
||||
<?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.payservice.dal.mysql.mapper.transaction.PayTransactionMapper">
|
||||
|
||||
<update id="updatePriceTotalIncr">
|
||||
UPDATE pay_transaction
|
||||
SET refund_total = refund_total + ${refundTotalIncr}
|
||||
WHERE id = #{id}
|
||||
AND price >= refund_total + ${refundTotalIncr}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
29
归档/pay-service-project/pay-service-integration-test/pom.xml
Normal file
29
归档/pay-service-project/pay-service-integration-test/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>pay-service-project</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>pay-service-integration-test</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>pay-service-app</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test 相关 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,49 @@
|
||||
package cn.iocoder.mall.payservice.common.dubbo;
|
||||
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ReferenceConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.rpc.service.GenericService;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class DubboGenericInvokerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
ApplicationConfig application = new ApplicationConfig();
|
||||
application.setName("api-generic-consumer");
|
||||
|
||||
RegistryConfig registry = new RegistryConfig();
|
||||
registry.setAddress("nacos://localhost:8848?namespace=dev");
|
||||
|
||||
application.setRegistry(registry);
|
||||
|
||||
ReferenceConfig<GenericService> reference = new ReferenceConfig<>();
|
||||
// 弱类型接口名
|
||||
reference.setInterface("cn.iocoder.mall.tradeservice.rpc.order.TradeOrderRpc");
|
||||
reference.setVersion("1.0.0");
|
||||
// 声明为泛化接口
|
||||
reference.setGeneric(true);
|
||||
|
||||
reference.setApplication(application);
|
||||
|
||||
// 用com.alibaba.dubbo.rpc.service.GenericService可以替代所有接口引用
|
||||
GenericService genericService = reference.get();
|
||||
|
||||
Object result = genericService.$invoke("updateTradeOrderPaySuccess",
|
||||
new String[]{String.class.getName(), Integer.class.getName()},
|
||||
new Object[]{"1", 100});
|
||||
CommonResult<Boolean> commonResult = parseCommonResult((Map<String, Object>) result);
|
||||
System.out.println(result);
|
||||
}
|
||||
|
||||
private static CommonResult<Boolean> parseCommonResult(Map<String, Object> dubboResult) {
|
||||
CommonResult<Boolean> commonResult = new CommonResult<>();
|
||||
commonResult.setCode((Integer) dubboResult.get("code"));
|
||||
commonResult.setMessage((String) dubboResult.get("message"));
|
||||
commonResult.setData((Boolean) dubboResult.get("data"));
|
||||
return commonResult;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.common;
|
||||
@@ -0,0 +1,27 @@
|
||||
package cn.iocoder.mall.payservice.service.transaction.impl;
|
||||
|
||||
import cn.iocoder.mall.payservice.enums.PayChannelEnum;
|
||||
import cn.iocoder.mall.payservice.rpc.transaction.dto.PayTransactionSubmitReqDTO;
|
||||
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.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
public class PayTransactionServiceImplTest {
|
||||
|
||||
@Autowired
|
||||
private PayTransactionServiceImpl payTransactionService;
|
||||
|
||||
@Test
|
||||
public void testSubmitPayTransaction() {
|
||||
payTransactionService.submitPayTransaction(new PayTransactionSubmitReqDTO()
|
||||
.setAppId("POd4RC6a")
|
||||
.setCreateIp("127.0.0.1")
|
||||
.setOrderId("239")
|
||||
.setPayChannel(PayChannelEnum.PINGXX.getId()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package cn.iocoder.mall.payservice.service.transaction;
|
||||
48
归档/pay-service-project/pom.xml
Normal file
48
归档/pay-service-project/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>onemall</artifactId>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>pay-service-project</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<description>支付相关服务</description>
|
||||
<modules>
|
||||
<module>pay-service-api</module>
|
||||
<module>pay-service-app</module>
|
||||
<module>pay-service-integration-test</module>
|
||||
</modules>
|
||||
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<!-- onemall 基础 bom 文件 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-dependencies</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 自身项目 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>pay-service-api</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
</project>
|
||||
Reference in New Issue
Block a user