Files
template-10559/java/payment/service/WxPayNotifyService.java
赵忠林 5749fab9e8 feat(payment): 初始化支付模块核心代码
- 添加支付常量类PaymentConstants,定义支付状态、微信、支付宝、银联等相关常量
- 创建微信支付类型常量类WechatPayType,支持JSAPI、NATIVE、H5、APP支付方式
- 新增支付控制器PaymentController,提供创建支付、查询状态、退款等统一接口
- 实现支付回调控制器PaymentNotifyController,处理微信、支付宝、银联异步通知
- 添加支付请求数据传输对象PaymentRequest,支持多种支付方式参数校验
- 定义支付响应、状态更新请求等相关DTO类- 集成Swagger注解,完善接口文档说明- 添加参数校验和异常处理机制,确保支付流程安全可靠
2025-11-03 12:31:47 +08:00

367 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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());
}
}
}