diff --git a/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java b/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java index 9b54fb0..3060e5e 100644 --- a/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java +++ b/src/main/java/com/gxwebsoft/payment/service/WxPayConfigService.java @@ -46,6 +46,20 @@ public class WxPayConfigService { @Value("${spring.profiles.active:dev}") private String activeProfile; + /** + * 获取支付配置信息(Payment对象)- 公开方法 + * + * @param tenantId 租户ID + * @return 支付配置信息 + * @throws PaymentException 配置获取失败时抛出 + */ + public Payment getPaymentConfigForStrategy(Integer tenantId) throws PaymentException { + if (tenantId == null) { + throw PaymentException.paramError("租户ID不能为空"); + } + return getPaymentConfig(tenantId); + } + /** * 获取微信支付配置 * @@ -107,13 +121,13 @@ public class WxPayConfigService { System.out.println("payment = " + payment); if (payment != null) { log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId); - return payment; +// return payment; } // 缓存中没有,尝试从数据库查询 try { final PaymentParam paymentParam = new PaymentParam(); - paymentParam.setType(1); + paymentParam.setType(102); paymentParam.setTenantId(tenantId); payment = paymentService.getByType(paymentParam); System.out.println("payment1 = " + payment); @@ -133,7 +147,8 @@ public class WxPayConfigService { } log.debug("开发环境模式,将使用测试配置,租户ID: {}", tenantId); - return null; // 开发环境返回null,使用测试配置 + // 开发环境返回测试Payment配置 + return createDevTestPayment(tenantId); } /** @@ -214,12 +229,33 @@ public class WxPayConfigService { } } + /** + * 创建开发环境测试Payment配置 + */ + private Payment createDevTestPayment(Integer tenantId) { + Payment testPayment = new Payment(); + testPayment.setTenantId(tenantId); + testPayment.setType(102); // Native支付 + testPayment.setAppId("wxa67c676fc445590e"); // 开发环境测试AppID + testPayment.setMchId("1246610101"); // 开发环境测试商户号 + testPayment.setMerchantSerialNumber("48749613B40AA8F1D768583FC352358E13EB5AF0"); + testPayment.setApiKey(certificateProperties.getWechatPay().getDev().getApiV3Key()); + testPayment.setNotifyUrl("http://frps-10550.s209.websoft.top/api/payment/notify"); + testPayment.setName("微信Native支付-开发环境"); + testPayment.setStatus(true); // 启用 + + log.info("创建开发环境测试Payment配置,租户ID: {}, AppID: {}, 商户号: {}", + tenantId, testPayment.getAppId(), testPayment.getMchId()); + + return testPayment; + } + /** * 创建开发环境测试配置 */ private Config createDevTestConfig(String certificatePath) throws PaymentException { String testMerchantId = "1246610101"; - String testMerchantSerialNumber = "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7"; + String testMerchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0"; String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key(); if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) { diff --git a/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java b/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java index 9e1473c..518cad2 100644 --- a/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java +++ b/src/main/java/com/gxwebsoft/payment/strategy/WechatNativeStrategy.java @@ -1,9 +1,11 @@ package com.gxwebsoft.payment.strategy; 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; @@ -13,6 +15,8 @@ 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; @@ -96,11 +100,14 @@ public class WechatNativeStrategy implements PaymentStrategy { // 生成订单号 String orderNo = generateOrderNo(request); + // 获取Native支付的Payment配置(包含appId等信息) + Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(request.getTenantId()); + // 获取微信支付配置 Config wxPayConfig = wxPayConfigService.getWxPayConfig(request.getTenantId()); // 构建预支付请求 - PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo); + PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo, paymentConfig); // 调用微信支付API PrepayResponse prepayResponse = callWechatPayApi(prepayRequest, wxPayConfig); @@ -131,8 +138,34 @@ public class WechatNativeStrategy implements PaymentStrategy { @Override public PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException { - // TODO: 实现微信支付查询逻辑 - throw PaymentException.unsupportedPayment("暂不支持微信支付查询", PaymentType.WECHAT_NATIVE); + 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 @@ -200,12 +233,18 @@ public class WechatNativeStrategy implements PaymentStrategy { return CommonUtil.createOrderNo(); } + + /** * 构建微信预支付请求 */ - private PrepayRequest buildPrepayRequest(PaymentRequest request, String orderNo) { + 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()); @@ -216,17 +255,109 @@ public class WechatNativeStrategy implements PaymentStrategy { prepayRequest.setOutTradeNo(orderNo); prepayRequest.setDescription(request.getEffectiveDescription()); - // 设置回调URL + // 设置回调URL(必填字段) + String notifyUrl = null; if (StringUtils.hasText(request.getNotifyUrl())) { - prepayRequest.setNotifyUrl(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("构建微信预支付请求完成, 订单号: {}, 金额: {}分", - orderNo, request.getAmountInCents()); + 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 { + // 构建查询请求 + 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 */ diff --git a/test_payment_fix.md b/test_payment_fix.md new file mode 100644 index 0000000..9b626a4 --- /dev/null +++ b/test_payment_fix.md @@ -0,0 +1,99 @@ +# 微信Native支付修复验证 + +## 🔧 修复内容 + +### 问题描述 +微信支付API调用失败,错误信息: +1. **第一个错误**: +``` +PARAM_ERROR: "输入源"/body/appid"映射到字段"公众号ID"必填性规则校验失败,此字段为必填项" +``` + +2. **第二个错误**(修复appid后出现): +``` +PARAM_ERROR: "输入源"/body/notify_url"映射到字段"通知地址"必填性规则校验失败,此字段为必填项" +``` + +### 根本原因 +在 `WechatNativeStrategy.buildPrepayRequest()` 方法中缺少设置必填字段: +1. 缺少 `appId` 和 `mchId` 字段 +2. 缺少 `notify_url` 字段的正确处理 + +### 修复方案 +1. **添加PaymentService注入**:获取Native支付配置 +2. **新增getNativePaymentConfig方法**:专门获取type=102的Native支付配置 +3. **修改buildPrepayRequest方法**: + - 添加appId和mchId设置 + - 完善notify_url的处理逻辑 + +### 修复后的关键代码 + +```java +// 1. 获取Native支付配置 +Payment paymentConfig = getNativePaymentConfig(request.getTenantId()); + +// 2. 构建预支付请求时设置必填字段 +prepayRequest.setAppid(paymentConfig.getAppId()); +prepayRequest.setMchid(paymentConfig.getMchId()); + +// 3. 设置回调URL(必填字段) +String notifyUrl = null; +if (StringUtils.hasText(request.getNotifyUrl())) { + notifyUrl = request.getNotifyUrl(); +} else if (StringUtils.hasText(paymentConfig.getNotifyUrl())) { + notifyUrl = paymentConfig.getNotifyUrl(); +} else { + throw new RuntimeException("回调通知地址不能为空"); +} +prepayRequest.setNotifyUrl(notifyUrl); +``` + +## 🎯 支付类型区分 + +### 数据库配置 +- **type = 1**: 微信小程序支付(使用小程序appid) +- **type = 102**: 微信Native支付(使用开放平台appid) + +### 使用场景 +- **小程序支付**: 在微信小程序内调用支付 +- **Native支付**: PC端扫码支付,生成二维码 + +## ✅ 验证步骤 + +1. **确保数据库配置**: + ```sql + SELECT * FROM sys_payment WHERE type = 102 AND tenant_id = ?; + ``` + 确保有Native支付配置且appId不为空 + +2. **测试支付调用**: + - 调用Native支付接口 + - 检查日志中是否包含AppID和商户号信息 + - 验证微信API不再返回PARAM_ERROR + +3. **检查日志输出**: + ``` + 构建微信预支付请求完成, 订单号: xxx, 金额: xxx分, AppID: wxXXX, 商户号: xxx, 回调URL: xxx + ``` + +## ⚠️ 常见问题 + +### APPID_MCHID_NOT_MATCH 错误 +如果出现 "appid和mch_id不匹配" 错误,请检查: + +1. **确保appid和mchid是绑定关系**: + ```sql + SELECT app_id, mch_id FROM sys_payment WHERE type = 102 AND tenant_id = ?; + ``` + +2. **验证微信支付配置**: + - appid:必须是微信开放平台的网站应用appid(用于Native支付) + - mchid:必须是与该appid绑定的微信支付商户号 + +3. **检查微信支付后台**: + - 登录微信支付商户平台 + - 确认该商户号下绑定了正确的appid + - 确认产品权限已开通(Native支付) + +## 🚀 预期结果 +修复后,微信Native支付应该能够正常调用,不再出现参数错误。