- 添加支付常量类PaymentConstants,定义支付状态、微信、支付宝、银联等相关常量 - 创建微信支付类型常量类WechatPayType,支持JSAPI、NATIVE、H5、APP支付方式 - 新增支付控制器PaymentController,提供创建支付、查询状态、退款等统一接口 - 实现支付回调控制器PaymentNotifyController,处理微信、支付宝、银联异步通知 - 添加支付请求数据传输对象PaymentRequest,支持多种支付方式参数校验 - 定义支付响应、状态更新请求等相关DTO类- 集成Swagger注解,完善接口文档说明- 添加参数校验和异常处理机制,确保支付流程安全可靠
402 lines
16 KiB
Java
402 lines
16 KiB
Java
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);
|
||
}
|
||
}
|
||
}
|