fix(payment): 修复微信 Native支付参数错误问题
- 添加获取 Native 支付配置的方法- 在构建预支付请求时设置必填字段:appId、mchId 和 notifyUrl - 实现微信支付状态查询功能 -优化日志输出,增加支付配置信息
This commit is contained in:
@@ -46,6 +46,20 @@ public class WxPayConfigService {
|
|||||||
@Value("${spring.profiles.active:dev}")
|
@Value("${spring.profiles.active:dev}")
|
||||||
private String activeProfile;
|
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);
|
System.out.println("payment = " + payment);
|
||||||
if (payment != null) {
|
if (payment != null) {
|
||||||
log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId);
|
log.debug("从缓存获取支付配置成功,租户ID: {}", tenantId);
|
||||||
return payment;
|
// return payment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 缓存中没有,尝试从数据库查询
|
// 缓存中没有,尝试从数据库查询
|
||||||
try {
|
try {
|
||||||
final PaymentParam paymentParam = new PaymentParam();
|
final PaymentParam paymentParam = new PaymentParam();
|
||||||
paymentParam.setType(1);
|
paymentParam.setType(102);
|
||||||
paymentParam.setTenantId(tenantId);
|
paymentParam.setTenantId(tenantId);
|
||||||
payment = paymentService.getByType(paymentParam);
|
payment = paymentService.getByType(paymentParam);
|
||||||
System.out.println("payment1 = " + payment);
|
System.out.println("payment1 = " + payment);
|
||||||
@@ -133,7 +147,8 @@ public class WxPayConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.debug("开发环境模式,将使用测试配置,租户ID: {}", tenantId);
|
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 {
|
private Config createDevTestConfig(String certificatePath) throws PaymentException {
|
||||||
String testMerchantId = "1246610101";
|
String testMerchantId = "1246610101";
|
||||||
String testMerchantSerialNumber = "2903B872D5CA36E525FAEC37AEDB22E54ECDE7B7";
|
String testMerchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0";
|
||||||
String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key();
|
String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key();
|
||||||
|
|
||||||
if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) {
|
if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) {
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.gxwebsoft.payment.strategy;
|
package com.gxwebsoft.payment.strategy;
|
||||||
|
|
||||||
import com.gxwebsoft.common.core.utils.CommonUtil;
|
import com.gxwebsoft.common.core.utils.CommonUtil;
|
||||||
|
import com.gxwebsoft.common.system.entity.Payment;
|
||||||
import com.gxwebsoft.payment.constants.PaymentConstants;
|
import com.gxwebsoft.payment.constants.PaymentConstants;
|
||||||
import com.gxwebsoft.payment.dto.PaymentRequest;
|
import com.gxwebsoft.payment.dto.PaymentRequest;
|
||||||
import com.gxwebsoft.payment.dto.PaymentResponse;
|
import com.gxwebsoft.payment.dto.PaymentResponse;
|
||||||
|
import com.gxwebsoft.payment.enums.PaymentStatus;
|
||||||
import com.gxwebsoft.payment.enums.PaymentType;
|
import com.gxwebsoft.payment.enums.PaymentType;
|
||||||
import com.gxwebsoft.payment.exception.PaymentException;
|
import com.gxwebsoft.payment.exception.PaymentException;
|
||||||
import com.gxwebsoft.payment.service.WxPayConfigService;
|
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.Amount;
|
||||||
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
|
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.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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@@ -96,11 +100,14 @@ public class WechatNativeStrategy implements PaymentStrategy {
|
|||||||
// 生成订单号
|
// 生成订单号
|
||||||
String orderNo = generateOrderNo(request);
|
String orderNo = generateOrderNo(request);
|
||||||
|
|
||||||
|
// 获取Native支付的Payment配置(包含appId等信息)
|
||||||
|
Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(request.getTenantId());
|
||||||
|
|
||||||
// 获取微信支付配置
|
// 获取微信支付配置
|
||||||
Config wxPayConfig = wxPayConfigService.getWxPayConfig(request.getTenantId());
|
Config wxPayConfig = wxPayConfigService.getWxPayConfig(request.getTenantId());
|
||||||
|
|
||||||
// 构建预支付请求
|
// 构建预支付请求
|
||||||
PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo);
|
PrepayRequest prepayRequest = buildPrepayRequest(request, orderNo, paymentConfig);
|
||||||
|
|
||||||
// 调用微信支付API
|
// 调用微信支付API
|
||||||
PrepayResponse prepayResponse = callWechatPayApi(prepayRequest, wxPayConfig);
|
PrepayResponse prepayResponse = callWechatPayApi(prepayRequest, wxPayConfig);
|
||||||
@@ -131,8 +138,34 @@ public class WechatNativeStrategy implements PaymentStrategy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException {
|
public PaymentResponse queryPayment(String orderNo, Integer tenantId) throws PaymentException {
|
||||||
// TODO: 实现微信支付查询逻辑
|
log.info("开始查询微信Native支付状态, 订单号: {}, 租户ID: {}", orderNo, tenantId);
|
||||||
throw PaymentException.unsupportedPayment("暂不支持微信支付查询", PaymentType.WECHAT_NATIVE);
|
|
||||||
|
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
|
@Override
|
||||||
@@ -200,12 +233,18 @@ public class WechatNativeStrategy implements PaymentStrategy {
|
|||||||
return CommonUtil.createOrderNo();
|
return CommonUtil.createOrderNo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建微信预支付请求
|
* 构建微信预支付请求
|
||||||
*/
|
*/
|
||||||
private PrepayRequest buildPrepayRequest(PaymentRequest request, String orderNo) {
|
private PrepayRequest buildPrepayRequest(PaymentRequest request, String orderNo, Payment paymentConfig) {
|
||||||
PrepayRequest prepayRequest = new PrepayRequest();
|
PrepayRequest prepayRequest = new PrepayRequest();
|
||||||
|
|
||||||
|
// 设置应用ID和商户号(关键修复)
|
||||||
|
prepayRequest.setAppid(paymentConfig.getAppId());
|
||||||
|
prepayRequest.setMchid(paymentConfig.getMchId());
|
||||||
|
|
||||||
// 设置金额
|
// 设置金额
|
||||||
Amount amount = new Amount();
|
Amount amount = new Amount();
|
||||||
amount.setTotal(request.getAmountInCents());
|
amount.setTotal(request.getAmountInCents());
|
||||||
@@ -216,17 +255,109 @@ public class WechatNativeStrategy implements PaymentStrategy {
|
|||||||
prepayRequest.setOutTradeNo(orderNo);
|
prepayRequest.setOutTradeNo(orderNo);
|
||||||
prepayRequest.setDescription(request.getEffectiveDescription());
|
prepayRequest.setDescription(request.getEffectiveDescription());
|
||||||
|
|
||||||
// 设置回调URL
|
// 设置回调URL(必填字段)
|
||||||
|
String notifyUrl = null;
|
||||||
if (StringUtils.hasText(request.getNotifyUrl())) {
|
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("构建微信预支付请求完成, 订单号: {}, 金额: {}分",
|
log.debug("构建微信预支付请求完成, 订单号: {}, 金额: {}分, AppID: {}, 商户号: {}, 回调URL: {}",
|
||||||
orderNo, request.getAmountInCents());
|
orderNo, request.getAmountInCents(), paymentConfig.getAppId(), paymentConfig.getMchId(), notifyUrl);
|
||||||
|
|
||||||
return prepayRequest;
|
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
|
* 调用微信支付API
|
||||||
*/
|
*/
|
||||||
|
|||||||
99
test_payment_fix.md
Normal file
99
test_payment_fix.md
Normal file
@@ -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支付应该能够正常调用,不再出现参数错误。
|
||||||
Reference in New Issue
Block a user