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

402 lines
16 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.strategy;
import cn.hutool.core.util.IdUtil;
import com.gxwebsoft.common.core.utils.CommonUtil;
import com.gxwebsoft.common.system.entity.Payment;
import com.gxwebsoft.payment.constants.PaymentConstants;
import com.gxwebsoft.payment.dto.PaymentRequest;
import com.gxwebsoft.payment.dto.PaymentResponse;
import com.gxwebsoft.payment.enums.PaymentStatus;
import com.gxwebsoft.payment.enums.PaymentType;
import com.gxwebsoft.payment.exception.PaymentException;
import com.gxwebsoft.payment.service.WxPayConfigService;
import com.gxwebsoft.payment.service.WxPayNotifyService;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.payments.model.Transaction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Map;
/**
* 微信Native支付策略实现
* 处理微信Native扫码支付
*
* @author 科技小王子
* @since 2025-01-26
*/
@Slf4j
@Component
public class WechatNativeStrategy implements PaymentStrategy {
@Resource
private WxPayConfigService wxPayConfigService;
@Resource
private WxPayNotifyService wxPayNotifyService;
@Override
public PaymentType getSupportedPaymentType() {
return PaymentType.WECHAT_NATIVE;
}
@Override
public void validateRequest(PaymentRequest request) throws PaymentException {
if (request == null) {
throw PaymentException.paramError("支付请求不能为空");
}
if (request.getTenantId() == null) {
throw PaymentException.paramError("租户ID不能为空");
}
if (request.getUserId() == null) {
throw PaymentException.paramError("用户ID不能为空");
}
if (request.getAmount() == null || request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw PaymentException.amountError("支付金额必须大于0");
}
if (!StringUtils.hasText(request.getSubject())) {
throw PaymentException.paramError("订单标题不能为空");
}
// 验证金额范围
if (request.getAmount().compareTo(new BigDecimal("0.01")) < 0) {
throw PaymentException.amountError("支付金额不能小于0.01元");
}
if (request.getAmount().compareTo(new BigDecimal("999999.99")) > 0) {
throw PaymentException.amountError("支付金额不能超过999999.99元");
}
// 验证订单标题长度
if (request.getSubject().length() > PaymentConstants.Config.DESCRIPTION_MAX_LENGTH) {
throw PaymentException.paramError("订单标题长度不能超过" + PaymentConstants.Config.DESCRIPTION_MAX_LENGTH + "个字符");
}
log.debug("微信Native支付请求参数验证通过, 租户ID: {}, 金额: {}",
request.getTenantId(), request.getFormattedAmount());
}
@Override
public PaymentResponse createPayment(PaymentRequest request) throws PaymentException {
log.info("{}, 支付类型: {}, 租户ID: {}, 金额: {}",
PaymentConstants.LogMessage.PAYMENT_START, getSupportedPaymentType(),
request.getTenantId(), request.getFormattedAmount());
try {
// 验证请求参数
validateRequest(request);
// 生成订单号
String orderNo = generateOrderNo(request);
log.info("生成的订单号: {}", orderNo);
// 获取Native支付的Payment配置包含appId等信息
Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(request.getTenantId());
// 获取微信支付配置
Config wxPayConfig = wxPayConfigService.getWxPayConfig(request.getTenantId());
// 构建预支付请求
PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo, paymentConfig);
// 调用微信支付API
PrepayResponse prepayResponse = callWechatPayApi(prepayRequest, wxPayConfig);
// 构建响应
PaymentResponse response = PaymentResponse.wechatNative(
orderNo, prepayResponse.getCodeUrl(), request.getAmount(), request.getTenantId());
response.setUserId(request.getUserId());
// 确保orderNo被正确设置
response.setOrderNo(orderNo);
// 调试日志检查响应对象的orderNo
log.info("构建的响应对象 - orderNo: {}, codeUrl: {}, success: {}",
response.getOrderNo(), response.getCodeUrl(), response.getSuccess());
log.info("{}, 支付类型: {}, 租户ID: {}, 订单号: {}, 金额: {}",
PaymentConstants.LogMessage.PAYMENT_SUCCESS, getSupportedPaymentType(),
request.getTenantId(), orderNo, request.getFormattedAmount());
return response;
} catch (PaymentException e) {
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
PaymentConstants.LogMessage.PAYMENT_FAILED, getSupportedPaymentType(),
request.getTenantId(), e.getMessage());
throw e;
} catch (Exception e) {
log.error("{}, 支付类型: {}, 租户ID: {}, 系统错误: {}",
PaymentConstants.LogMessage.PAYMENT_FAILED, getSupportedPaymentType(),
request.getTenantId(), e.getMessage(), e);
throw PaymentException.systemError("微信Native支付创建失败: " + e.getMessage(), e);
}
}
@Override
public PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException {
log.info("开始查询微信Native支付状态, 订单号: {}, 租户ID: {}", orderNo, tenantId);
try {
// 参数验证
if (!StringUtils.hasText(orderNo)) {
throw PaymentException.paramError("订单号不能为空");
}
if (tenantId == null) {
throw PaymentException.paramError("租户ID不能为空");
}
// 获取支付配置(包含商户号等信息)
Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(tenantId);
// 获取微信支付配置
Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);
// 调用微信支付查询API
return queryWechatPaymentStatus(orderNo, tenantId, paymentConfig, wxPayConfig);
} catch (Exception e) {
if (e instanceof PaymentException) {
throw e;
}
log.error("查询微信Native支付状态失败, 订单号: {}, 租户ID: {}, 错误: {}",
orderNo, tenantId, e.getMessage(), e);
throw PaymentException.systemError("查询微信支付状态失败: " + e.getMessage(), e);
}
}
@Override
public String handleNotify(Map<String, String> headers, String body, Integer tenantId) throws PaymentException {
log.info("{}, 支付类型: {}, 租户ID: {}",
PaymentConstants.LogMessage.NOTIFY_START, getSupportedPaymentType(), tenantId);
try {
// 委托给专门的回调处理服务
return wxPayNotifyService.handlePaymentNotify(headers, body, tenantId);
} catch (Exception e) {
log.error("{}, 支付类型: {}, 租户ID: {}, 错误: {}",
PaymentConstants.LogMessage.NOTIFY_FAILED, getSupportedPaymentType(),
tenantId, e.getMessage(), e);
throw PaymentException.systemError("微信支付回调处理失败: " + e.getMessage(), e);
}
}
@Override
public PaymentResponse refund(String orderNo, String refundNo, BigDecimal totalAmount,
BigDecimal refundAmount, String reason, Integer tenantId) throws PaymentException {
// TODO: 实现微信支付退款逻辑
throw PaymentException.unsupportedPayment("暂不支持微信支付退款", PaymentType.WECHAT_NATIVE);
}
@Override
public PaymentResponse queryRefund(String refundNo, Integer tenantId) throws PaymentException {
// TODO: 实现微信退款查询逻辑
throw PaymentException.unsupportedPayment("暂不支持微信退款查询", PaymentType.WECHAT_NATIVE);
}
@Override
public boolean closeOrder(String orderNo, Integer tenantId) throws PaymentException {
// TODO: 实现微信订单关闭逻辑
throw PaymentException.unsupportedPayment("暂不支持微信订单关闭", PaymentType.WECHAT_NATIVE);
}
@Override
public boolean supportRefund() {
return true;
}
@Override
public boolean supportQuery() {
return true;
}
@Override
public boolean supportClose() {
return true;
}
@Override
public boolean needNotify() {
return true;
}
/**
* 生成订单号(使用雪花算法保证全局唯一)
*/
private String generateOrderNo(PaymentRequest request) {
if (StringUtils.hasText(request.getOrderNo())) {
return request.getOrderNo();
}
return Long.toString(IdUtil.getSnowflakeNextId());
}
/**
* 构建微信预支付请求
*/
private PrepayRequest buildPrepayRequest(PaymentRequest request, String orderNo, Payment paymentConfig) {
PrepayRequest prepayRequest = new PrepayRequest();
// 设置应用ID和商户号关键修复
prepayRequest.setAppid(paymentConfig.getAppId());
prepayRequest.setMchid(paymentConfig.getMchId());
// 设置金额
Amount amount = new Amount();
amount.setTotal(request.getAmountInCents());
amount.setCurrency(PaymentConstants.Wechat.CURRENCY);
prepayRequest.setAmount(amount);
// 设置基本信息
prepayRequest.setOutTradeNo(orderNo);
prepayRequest.setDescription(request.getEffectiveDescription());
log.info("创建微信支付订单 - 订单号: {}, 商户号: {}, 金额: {}分",
orderNo, paymentConfig.getMchId(), request.getAmountInCents());
// 设置回调URL必填字段
String notifyUrl = null;
if (StringUtils.hasText(request.getNotifyUrl())) {
// 优先使用请求中的回调URL
notifyUrl = request.getNotifyUrl();
} else if (StringUtils.hasText(paymentConfig.getNotifyUrl())) {
// 使用配置中的回调URL
notifyUrl = paymentConfig.getNotifyUrl();
} else {
// 如果都没有,抛出异常
throw new RuntimeException("回调通知地址不能为空请在支付请求中设置notifyUrl或在支付配置中设置notifyUrl");
}
prepayRequest.setNotifyUrl(notifyUrl);
log.debug("构建微信预支付请求完成, 订单号: {}, 金额: {}分, AppID: {}, 商户号: {}, 回调URL: {}",
orderNo, request.getAmountInCents(), paymentConfig.getAppId(), paymentConfig.getMchId(), notifyUrl);
return prepayRequest;
}
/**
* 查询微信支付状态
*/
private PaymentResponse queryWechatPaymentStatus(String orderNo, Integer tenantId, Payment paymentConfig, Config wxPayConfig) throws PaymentException {
try {
log.info("开始查询微信支付状态 - 订单号: {}, 商户号: {}, 租户ID: {}",
orderNo, paymentConfig.getMchId(), tenantId);
// 构建查询请求
QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest();
queryRequest.setOutTradeNo(orderNo);
queryRequest.setMchid(paymentConfig.getMchId());
// 构建服务
NativePayService service = new NativePayService.Builder().config(wxPayConfig).build();
// 调用查询接口
Transaction transaction = service.queryOrderByOutTradeNo(queryRequest);
if (transaction == null) {
throw PaymentException.systemError("微信支付查询返回空结果", null);
}
// 转换支付状态
PaymentStatus paymentStatus = convertWechatPaymentStatus(transaction.getTradeState());
// 构建响应
PaymentResponse response = new PaymentResponse();
response.setSuccess(true);
response.setOrderNo(orderNo);
response.setPaymentStatus(paymentStatus);
response.setTenantId(tenantId);
response.setPaymentType(PaymentType.WECHAT_NATIVE);
if (transaction.getAmount() != null) {
// 微信返回的金额是分,需要转换为元
BigDecimal amount = new BigDecimal(transaction.getAmount().getTotal()).divide(new BigDecimal("100"));
response.setAmount(amount);
}
if (transaction.getTransactionId() != null) {
response.setTransactionId(transaction.getTransactionId());
}
log.info("微信Native支付状态查询成功, 订单号: {}, 状态: {}, 微信交易号: {}",
orderNo, paymentStatus, transaction.getTransactionId());
return response;
} catch (Exception e) {
if (e instanceof PaymentException) {
throw e;
}
log.error("查询微信支付状态失败, 订单号: {}, 错误: {}", orderNo, e.getMessage(), e);
throw PaymentException.networkError("查询微信支付状态失败: " + e.getMessage(), PaymentType.WECHAT_NATIVE, e);
}
}
/**
* 转换微信支付状态
*/
private PaymentStatus convertWechatPaymentStatus(Transaction.TradeStateEnum tradeState) {
if (tradeState == null) {
return PaymentStatus.PENDING;
}
switch (tradeState) {
case SUCCESS:
return PaymentStatus.SUCCESS;
case REFUND:
return PaymentStatus.REFUNDED;
case NOTPAY:
return PaymentStatus.PENDING;
case CLOSED:
return PaymentStatus.CANCELLED;
case REVOKED:
return PaymentStatus.CANCELLED;
case USERPAYING:
return PaymentStatus.PROCESSING;
case PAYERROR:
return PaymentStatus.FAILED;
default:
return PaymentStatus.PENDING;
}
}
/**
* 调用微信支付API
*/
private PrepayResponse callWechatPayApi(PrepayRequest request, Config wxPayConfig) throws PaymentException {
try {
// 构建服务
NativePayService service = new NativePayService.Builder().config(wxPayConfig).build();
// 调用预支付接口
PrepayResponse response = service.prepay(request);
if (response == null || !StringUtils.hasText(response.getCodeUrl())) {
throw PaymentException.networkError("微信支付API返回数据异常", PaymentType.WECHAT_NATIVE, null);
}
log.debug("微信支付API调用成功, 订单号: {}", request.getOutTradeNo());
return response;
} catch (Exception e) {
if (e instanceof PaymentException) {
throw e;
}
throw PaymentException.networkError("调用微信支付API失败: " + e.getMessage(), PaymentType.WECHAT_NATIVE, e);
}
}
}