feat(payment): 初始化支付模块核心代码
- 添加支付常量类PaymentConstants,定义支付状态、微信、支付宝、银联等相关常量 - 创建微信支付类型常量类WechatPayType,支持JSAPI、NATIVE、H5、APP支付方式 - 新增支付控制器PaymentController,提供创建支付、查询状态、退款等统一接口 - 实现支付回调控制器PaymentNotifyController,处理微信、支付宝、银联异步通知 - 添加支付请求数据传输对象PaymentRequest,支持多种支付方式参数校验 - 定义支付响应、状态更新请求等相关DTO类- 集成Swagger注解,完善接口文档说明- 添加参数校验和异常处理机制,确保支付流程安全可靠
This commit is contained in:
366
java/payment/service/WxPayNotifyService.java
Normal file
366
java/payment/service/WxPayNotifyService.java
Normal file
@@ -0,0 +1,366 @@
|
||||
package com.gxwebsoft.payment.service;
|
||||
|
||||
|
||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||
import com.gxwebsoft.payment.exception.PaymentException;
|
||||
import com.gxwebsoft.shop.entity.ShopOrder;
|
||||
import com.gxwebsoft.shop.service.ShopOrderService;
|
||||
import com.wechat.pay.java.core.Config;
|
||||
import com.wechat.pay.java.core.notification.NotificationConfig;
|
||||
import com.wechat.pay.java.core.notification.NotificationParser;
|
||||
import com.wechat.pay.java.core.notification.RequestParam;
|
||||
import com.wechat.pay.java.service.payments.model.Transaction;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 微信支付回调通知处理服务
|
||||
* 负责处理微信支付的异步通知回调
|
||||
*
|
||||
* @author 科技小王子
|
||||
* @since 2025-01-26
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WxPayNotifyService {
|
||||
|
||||
@Resource
|
||||
private WxPayConfigService wxPayConfigService;
|
||||
|
||||
@Resource
|
||||
private ShopOrderService shopOrderService;
|
||||
|
||||
/**
|
||||
* 处理微信支付回调通知
|
||||
*
|
||||
* @param headers 请求头
|
||||
* @param body 请求体
|
||||
* @param tenantId 租户ID
|
||||
* @return 处理结果响应
|
||||
*/
|
||||
public String handlePaymentNotify(Map<String, String> headers, String body, Integer tenantId) {
|
||||
log.info("{}, 租户ID: {}", PaymentConstants.LogMessage.NOTIFY_START, tenantId);
|
||||
|
||||
try {
|
||||
// 参数验证
|
||||
validateNotifyParams(headers, body, tenantId);
|
||||
|
||||
// 获取微信支付配置
|
||||
Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);
|
||||
|
||||
// 解析并验证回调数据
|
||||
Transaction transaction = parseAndVerifyNotification(headers, body, wxPayConfig);
|
||||
|
||||
// 处理支付结果
|
||||
processPaymentResult(transaction, tenantId);
|
||||
|
||||
log.info("{}, 租户ID: {}, 订单号: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_SUCCESS, tenantId, transaction.getOutTradeNo());
|
||||
|
||||
return "SUCCESS";
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("{}, 租户ID: {}, 错误: {}",
|
||||
PaymentConstants.LogMessage.NOTIFY_FAILED, tenantId, e.getMessage(), e);
|
||||
return "FAIL";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调通知参数
|
||||
*/
|
||||
private void validateNotifyParams(Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
|
||||
if (tenantId == null) {
|
||||
throw PaymentException.paramError("租户ID不能为空");
|
||||
}
|
||||
|
||||
if (headers == null || headers.isEmpty()) {
|
||||
throw PaymentException.paramError("请求头不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(body)) {
|
||||
throw PaymentException.paramError("请求体不能为空");
|
||||
}
|
||||
|
||||
// 验证必要的微信支付头部信息
|
||||
String signature = headers.get("Wechatpay-Signature");
|
||||
String timestamp = headers.get("Wechatpay-Timestamp");
|
||||
String nonce = headers.get("Wechatpay-Nonce");
|
||||
String serial = headers.get("Wechatpay-Serial");
|
||||
|
||||
if (!StringUtils.hasText(signature)) {
|
||||
throw PaymentException.paramError("微信支付签名不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(timestamp)) {
|
||||
throw PaymentException.paramError("微信支付时间戳不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(nonce)) {
|
||||
throw PaymentException.paramError("微信支付随机数不能为空");
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(serial)) {
|
||||
throw PaymentException.paramError("微信支付序列号不能为空");
|
||||
}
|
||||
|
||||
log.debug("回调通知参数验证通过, 租户ID: {}", tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并验证回调通知
|
||||
*/
|
||||
private Transaction parseAndVerifyNotification(Map<String, String> headers, String body, Config wxPayConfig) throws PaymentException {
|
||||
if (wxPayConfig == null) {
|
||||
throw PaymentException.systemError("微信支付配置为空", null);
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
RequestParam requestParam = new RequestParam.Builder()
|
||||
.serialNumber(headers.get("Wechatpay-Serial"))
|
||||
.nonce(headers.get("Wechatpay-Nonce"))
|
||||
.signature(headers.get("Wechatpay-Signature"))
|
||||
.timestamp(headers.get("Wechatpay-Timestamp"))
|
||||
.body(body)
|
||||
.build();
|
||||
|
||||
// 创建通知解析器
|
||||
NotificationParser parser = new NotificationParser((NotificationConfig) wxPayConfig);
|
||||
|
||||
// 解析并验证通知
|
||||
Transaction transaction = parser.parse(requestParam, Transaction.class);
|
||||
|
||||
if (transaction == null) {
|
||||
throw PaymentException.systemError("解析回调通知失败:transaction为空", null);
|
||||
}
|
||||
|
||||
log.debug("回调通知解析成功, 订单号: {}, 交易状态: {}",
|
||||
transaction.getOutTradeNo(), transaction.getTradeState());
|
||||
|
||||
return transaction;
|
||||
|
||||
} catch (Exception e) {
|
||||
if (e instanceof PaymentException) {
|
||||
throw e;
|
||||
}
|
||||
throw PaymentException.systemError("解析回调通知失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付结果
|
||||
*/
|
||||
private void processPaymentResult(Transaction transaction, Integer tenantId) throws PaymentException {
|
||||
String outTradeNo = transaction.getOutTradeNo();
|
||||
String tradeState = String.valueOf(transaction.getTradeState());
|
||||
|
||||
if (!StringUtils.hasText(outTradeNo)) {
|
||||
throw PaymentException.paramError("商户订单号不能为空");
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
ShopOrder order = shopOrderService.getByOutTradeNo(outTradeNo);
|
||||
if (order == null) {
|
||||
throw PaymentException.systemError("订单不存在: " + outTradeNo, null);
|
||||
}
|
||||
|
||||
// 验证租户ID
|
||||
if (!tenantId.equals(order.getTenantId())) {
|
||||
throw PaymentException.paramError("订单租户ID不匹配");
|
||||
}
|
||||
|
||||
// 验证订单状态 - 使用Boolean类型的payStatus字段
|
||||
if (Boolean.TRUE.equals(order.getPayStatus())) {
|
||||
log.info("订单已支付,跳过处理, 订单号: {}", outTradeNo);
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据交易状态处理
|
||||
switch (tradeState) {
|
||||
case "SUCCESS":
|
||||
handlePaymentSuccess(order, transaction);
|
||||
break;
|
||||
case "REFUND":
|
||||
handlePaymentRefund(order, transaction);
|
||||
break;
|
||||
case "CLOSED":
|
||||
case "REVOKED":
|
||||
case "PAYERROR":
|
||||
handlePaymentFailed(order, transaction);
|
||||
break;
|
||||
default:
|
||||
log.warn("未处理的交易状态: {}, 订单号: {}", tradeState, outTradeNo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付成功
|
||||
*/
|
||||
private void handlePaymentSuccess(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
// 验证金额
|
||||
validateAmount(order, transaction);
|
||||
|
||||
// 更新订单状态
|
||||
order.setPayStatus(true); // 使用Boolean类型
|
||||
order.setTransactionId(transaction.getTransactionId());
|
||||
order.setPayTime(LocalDateTime.parse(transaction.getSuccessTime()));
|
||||
|
||||
// 使用专门的更新方法,会触发支付成功后的业务逻辑
|
||||
shopOrderService.updateByOutTradeNo(order);
|
||||
|
||||
// 推送支付结果通知
|
||||
pushPaymentNotification(order, transaction);
|
||||
|
||||
log.info("支付成功处理完成, 订单号: {}, 微信交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付成功回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付退款
|
||||
*/
|
||||
private void handlePaymentRefund(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
log.info("处理支付退款, 订单号: {}, 微信交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
|
||||
// 这里可以添加退款相关的业务逻辑
|
||||
// 例如:更新订单状态、处理库存、发送通知等
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付退款回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付失败
|
||||
*/
|
||||
private void handlePaymentFailed(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
try {
|
||||
log.info("处理支付失败, 订单号: {}, 交易状态: {}",
|
||||
order.getOrderNo(), transaction.getTradeState());
|
||||
|
||||
// 这里可以添加支付失败相关的业务逻辑
|
||||
// 例如:释放库存、发送通知等
|
||||
|
||||
} catch (Exception e) {
|
||||
throw PaymentException.systemError("处理支付失败回调失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证支付金额
|
||||
*/
|
||||
private void validateAmount(ShopOrder order, Transaction transaction) throws PaymentException {
|
||||
if (transaction.getAmount() == null || transaction.getAmount().getTotal() == null) {
|
||||
throw PaymentException.amountError("回调通知中金额信息为空");
|
||||
}
|
||||
|
||||
// 将订单金额转换为分
|
||||
BigDecimal orderAmount = order.getMoney();
|
||||
if (orderAmount == null) {
|
||||
throw PaymentException.amountError("订单金额为空");
|
||||
}
|
||||
|
||||
int orderAmountFen = orderAmount.multiply(new BigDecimal(100)).intValue();
|
||||
int callbackAmountFen = transaction.getAmount().getTotal();
|
||||
|
||||
if (orderAmountFen != callbackAmountFen) {
|
||||
throw PaymentException.amountError(
|
||||
String.format("订单金额不匹配,订单金额: %d分, 回调金额: %d分",
|
||||
orderAmountFen, callbackAmountFen));
|
||||
}
|
||||
|
||||
log.debug("金额验证通过, 订单号: {}, 金额: {}分", order.getOrderNo(), orderAmountFen);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送支付结果通知
|
||||
*/
|
||||
private void pushPaymentNotification(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
log.info("开始推送支付成功通知, 订单号: {}, 交易号: {}, 用户ID: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId(), order.getUserId());
|
||||
|
||||
// 1. 记录支付成功日志
|
||||
logPaymentSuccess(order, transaction);
|
||||
|
||||
// 2. 发送支付成功通知(可扩展)
|
||||
sendPaymentSuccessNotification(order, transaction);
|
||||
|
||||
// 3. 触发其他业务逻辑(可扩展)
|
||||
triggerPostPaymentActions(order, transaction);
|
||||
|
||||
log.info("支付结果通知推送完成, 订单号: {}, 交易号: {}",
|
||||
order.getOrderNo(), transaction.getTransactionId());
|
||||
} catch (Exception e) {
|
||||
log.warn("支付结果通知推送失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
// 推送失败不影响主流程,只记录日志
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录支付成功日志
|
||||
*/
|
||||
private void logPaymentSuccess(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
log.info("=== 支付成功详细信息 ===");
|
||||
log.info("订单号: {}", order.getOrderNo());
|
||||
log.info("微信交易号: {}", transaction.getTransactionId());
|
||||
log.info("支付金额: {}元", order.getPayPrice());
|
||||
log.info("支付时间: {}", transaction.getSuccessTime());
|
||||
log.info("用户ID: {}", order.getUserId());
|
||||
log.info("租户ID: {}", order.getTenantId());
|
||||
log.info("订单标题: {}", order.getTitle());
|
||||
log.info("========================");
|
||||
} catch (Exception e) {
|
||||
log.warn("记录支付成功日志失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送支付成功通知
|
||||
*/
|
||||
private void sendPaymentSuccessNotification(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
// TODO: 实现具体的通知逻辑
|
||||
// 1. 发送邮件通知
|
||||
// 2. 发送短信通知
|
||||
// 3. 站内消息通知
|
||||
// 4. 微信模板消息通知
|
||||
|
||||
log.debug("支付成功通知发送完成, 订单号: {}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
log.warn("发送支付成功通知失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发支付成功后的其他业务逻辑
|
||||
*/
|
||||
private void triggerPostPaymentActions(ShopOrder order, Transaction transaction) {
|
||||
try {
|
||||
// TODO: 根据业务需求实现
|
||||
// 1. 开通网站服务
|
||||
// 2. 激活会员权益
|
||||
// 3. 发放积分奖励
|
||||
// 4. 触发营销活动
|
||||
|
||||
log.debug("支付后业务逻辑触发完成, 订单号: {}", order.getOrderNo());
|
||||
} catch (Exception e) {
|
||||
log.warn("触发支付后业务逻辑失败, 订单号: {}, 错误: {}", order.getOrderNo(), e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user