feat(pay): 新增支付订单模块
- 创建支付订单实体类PaymentOrder,包含订单基本信息和状态字段 - 实现PaymentOrderMapper接口,提供根据商户订单号和交易流水号查询方法 - 定义PaymentOrderService接口,包含创建订单、查询订单和更新订单状态方法 - 实现PaymentOrderServiceImpl类,完成订单创建、查询和状态更新业务逻辑 - 集成PingPong支付服务,支持调用其API创建支付订单 - 添加订单重复性校验,防止相同商户订单号重复创建 - 实现订单状态管理和异步通知处理机制 - 记录支付操作日志和异常情况处理
This commit is contained in:
130
mt-pay/src/main/java/com/mtkj/mtpay/entity/PaymentOrder.java
Normal file
130
mt-pay/src/main/java/com/mtkj/mtpay/entity/PaymentOrder.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package com.mtkj.mtpay.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 支付订单实体
|
||||
*/
|
||||
@TableName(value = "payment_order", resultMap = "BaseResultMap")
|
||||
@Data
|
||||
public class PaymentOrder {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 商户订单号(全局唯一)
|
||||
*/
|
||||
@TableField(value = "merchant_transaction_id", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String merchantTransactionId;
|
||||
|
||||
/**
|
||||
* PingPong交易流水号
|
||||
*/
|
||||
@TableField(value = "transaction_id", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String transactionId;
|
||||
|
||||
/**
|
||||
* PingPong商户号
|
||||
*/
|
||||
@TableField(value = "client_id", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* PingPong商户店铺编号
|
||||
*/
|
||||
@TableField(value = "acc_id", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String accId;
|
||||
|
||||
/**
|
||||
* 交易金额
|
||||
*/
|
||||
@TableField(value = "amount", jdbcType = org.apache.ibatis.type.JdbcType.DECIMAL)
|
||||
private BigDecimal amount;
|
||||
|
||||
/**
|
||||
* 交易币种(ISO 4217三位币种)
|
||||
*/
|
||||
@TableField(value = "currency", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String currency;
|
||||
|
||||
/**
|
||||
* 交易类型:SALE-直接付款,AUTH-预授权
|
||||
*/
|
||||
@TableField(value = "payment_type", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String paymentType;
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
@TableField(value = "payment_brand", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String paymentBrand;
|
||||
|
||||
/**
|
||||
* 订单状态:PENDING-待支付,SUCCESS-支付成功,FAILED-支付失败,REVIEW-审核中,CANCELLED-已取消
|
||||
*/
|
||||
@TableField(value = "status", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String status = "PENDING";
|
||||
|
||||
/**
|
||||
* PingPong返回的token
|
||||
*/
|
||||
@TableField(value = "token", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 支付收银台地址
|
||||
*/
|
||||
@TableField(value = "payment_url", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String paymentUrl;
|
||||
|
||||
/**
|
||||
* 商户用户ID
|
||||
*/
|
||||
@TableField(value = "merchant_user_id", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String merchantUserId;
|
||||
|
||||
/**
|
||||
* 结果重定向URL
|
||||
*/
|
||||
@TableField(value = "shopper_result_url", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String shopperResultUrl;
|
||||
|
||||
/**
|
||||
* 取消重定向URL
|
||||
*/
|
||||
@TableField(value = "shopper_cancel_url", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String shopperCancelUrl;
|
||||
|
||||
/**
|
||||
* 异步通知地址
|
||||
*/
|
||||
@TableField(value = "notification_url", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String notificationUrl;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@TableField(value = "remark", jdbcType = org.apache.ibatis.type.JdbcType.VARCHAR)
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.mtkj.mtpay.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.mtkj.mtpay.entity.PaymentOrder;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 支付订单Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PaymentOrderMapper extends BaseMapper<PaymentOrder> {
|
||||
|
||||
/**
|
||||
* 根据商户订单号查询
|
||||
*/
|
||||
default Optional<PaymentOrder> findByMerchantTransactionId(String merchantTransactionId) {
|
||||
PaymentOrder order = selectOne(
|
||||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.<PaymentOrder>lambdaQuery()
|
||||
.eq(PaymentOrder::getMerchantTransactionId, merchantTransactionId)
|
||||
);
|
||||
return Optional.ofNullable(order);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据PingPong交易流水号查询
|
||||
*/
|
||||
default Optional<PaymentOrder> findByTransactionId(String transactionId) {
|
||||
PaymentOrder order = selectOne(
|
||||
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.<PaymentOrder>lambdaQuery()
|
||||
.eq(PaymentOrder::getTransactionId, transactionId)
|
||||
);
|
||||
return Optional.ofNullable(order);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.mtkj.mtpay.service;
|
||||
|
||||
import com.mtkj.mtpay.dto.request.CheckoutRequestDTO;
|
||||
import com.mtkj.mtpay.entity.PaymentOrder;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 支付订单服务接口
|
||||
*/
|
||||
public interface PaymentOrderService {
|
||||
|
||||
/**
|
||||
* 创建支付订单
|
||||
*
|
||||
* @param request 支付请求
|
||||
* @return 支付订单
|
||||
*/
|
||||
PaymentOrder createPaymentOrder(CheckoutRequestDTO request);
|
||||
|
||||
/**
|
||||
* 根据商户订单号查询订单
|
||||
*
|
||||
* @param merchantTransactionId 商户订单号
|
||||
* @return 支付订单
|
||||
*/
|
||||
Optional<PaymentOrder> findByMerchantTransactionId(String merchantTransactionId);
|
||||
|
||||
/**
|
||||
* 根据PingPong交易流水号查询订单
|
||||
*
|
||||
* @param transactionId PingPong交易流水号
|
||||
* @return 支付订单
|
||||
*/
|
||||
Optional<PaymentOrder> findByTransactionId(String transactionId);
|
||||
|
||||
/**
|
||||
* 更新订单状态
|
||||
*
|
||||
* @param merchantTransactionId 商户订单号
|
||||
* @param status 订单状态
|
||||
* @param transactionId PingPong交易流水号
|
||||
* @return 支付订单
|
||||
*/
|
||||
PaymentOrder updateOrderStatus(String merchantTransactionId, String status, String transactionId);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.mtkj.mtpay.service.impl;
|
||||
|
||||
import com.mtkj.mtpay.dto.request.CheckoutRequestDTO;
|
||||
import com.mtkj.mtpay.dto.response.CheckoutResponseDTO;
|
||||
import com.mtkj.mtpay.entity.PaymentOrder;
|
||||
import com.mtkj.mtpay.entity.PaymentRecord;
|
||||
import com.mtkj.mtpay.mapper.PaymentOrderMapper;
|
||||
import com.mtkj.mtpay.mapper.PaymentRecordMapper;
|
||||
import com.mtkj.mtpay.service.PaymentOrderService;
|
||||
import com.mtkj.mtpay.service.PingPongPayService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 支付订单服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class PaymentOrderServiceImpl implements PaymentOrderService {
|
||||
|
||||
private final PaymentOrderMapper paymentOrderMapper;
|
||||
private final PaymentRecordMapper paymentRecordMapper;
|
||||
private final PingPongPayService pingPongPayService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public PaymentOrder createPaymentOrder(CheckoutRequestDTO request) {
|
||||
log.info("创建支付订单,商户订单号: {}", request.getMerchantTransactionId());
|
||||
|
||||
// 检查订单号是否已存在
|
||||
Optional<PaymentOrder> existingOrder = paymentOrderMapper.findByMerchantTransactionId(
|
||||
request.getMerchantTransactionId()
|
||||
);
|
||||
if (existingOrder.isPresent()) {
|
||||
throw new RuntimeException("商户订单号已存在: " + request.getMerchantTransactionId());
|
||||
}
|
||||
|
||||
// 创建订单实体
|
||||
PaymentOrder order = new PaymentOrder();
|
||||
order.setMerchantTransactionId(request.getMerchantTransactionId());
|
||||
order.setAmount(new BigDecimal(request.getAmount()));
|
||||
order.setCurrency(request.getCurrency());
|
||||
order.setPaymentType(request.getPaymentType());
|
||||
order.setPaymentBrand(request.getPaymentBrand());
|
||||
order.setMerchantUserId(request.getMerchantUserId());
|
||||
order.setShopperResultUrl(request.getShopperResultUrl());
|
||||
order.setShopperCancelUrl(request.getShopperCancelUrl());
|
||||
order.setNotificationUrl(request.getNotificationUrl());
|
||||
order.setRemark(request.getRemark());
|
||||
order.setStatus("PENDING");
|
||||
|
||||
// 调用PingPong API创建支付
|
||||
CheckoutResponseDTO response = pingPongPayService.checkout(request);
|
||||
|
||||
// 更新订单信息
|
||||
if ("001000".equals(response.getCode())) {
|
||||
// transactionId在回调中返回,这里先不设置
|
||||
order.setClientId(response.getClientId());
|
||||
order.setAccId(response.getAccId());
|
||||
order.setToken(response.getToken());
|
||||
order.setPaymentUrl(response.getPaymentUrl());
|
||||
} else {
|
||||
order.setStatus("FAILED");
|
||||
log.warn("PingPong返回错误,code: {}, description: {}", response.getCode(), response.getDescription());
|
||||
}
|
||||
|
||||
// 保存订单
|
||||
paymentOrderMapper.insert(order);
|
||||
|
||||
// 记录支付记录
|
||||
PaymentRecord record = new PaymentRecord();
|
||||
record.setMerchantTransactionId(order.getMerchantTransactionId());
|
||||
record.setTransactionId(order.getTransactionId());
|
||||
record.setRecordType("CHECKOUT");
|
||||
record.setCode(response.getCode());
|
||||
record.setDescription(response.getDescription());
|
||||
record.setStatus(order.getStatus());
|
||||
paymentRecordMapper.insert(record);
|
||||
|
||||
log.info("支付订单创建成功,订单ID: {}", order.getId());
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PaymentOrder> findByMerchantTransactionId(String merchantTransactionId) {
|
||||
return paymentOrderMapper.findByMerchantTransactionId(merchantTransactionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<PaymentOrder> findByTransactionId(String transactionId) {
|
||||
return paymentOrderMapper.findByTransactionId(transactionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public PaymentOrder updateOrderStatus(String merchantTransactionId, String status, String transactionId) {
|
||||
Optional<PaymentOrder> orderOpt = paymentOrderMapper.findByMerchantTransactionId(merchantTransactionId);
|
||||
if (orderOpt.isEmpty()) {
|
||||
throw new RuntimeException("订单不存在: " + merchantTransactionId);
|
||||
}
|
||||
|
||||
PaymentOrder order = orderOpt.get();
|
||||
order.setStatus(status);
|
||||
if (transactionId != null && !transactionId.isEmpty()) {
|
||||
order.setTransactionId(transactionId);
|
||||
}
|
||||
|
||||
paymentOrderMapper.updateById(order);
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user