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