完成 pingxx 支付回调接口
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package cn.iocoder.mall.payservice.enums.transaction;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 支付交易状态枚举
|
||||
*/
|
||||
@Getter
|
||||
public enum PayTransactionStatusEnum {
|
||||
|
||||
WAITING(1, "等待支付"),
|
||||
@@ -13,33 +16,15 @@ public enum PayTransactionStatusEnum {
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private Integer value;
|
||||
private final Integer status;
|
||||
/**
|
||||
* 名字
|
||||
*/
|
||||
private String name;
|
||||
private final String name;
|
||||
|
||||
PayTransactionStatusEnum(Integer value, String name) {
|
||||
this.value = value;
|
||||
PayTransactionStatusEnum(Integer status, String name) {
|
||||
this.status = status;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public PayTransactionStatusEnum setValue(Integer value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public PayTransactionStatusEnum setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,4 +32,12 @@ public interface PayTransactionRpc {
|
||||
*/
|
||||
CommonResult<PayTransactionRespDTO> getPayTransaction(PayTransactionGetReqDTO getReqDTO);
|
||||
|
||||
/**
|
||||
* 更新交易支付成功
|
||||
*
|
||||
* @param successReqDTO 支付成功信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
CommonResult<Boolean> updatePayTransactionSuccess(PayTransactionSuccessReqDTO successReqDTO);
|
||||
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ 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 {
|
||||
public class PayTransactionGetReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.iocoder.mall.payservice.rpc.transaction.dto;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -10,7 +11,7 @@ import java.util.Date;
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PayTransactionRespDTO {
|
||||
public class PayTransactionRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 编号,自增
|
||||
|
||||
@@ -7,13 +7,14 @@ 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 {
|
||||
public class PayTransactionSubmitReqDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 应用编号
|
||||
|
||||
@@ -3,12 +3,14 @@ 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 {
|
||||
public class PayTransactionSubmitRespDTO implements Serializable {
|
||||
|
||||
/**
|
||||
* 支付交易拓展单编号
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -31,6 +31,12 @@
|
||||
<artifactId>spring-boot-starter-web</artifactId> <!-- 需要开启 Web 容器,因为 Actuator 需要使用到 -->
|
||||
</dependency>
|
||||
|
||||
<!-- MQ 相关 -->
|
||||
<dependency>
|
||||
<groupId>cn.iocoder.mall</groupId>
|
||||
<artifactId>mall-spring-boot-starter-rocketmq</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Registry 和 Config 相关 -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface PayNotifyConvert {
|
||||
|
||||
PayNotifyConvert INSTANCE = Mappers.getMapper(PayNotifyConvert.class);
|
||||
|
||||
PayTransactionSuccessMessage convertTransaction(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
||||
|
||||
PayRefundSuccessMessage convertRefund(PayNotifyTaskDO payTransactionNotifyTaskDO);
|
||||
|
||||
}
|
||||
@@ -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,148 @@
|
||||
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 cn.iocoder.mall.payservice.service.transaction.PayTransactionService;
|
||||
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;
|
||||
/**
|
||||
* 下一次通知时间
|
||||
*/
|
||||
private Date nextNotifyTime;
|
||||
/**
|
||||
* 最后一次执行时间
|
||||
*
|
||||
* 这个字段,需要结合 {@link #nextNotifyTime} 一起使用。
|
||||
*
|
||||
* 1. 初始时,{@link PayTransactionService#updateTransactionPaySuccess(Integer, String)}
|
||||
* nextNotifyTime 为当前时间 + 15 秒
|
||||
* lastExecuteTime 为空
|
||||
* 并发送给 MQ ,执行执行
|
||||
*
|
||||
* 2. MQ 消费时,更新 lastExecuteTime 为当时时间
|
||||
*
|
||||
* 3. 定时任务,扫描 nextNotifyTime < lastExecuteTime 的任务
|
||||
* nextNotifyTime 为当前时间 + N 秒。具体的 N ,由第几次通知决定
|
||||
* lastExecuteTime 为当前时间
|
||||
*/
|
||||
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,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,41 @@
|
||||
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. lastExecuteTime > nextNotifyTime
|
||||
*
|
||||
* @return PayTransactionNotifyTaskDO 数组
|
||||
*/
|
||||
default List<PayNotifyTaskDO> selectListByNotify() {
|
||||
return selectList(new QueryWrapper<PayNotifyTaskDO>()
|
||||
.in("status", PayNotifyStatusEnum.WAITING.getName(), PayNotifyStatusEnum.REQUEST_SUCCESS.getName(),
|
||||
PayNotifyStatusEnum.REQUEST_FAILURE.getName())
|
||||
.le("next_notify_time", "NOW()")
|
||||
.gt("last_execute_time", "next_notify_time"));
|
||||
}
|
||||
|
||||
//
|
||||
// <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>
|
||||
|
||||
}
|
||||
@@ -13,6 +13,11 @@ public interface PayTransactionMapper extends BaseMapper<PayTransactionDO> {
|
||||
// new QueryWrapperX<PayTransactionDO>());
|
||||
// }
|
||||
|
||||
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));
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
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, Integer refundId, Integer transactionId, String orderId) {
|
||||
message.setRefundId(refundId).setTransactionId(transactionId).setOrderId(orderId);
|
||||
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, Integer transactionId, String orderId) {
|
||||
message.setTransactionId(transactionId).setOrderId(orderId);
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -29,4 +29,10 @@ public class PayTransactionRpcImpl implements PayTransactionRpc {
|
||||
return success(payTransactionService.getPayTransaction(getReqDTO));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommonResult<Boolean> updatePayTransactionSuccess(PayTransactionSuccessReqDTO successReqDTO) {
|
||||
return success(payTransactionService.updateTransactionPaySuccess(successReqDTO.getPayChannel(),
|
||||
successReqDTO.getParams()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.iocoder.mall.payservice.service.notify;
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
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 消息
|
||||
payMQProducer.sendPayRefundNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertRefund(payNotifyTaskDO),
|
||||
refund.getId(), refund.getTransactionId(), refund.getOrderId());
|
||||
}
|
||||
|
||||
@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 消息
|
||||
payMQProducer.sendPayTransactionNotifyTaskMessage(PayNotifyConvert.INSTANCE.convertTransaction(payNotifyTaskDO),
|
||||
transaction.getId(), transaction.getOrderId());
|
||||
}
|
||||
|
||||
private PayNotifyTaskDO createBasePayNotifyTaskDO(String appId, String notifyUrl) {
|
||||
return new PayNotifyTaskDO()
|
||||
.setAppId(appId)
|
||||
.setStatus(PayNotifyStatusEnum.WAITING.getStatus())
|
||||
.setNotifyTimes(0).setMaxNotifyTimes(PayNotifyTaskDO.NOTIFY_FREQUENCY.length + 1)
|
||||
.setNextNotifyTime(DateUtil.addDate(Calendar.SECOND, PayNotifyTaskDO.NOTIFY_FREQUENCY[0]))
|
||||
.setNotifyUrl(notifyUrl);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,4 +31,16 @@ public interface PayTransactionService {
|
||||
*/
|
||||
PayTransactionRespDTO getPayTransaction(PayTransactionGetReqDTO getReqDTO);
|
||||
|
||||
/**
|
||||
* 更新交易支付成功
|
||||
*
|
||||
* 该接口用于不同支付平台,支付成功后,回调该接口
|
||||
*
|
||||
* @param payChannel 支付渠道
|
||||
* @param params 回调参数。
|
||||
* 因为不同平台,能够提供的参数不同,所以使用 String 类型统一接收,然后在使用不同的 AbstractThirdPayClient 进行处理。
|
||||
* @return 是否支付成功
|
||||
*/
|
||||
Boolean updateTransactionPaySuccess(Integer payChannel, String params);
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import cn.iocoder.common.framework.util.MathUtil;
|
||||
import cn.iocoder.common.framework.vo.CommonResult;
|
||||
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;
|
||||
@@ -15,16 +16,17 @@ 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 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.PAY_TRANSACTION_NOT_FOUND;
|
||||
import static cn.iocoder.mall.payservice.enums.PayErrorCodeConstants.PAY_TRANSACTION_STATUS_IS_NOT_WAITING;
|
||||
import static cn.iocoder.mall.payservice.enums.PayErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 支付交易单 Service 实现类
|
||||
@@ -41,6 +43,8 @@ public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
|
||||
@Autowired
|
||||
private PayAppService payAppService;
|
||||
@Autowired
|
||||
private PayNotifyService payNotifyService;
|
||||
|
||||
@Override
|
||||
public Integer createPayTransaction(PayTransactionCreateReqDTO createReqDTO) {
|
||||
@@ -58,7 +62,7 @@ public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
|
||||
// 创建支付交易单
|
||||
payTransaction = PayTransactionConvert.INSTANCE.convert(createReqDTO)
|
||||
.setStatus(PayTransactionStatusEnum.WAITING.getValue())
|
||||
.setStatus(PayTransactionStatusEnum.WAITING.getStatus())
|
||||
.setNotifyUrl(payAppRespDTO.getPayNotifyUrl());
|
||||
payTransactionMapper.insert(payTransaction);
|
||||
// 最终返回
|
||||
@@ -77,14 +81,14 @@ public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
if (payTransaction == null) { // 是否存在
|
||||
throw ServiceExceptionUtil.exception(PAY_TRANSACTION_NOT_FOUND);
|
||||
}
|
||||
if (!PayTransactionStatusEnum.WAITING.getValue().equals(payTransaction.getStatus())) { // 校验状态,必须是待支付
|
||||
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.getValue());
|
||||
.setStatus(PayTransactionStatusEnum.WAITING.getStatus());
|
||||
payTransactionExtensionMapper.insert(payTransactionExtensionDO);
|
||||
|
||||
// 调用三方接口
|
||||
@@ -103,6 +107,64 @@ public class PayTransactionServiceImpl implements PayTransactionService {
|
||||
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;
|
||||
}
|
||||
|
||||
private String generateTransactionCode() {
|
||||
// wx
|
||||
// 2014
|
||||
|
||||
Reference in New Issue
Block a user