From 8f9244e434a81d26183c22d31bf9709a2c1797b2 Mon Sep 17 00:00:00 2001 From: qiube <18969599531@163.com> Date: Thu, 18 Dec 2025 18:01:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(pay):=20=E6=96=B0=E5=A2=9E=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E8=AE=A2=E5=8D=95=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建支付订单实体类PaymentOrder,包含订单基本信息和状态字段 - 实现PaymentOrderMapper接口,提供根据商户订单号和交易流水号查询方法 - 定义PaymentOrderService接口,包含创建订单、查询订单和更新订单状态方法 - 实现PaymentOrderServiceImpl类,完成订单创建、查询和状态更新业务逻辑 - 集成PingPong支付服务,支持调用其API创建支付订单 - 添加订单重复性校验,防止相同商户订单号重复创建 - 实现订单状态管理和异步通知处理机制 - 记录支付操作日志和异常情况处理 --- .../com/mtkj/mtpay/entity/PaymentOrder.java | 130 ++++++++++++++++++ .../mtkj/mtpay/mapper/PaymentOrderMapper.java | 37 +++++ .../mtpay/service/PaymentOrderService.java | 46 +++++++ .../service/impl/PaymentOrderServiceImpl.java | 119 ++++++++++++++++ 4 files changed, 332 insertions(+) create mode 100644 mt-pay/src/main/java/com/mtkj/mtpay/entity/PaymentOrder.java create mode 100644 mt-pay/src/main/java/com/mtkj/mtpay/mapper/PaymentOrderMapper.java create mode 100644 mt-pay/src/main/java/com/mtkj/mtpay/service/PaymentOrderService.java create mode 100644 mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/entity/PaymentOrder.java b/mt-pay/src/main/java/com/mtkj/mtpay/entity/PaymentOrder.java new file mode 100644 index 0000000..56b397a --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/entity/PaymentOrder.java @@ -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; +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/mapper/PaymentOrderMapper.java b/mt-pay/src/main/java/com/mtkj/mtpay/mapper/PaymentOrderMapper.java new file mode 100644 index 0000000..2793044 --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/mapper/PaymentOrderMapper.java @@ -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 { + + /** + * 根据商户订单号查询 + */ + default Optional findByMerchantTransactionId(String merchantTransactionId) { + PaymentOrder order = selectOne( + com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.lambdaQuery() + .eq(PaymentOrder::getMerchantTransactionId, merchantTransactionId) + ); + return Optional.ofNullable(order); + } + + /** + * 根据PingPong交易流水号查询 + */ + default Optional findByTransactionId(String transactionId) { + PaymentOrder order = selectOne( + com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper.lambdaQuery() + .eq(PaymentOrder::getTransactionId, transactionId) + ); + return Optional.ofNullable(order); + } +} + diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/service/PaymentOrderService.java b/mt-pay/src/main/java/com/mtkj/mtpay/service/PaymentOrderService.java new file mode 100644 index 0000000..bb0991a --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/service/PaymentOrderService.java @@ -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 findByMerchantTransactionId(String merchantTransactionId); + + /** + * 根据PingPong交易流水号查询订单 + * + * @param transactionId PingPong交易流水号 + * @return 支付订单 + */ + Optional findByTransactionId(String transactionId); + + /** + * 更新订单状态 + * + * @param merchantTransactionId 商户订单号 + * @param status 订单状态 + * @param transactionId PingPong交易流水号 + * @return 支付订单 + */ + PaymentOrder updateOrderStatus(String merchantTransactionId, String status, String transactionId); +} diff --git a/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java b/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java new file mode 100644 index 0000000..0c3a953 --- /dev/null +++ b/mt-pay/src/main/java/com/mtkj/mtpay/service/impl/PaymentOrderServiceImpl.java @@ -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 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 findByMerchantTransactionId(String merchantTransactionId) { + return paymentOrderMapper.findByMerchantTransactionId(merchantTransactionId); + } + + @Override + public Optional findByTransactionId(String transactionId) { + return paymentOrderMapper.findByTransactionId(transactionId); + } + + @Override + @Transactional + public PaymentOrder updateOrderStatus(String merchantTransactionId, String status, String transactionId) { + Optional 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; + } +} +