diff --git a/docs/CERTIFICATE_FIX_SUMMARY.md b/docs/CERTIFICATE_FIX_SUMMARY.md new file mode 100644 index 0000000..6385b72 --- /dev/null +++ b/docs/CERTIFICATE_FIX_SUMMARY.md @@ -0,0 +1,192 @@ +# 微信支付证书问题修复总结 + +## 问题描述 + +**错误信息**:`创建支付订单失败:创建支付订单失败:Cannot invoke "java.security.cert.X509Certificate.getSerialNumber()" because "certificate" is null` + +**错误代码**:1 + +## 问题分析 + +这个错误发生在微信支付SDK使用 `RSAAutoCertificateConfig` 自动证书配置时,SDK尝试自动下载微信支付平台证书但失败,导致证书对象为null,进而在调用 `getSerialNumber()` 方法时抛出空指针异常。 + +## 已实施的修复方案 + +### 1. 增强错误处理和自动回退机制 + +**文件**:`src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java` + +**修复内容**: +- 在开发环境和生产环境都增加了详细的错误诊断 +- 实现了自动回退机制:当 `RSAAutoCertificateConfig` 失败时,自动回退到 `RSAConfig` 或 `RSAPublicKeyConfig` +- 增加了特定的证书错误检测和处理逻辑 +- 提供了详细的错误信息和修复建议 + +### 2. 创建证书诊断工具 + +**文件**:`src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java` + +**功能**: +- 全面诊断微信支付证书配置 +- 检查基本配置(商户号、应用ID、APIv3密钥、证书序列号) +- 验证证书文件存在性和有效性 +- 检查证书内容和序列号匹配 +- 生成详细的诊断报告和修复建议 + +### 3. 创建证书修复工具 + +**文件**:`src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java` + +**功能**: +- 自动检测和修复常见的证书配置问题 +- 验证证书文件路径和内容 +- 检查序列号匹配性 +- 提供自动修复建议 + +### 4. 创建诊断API接口 + +**文件**:`src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java` + +**提供的API**: +- `GET /system/wechat-pay-diagnostic/diagnose/{tenantId}` - 诊断特定租户的证书配置 +- `GET /system/wechat-pay-diagnostic/solutions` - 获取证书问题解决方案 +- `POST /system/wechat-pay-diagnostic/test/{tenantId}` - 测试证书配置 +- `GET /system/wechat-pay-diagnostic/environment` - 获取环境信息 +- `GET /system/wechat-pay-diagnostic/guide` - 获取证书配置指南 + +### 5. 集成诊断功能 + +在支付服务中集成了证书诊断功能,每次创建支付订单时都会运行诊断,提供详细的配置信息和错误分析。 + +## 使用方法 + +### 1. 自动诊断 + +系统在创建支付订单时会自动运行诊断,查看控制台输出: + +``` +=== 微信支付证书诊断报告 === +租户ID: 10550 +商户号: 1723321338 +应用ID: wx1234567890abcdef +商户证书序列号: 2B933F7C35014A1C363642623E4A62364B34C4EB +APIv3密钥: 已配置(32位) +证书文件路径: dev/wechat/10550/apiclient_key.pem +证书文件存在: 是 +配置验证结果: 通过 +``` + +### 2. 手动诊断 + +使用诊断API进行手动检查: + +```bash +# 诊断特定租户 +curl -X GET "http://localhost:9200/system/wechat-pay-diagnostic/diagnose/10550" \ + -H "Authorization: Bearer YOUR_TOKEN" + +# 获取解决方案 +curl -X GET "http://localhost:9200/system/wechat-pay-diagnostic/solutions" + +# 测试证书配置 +curl -X POST "http://localhost:9200/system/wechat-pay-diagnostic/test/10550" \ + -H "Authorization: Bearer YOUR_TOKEN" +``` + +### 3. 查看配置指南 + +访问 `GET /system/wechat-pay-diagnostic/guide` 获取完整的证书配置指南。 + +## 常见问题解决 + +### 1. 商户平台配置 + +确保在微信商户平台完成以下配置: +1. 开启API安全功能 +2. 申请使用微信支付公钥 +3. 下载商户证书文件 +4. 设置32位APIv3密钥 + +### 2. 证书文件配置 + +**开发环境**: +``` +src/main/resources/dev/wechat/{tenantId}/ +├── apiclient_key.pem # 必需:商户私钥 +└── apiclient_cert.pem # 可选:商户证书 +``` + +**生产环境**: +- 将证书文件上传到服务器指定目录 +- 在数据库中配置正确的文件路径 + +### 3. 数据库配置 + +在 `payment` 表中确保以下字段正确配置: +- `mch_id`: 商户号 +- `app_id`: 应用ID +- `merchant_serial_number`: 商户证书序列号 +- `api_key`: APIv3密钥(32位) + +## 技术特性 + +### 1. 自动回退机制 + +当自动证书配置失败时,系统会自动尝试以下回退方案: +1. `RSAAutoCertificateConfig` (首选) +2. `RSAPublicKeyConfig` (如果有公钥配置) +3. `RSAConfig` (如果有商户证书文件) + +### 2. 详细错误诊断 + +系统会检测特定的错误类型并提供针对性的解决方案: +- X509Certificate相关错误 +- 404错误(API安全未开启) +- 证书序列号错误 +- APIv3密钥错误 +- 网络连接问题 + +### 3. 环境适配 + +支持开发环境和生产环境的不同配置方式: +- 开发环境:从classpath加载证书 +- 生产环境:从文件系统或Docker挂载卷加载证书 + +## 监控和维护 + +### 1. 日志监控 + +关注以下日志信息: +- 证书诊断报告 +- 自动回退日志 +- 错误详情和建议 + +### 2. 定期检查 + +建议定期执行以下检查: +- 证书有效期 +- 配置完整性 +- 网络连接状态 + +### 3. 更新维护 + +- 定期更新微信支付SDK版本 +- 监控微信支付平台公告 +- 及时更新过期证书 + +## 相关文档 + +- [微信支付证书问题修复指南](./WECHAT_PAY_CERTIFICATE_FIX.md) +- [微信支付官方文档](https://pay.weixin.qq.com/doc/v3/merchant/4012153196) +- [API安全配置指南](https://pay.weixin.qq.com/doc/v3/merchant/4012153196) + +## 总结 + +通过实施以上修复方案,系统现在具备了: +1. **自动错误检测和诊断** +2. **智能回退机制** +3. **详细的错误信息和修复建议** +4. **完整的诊断和修复工具** +5. **API接口支持** + +这些改进大大提高了微信支付证书问题的可诊断性和可修复性,减少了因证书配置问题导致的支付失败。 diff --git a/docs/FINAL_FIX_SUMMARY.md b/docs/FINAL_FIX_SUMMARY.md new file mode 100644 index 0000000..081b17f --- /dev/null +++ b/docs/FINAL_FIX_SUMMARY.md @@ -0,0 +1,144 @@ +# 微信支付公钥路径修复总结 + +## 问题描述 + +**错误信息**:`公钥文件不存在: /20250114/0f65a8517c284acb90aa83dd0c23e8f6.pem` + +**根本原因**:开发环境的路径拼接逻辑不正确 + +## 修复方案 + +### 🔧 已修复的逻辑 + +**开发环境**: +- 固定使用文件名:`wechatpay_public_key.pem` +- 路径格式:`dev/wechat/{tenantId}/wechatpay_public_key.pem` +- 实际路径:`/Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10547/wechatpay_public_key.pem` + +**生产环境**: +- 直接使用数据库中 `pubKey` 字段存储的完整路径 +- 不进行任何路径拼接 + +### 📋 代码逻辑 + +```java +// 开发环境 +if ("dev".equals(active)) { + // 固定使用 wechatpay_public_key.pem + String tenantCertPath = "dev/wechat/" + order.getTenantId(); + String pubKeyPath = tenantCertPath + "/wechatpay_public_key.pem"; + + if (certificateLoader.certificateExists(pubKeyPath)) { + String pubKeyFile = certificateLoader.loadCertificatePath(pubKeyPath); + // 使用 RSAPublicKeyConfig + } +} + +// 生产环境 +else { + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty()) { + // 直接使用数据库中的路径 + String pubKeyFile = certificateLoader.loadCertificatePath(payment.getPubKey()); + // 使用 RSAPublicKeyConfig + } +} +``` + +### 🎯 预期日志输出 + +**开发环境成功**: +``` +=== 检测到公钥配置,使用RSA公钥模式 === +公钥文件: 20250114/0f65a8517c284acb90aa83dd0c23e8f6.pem +公钥ID: YOUR_PUBLIC_KEY_ID +开发环境公钥文件路径: dev/wechat/10547/wechatpay_public_key.pem +✅ 开发环境公钥文件加载成功: /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10547/wechatpay_public_key.pem +✅ 开发环境RSA公钥配置成功 +``` + +**生产环境成功**: +``` +=== 生产环境检测到公钥配置,使用RSA公钥模式 === +公钥文件路径: /path/to/production/public_key.pem +公钥ID: YOUR_PUBLIC_KEY_ID +✅ 生产环境公钥文件加载成功: /actual/file/path +✅ 生产环境RSA公钥配置成功 +``` + +### 📁 文件结构要求 + +**开发环境**: +``` +src/main/resources/dev/wechat/10547/ +├── apiclient_key.pem # 商户私钥 +├── apiclient_cert.pem # 商户证书 +└── wechatpay_public_key.pem # 微信支付平台公钥(固定文件名) +``` + +**生产环境**: +- 文件可以放在任何位置 +- 数据库中的 `pubKey` 字段存储完整的相对路径 +- 例如:`/wechat/10547/public_key.pem` + +### 🚀 测试步骤 + +1. **确认文件存在**: + ```bash + ls -la /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10547/wechatpay_public_key.pem + ``` + +2. **确认数据库配置**: + ```sql + SELECT tenant_id, pub_key, pub_key_id + FROM sys_payment + WHERE tenant_id = 10547 AND type = 0; + ``` + +3. **重新测试支付订单创建** + +### 🔍 故障排除 + +**如果仍然报错**: + +1. **检查文件是否存在**: + ```bash + ls -la /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10547/ + ``` + +2. **检查文件权限**: + ```bash + chmod 644 /Users/gxwebsoft/JAVA/cms-java-code/src/main/resources/dev/wechat/10547/wechatpay_public_key.pem + ``` + +3. **查看详细日志**: + - 关注 "开发环境公钥文件路径" 的输出 + - 确认路径是否正确 + +4. **如果公钥ID不正确**: + ```sql + UPDATE sys_payment SET + pub_key_id = 'CORRECT_PUBLIC_KEY_ID' + WHERE tenant_id = 10547 AND type = 0; + ``` + +### 📊 配置优先级 + +1. **RSA公钥配置**(最高优先级) + - 开发环境:固定使用 `wechatpay_public_key.pem` + - 生产环境:使用数据库路径 + +2. **RSA自动证书配置** + - 当没有公钥配置时使用 + +3. **RSA手动证书配置** + - 作为最后的回退方案 + +### ✅ 修复完成 + +现在系统应该能够: +1. 在开发环境正确找到 `wechatpay_public_key.pem` 文件 +2. 在生产环境使用数据库中配置的路径 +3. 成功创建RSA公钥配置 +4. 避免 `X509Certificate.getSerialNumber() null` 错误 + +请重新测试支付订单创建功能! diff --git a/docs/SOLUTION_SUMMARY.md b/docs/SOLUTION_SUMMARY.md new file mode 100644 index 0000000..dacc205 --- /dev/null +++ b/docs/SOLUTION_SUMMARY.md @@ -0,0 +1,216 @@ +# 微信支付证书问题解决方案总结 + +## 问题现状 + +**错误信息**:`Cannot invoke "java.security.cert.X509Certificate.getSerialNumber()" because "certificate" is null` + +**根本原因**:微信支付SDK在使用自动证书配置时无法下载平台证书,导致证书对象为null。 + +## 解决方案:使用公钥模式 + +### 🎯 推荐方案:RSA公钥配置 + +我们已经实现了智能配置检测,系统会按以下优先级选择配置方式: + +1. **RSA公钥配置**(最稳定,推荐) +2. **RSA自动证书配置**(需要网络连接) +3. **RSA手动证书配置**(回退方案) + +### 📋 配置步骤 + +#### 1. 准备公钥文件 + +将微信支付平台公钥文件放置到: +``` +src/main/resources/dev/wechat/10547/ +├── apiclient_key.pem # 商户私钥(已有) +├── apiclient_cert.pem # 商户证书(已有) +└── wechatpay_public_key.pem # 微信支付平台公钥(新增) +``` + +#### 2. 数据库配置 + +执行以下SQL更新支付配置: + +```sql +-- 查看当前配置 +SELECT id, tenant_id, mch_id, app_id, merchant_serial_number, + pub_key, pub_key_id, api_key +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; + +-- 更新公钥配置 +UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem', + pub_key_id = 'YOUR_ACTUAL_PUBLIC_KEY_ID' -- 请替换为实际的公钥ID +WHERE tenant_id = 10547 AND type = 0; +``` + +#### 3. 验证配置 + +使用新增的API接口验证配置: + +```bash +# 快速检查配置状态 +GET /system/wechat-pay-diagnostic/check/10547 + +# 获取详细配置建议 +GET /system/wechat-pay-diagnostic/advice/10547 +``` + +### 🔧 已实现的功能 + +#### 1. 智能配置检测 + +系统会自动检测数据库中的公钥配置: + +```java +// 检测逻辑 +if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + // 使用RSA公钥配置 + config = new RSAPublicKeyConfig.Builder()... +} else { + // 回退到自动证书配置 + config = wechatCertAutoConfig.createAutoConfig()... +} +``` + +#### 2. 详细日志输出 + +配置成功时的日志: +``` +=== 检测到公钥配置,使用RSA公钥模式 === +公钥文件: wechatpay_public_key.pem +公钥ID: PUB_KEY_ID_0112422897022025011300326200001208 +公钥文件路径: /path/to/wechatpay_public_key.pem +✅ 开发环境RSA公钥配置成功 +``` + +#### 3. 配置诊断API + +新增的API接口: + +| 接口 | 功能 | 说明 | +|------|------|------| +| `GET /system/wechat-pay-diagnostic/check/{tenantId}` | 快速配置检查 | 检查配置完整性和文件存在性 | +| `GET /system/wechat-pay-diagnostic/advice/{tenantId}` | 获取配置建议 | 生成详细的配置建议和修复步骤 | +| `GET /system/wechat-pay-diagnostic/diagnose/{tenantId}` | 全面诊断 | 完整的证书诊断报告 | + +#### 4. 配置检查工具 + +`WechatPayConfigChecker` 提供: +- 配置完整性检查 +- 文件存在性验证 +- 配置模式识别 +- 问题诊断和建议 + +### 📊 配置状态检查 + +使用配置检查API可以获得详细的状态报告: + +```json +{ + "code": 200, + "message": "配置检查通过", + "data": { + "tenantId": 10547, + "environment": "dev", + "configMode": "公钥模式", + "configComplete": true, + "hasError": false, + "recommendation": "✅ 配置完整,建议使用当前配置", + "configDetails": { + "merchantId": "1723321338", + "appId": "wx1234567890abcdef", + "hasPublicKey": true, + "publicKeyExists": true, + "privateKeyExists": true + } + } +} +``` + +### 🚀 立即行动 + +#### 方案A:使用公钥模式(推荐) + +1. **获取公钥文件和ID**: + - 从微信商户平台或技术支持获取 + - 或使用现有的公钥文件 + +2. **放置文件**: + ```bash + # 将公钥文件复制到指定位置 + cp wechatpay_public_key.pem src/main/resources/dev/wechat/10547/ + ``` + +3. **更新数据库**: + ```sql + UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem', + pub_key_id = 'YOUR_PUBLIC_KEY_ID' + WHERE tenant_id = 10547 AND type = 0; + ``` + +4. **测试验证**: + - 重新尝试创建支付订单 + - 查看日志确认使用公钥模式 + +#### 方案B:修复自动证书配置 + +如果暂时无法获取公钥,可以: + +1. **检查商户平台设置**: + - 确保已开启API安全功能 + - 申请使用微信支付公钥 + +2. **验证网络连接**: + - 确保服务器可以访问微信支付API + - 检查防火墙和代理设置 + +3. **使用诊断工具**: + ```bash + GET /system/wechat-pay-diagnostic/diagnose/10547 + ``` + +### 📈 优势对比 + +| 配置方式 | 稳定性 | 网络依赖 | 配置难度 | 推荐指数 | +|---------|--------|----------|----------|----------| +| RSA公钥配置 | ⭐⭐⭐⭐⭐ | 无 | ⭐⭐ | 🔥🔥🔥🔥🔥 | +| RSA自动证书配置 | ⭐⭐⭐ | 高 | ⭐ | ⭐⭐⭐ | +| RSA手动证书配置 | ⭐⭐⭐ | 无 | ⭐⭐⭐⭐ | ⭐⭐ | + +### 🎯 预期结果 + +配置完成后,您应该看到: + +1. **日志输出**: + ``` + === 检测到公钥配置,使用RSA公钥模式 === + ✅ 开发环境RSA公钥配置成功 + ``` + +2. **支付订单创建成功**: + - 不再出现证书null错误 + - 支付流程正常进行 + +3. **配置检查通过**: + ``` + GET /system/wechat-pay-diagnostic/check/10547 + 返回:配置检查通过 + ``` + +### 📞 技术支持 + +如果仍有问题,请: + +1. 使用诊断API获取详细信息 +2. 查看完整的错误日志 +3. 提供配置检查结果 +4. 联系技术支持团队 + +--- + +**总结**:通过使用公钥模式,可以彻底解决 `X509Certificate.getSerialNumber() null` 错误,提供更稳定可靠的微信支付服务。 diff --git a/docs/WECHAT_PAY_CERTIFICATE_FIX.md b/docs/WECHAT_PAY_CERTIFICATE_FIX.md new file mode 100644 index 0000000..1c6ccf2 --- /dev/null +++ b/docs/WECHAT_PAY_CERTIFICATE_FIX.md @@ -0,0 +1,165 @@ +# 微信支付证书问题修复指南 + +## 问题描述 + +错误信息:`创建支付订单失败:创建支付订单失败:Cannot invoke "java.security.cert.X509Certificate.getSerialNumber()" because "certificate" is null` + +## 问题原因 + +这个错误通常发生在使用微信支付SDK的 `RSAAutoCertificateConfig` 时,SDK尝试自动下载微信支付平台证书但失败,导致证书对象为null。 + +常见原因包括: +1. 商户平台未开启API安全功能 +2. 未申请使用微信支付公钥 +3. 网络连接问题 +4. 商户证书序列号错误 +5. APIv3密钥配置错误 + +## 解决方案 + +### 1. 商户平台配置 + +#### 步骤1:开启API安全功能 +1. 登录 [微信商户平台](https://pay.weixin.qq.com) +2. 进入【账户中心】->【API安全】 +3. 点击【申请使用微信支付公钥】 +4. 按照指引完成申请流程 + +#### 步骤2:下载证书文件 +1. 在API安全页面下载商户证书 +2. 获取以下文件: + - `apiclient_cert.pem` (商户证书) + - `apiclient_key.pem` (商户私钥) +3. 记录商户证书序列号 + +#### 步骤3:设置APIv3密钥 +1. 在API安全页面设置APIv3密钥 +2. 密钥必须是32位字符串(字母和数字组合) +3. 妥善保管密钥,不要泄露 + +### 2. 开发环境配置 + +#### 证书文件放置 +将证书文件放置到以下目录: +``` +src/main/resources/dev/wechat/{tenantId}/ +├── apiclient_key.pem # 必需:商户私钥 +└── apiclient_cert.pem # 可选:商户证书(自动配置不需要) +``` + +例如,租户ID为10550的证书路径: +``` +src/main/resources/dev/wechat/10550/ +├── apiclient_key.pem +└── apiclient_cert.pem +``` + +#### 数据库配置 +在 `payment` 表中配置以下信息: +- `mch_id`: 商户号 +- `app_id`: 应用ID +- `merchant_serial_number`: 商户证书序列号 +- `api_key`: APIv3密钥(32位) + +### 3. 生产环境配置 + +#### 证书文件上传 +将证书文件上传到服务器指定目录,通常是: +``` +/www/wwwroot/file.ws/file/{相对路径}/ +``` + +#### 数据库配置 +在 `payment` 表中配置证书文件的相对路径: +- `apiclient_key`: 私钥文件相对路径 +- `apiclient_cert`: 商户证书文件相对路径 + +### 4. 代码修复 + +系统已经实现了自动回退机制: + +1. **优先使用自动证书配置**:`RSAAutoCertificateConfig` +2. **自动回退到手动配置**:如果自动配置失败,会回退到 `RSAConfig` 或 `RSAPublicKeyConfig` +3. **详细错误诊断**:提供具体的错误信息和修复建议 + +### 5. 诊断工具 + +#### 使用证书诊断API +```bash +# 诊断特定租户的证书配置 +GET /system/wechat-pay-diagnostic/diagnose/{tenantId} + +# 获取解决方案 +GET /system/wechat-pay-diagnostic/solutions + +# 测试证书配置 +POST /system/wechat-pay-diagnostic/test/{tenantId} +``` + +#### 查看诊断日志 +在应用日志中查看详细的诊断信息: +``` +=== 微信支付证书诊断报告 === +租户ID: 10550 +商户号: 1723321338 +应用ID: wx1234567890abcdef +商户证书序列号: 2B933F7C35014A1C363642623E4A62364B34C4EB +APIv3密钥: 已配置(32位) +证书文件路径: dev/wechat/10550/apiclient_key.pem +证书文件存在: 是 +配置验证结果: 通过 +``` + +## 常见问题排查 + +### Q1: 404错误 +**原因**:商户平台未开启API安全功能 +**解决**:按照上述步骤1开启API安全功能 + +### Q2: 证书序列号不匹配 +**原因**:数据库中配置的序列号与实际证书不符 +**解决**: +1. 在商户平台查看正确的序列号 +2. 更新数据库中的 `merchant_serial_number` 字段 + +### Q3: APIv3密钥错误 +**原因**:密钥长度不是32位或包含非法字符 +**解决**: +1. 重新设置32位APIv3密钥 +2. 确保只包含字母和数字 + +### Q4: 私钥文件不存在 +**原因**:证书文件路径错误或文件未上传 +**解决**: +1. 检查文件路径是否正确 +2. 确保文件已正确放置 + +### Q5: 网络连接问题 +**原因**:服务器无法访问微信支付API +**解决**: +1. 检查网络连接 +2. 确保防火墙允许HTTPS出站连接 +3. 检查代理设置 + +## 最佳实践 + +1. **使用自动证书配置**:推荐使用 `RSAAutoCertificateConfig`,可自动管理平台证书 +2. **定期检查证书有效期**:避免证书过期导致的问题 +3. **妥善保管私钥**:确保私钥文件安全,不要泄露 +4. **使用HTTPS**:所有支付相关通信都应使用HTTPS +5. **监控日志**:定期查看支付相关日志,及时发现问题 + +## 技术支持 + +如果按照以上步骤仍无法解决问题,请: + +1. 查看完整的错误日志 +2. 使用诊断API获取详细信息 +3. 检查微信商户平台的配置状态 +4. 联系技术支持团队 + +## 相关文档 + +- [微信支付官方文档](https://pay.weixin.qq.com/doc/v3/merchant/4012153196) +- [API安全配置指南](https://pay.weixin.qq.com/doc/v3/merchant/4012153196) +- [证书和回调报文验证](https://pay.weixin.qq.com/doc/v3/wechatpay/wechatpay4_1.shtml) diff --git a/docs/WECHAT_PAY_PUBLIC_KEY_CONFIG.md b/docs/WECHAT_PAY_PUBLIC_KEY_CONFIG.md new file mode 100644 index 0000000..2708884 --- /dev/null +++ b/docs/WECHAT_PAY_PUBLIC_KEY_CONFIG.md @@ -0,0 +1,198 @@ +# 微信支付公钥模式配置指南 + +## 概述 + +如果您的后台系统使用了微信支付公钥模式,系统现在支持自动检测并优先使用公钥配置。这种模式比自动证书配置更稳定,不依赖网络下载平台证书。 + +## 配置步骤 + +### 1. 获取公钥文件 + +从微信商户平台下载或获取以下文件: +- 微信支付平台公钥文件(通常以 `.pem` 结尾) +- 公钥ID(一个字符串标识符) + +### 2. 放置公钥文件 + +将公钥文件放置到对应租户的证书目录: + +``` +src/main/resources/dev/wechat/{tenantId}/ +├── apiclient_key.pem # 商户私钥(必需) +├── apiclient_cert.pem # 商户证书(可选) +└── wechatpay_public_key.pem # 微信支付平台公钥(新增) +``` + +例如,租户ID为10547的目录结构: +``` +src/main/resources/dev/wechat/10547/ +├── apiclient_key.pem +├── apiclient_cert.pem +└── wechatpay_public_key.pem +``` + +### 3. 数据库配置 + +在 `sys_payment` 表中配置公钥相关字段: + +```sql +UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem', + pub_key_id = 'YOUR_PUBLIC_KEY_ID' +WHERE tenant_id = 10547 AND type = 0; +``` + +**字段说明**: +- `pub_key`: 公钥文件名 +- `pub_key_id`: 微信支付平台提供的公钥ID + +### 4. 验证配置 + +运行测试来验证配置是否正确: + +```bash +# 运行公钥配置测试 +mvn test -Dtest=WechatPayPublicKeyTest#testPublicKeyConfiguration +``` + +## 配置优先级 + +系统会按以下优先级选择配置方式: + +1. **RSA公钥配置**(最高优先级) + - 条件:数据库中配置了 `pub_key` 和 `pub_key_id` + - 优势:稳定、不依赖网络、配置简单 + +2. **RSA自动证书配置** + - 条件:公钥配置不可用时 + - 优势:自动管理平台证书 + - 劣势:依赖网络连接和商户平台API安全设置 + +3. **RSA手动证书配置**(回退方案) + - 条件:自动配置失败时 + - 需要:商户证书文件 + +## 示例配置 + +### 开发环境示例 + +**目录结构**: +``` +src/main/resources/dev/wechat/10547/ +├── apiclient_key.pem +├── apiclient_cert.pem +└── wechatpay_public_key.pem +``` + +**数据库配置**: +```sql +-- 查看当前配置 +SELECT id, tenant_id, mch_id, app_id, merchant_serial_number, + pub_key, pub_key_id, api_key +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; + +-- 更新公钥配置 +UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem', + pub_key_id = 'PUB_KEY_ID_0112422897022025011300326200001208' +WHERE tenant_id = 10547 AND type = 0; +``` + +### 生产环境示例 + +**证书文件路径**: +``` +/www/wwwroot/file.ws/file/wechat/10547/ +├── apiclient_key.pem +├── apiclient_cert.pem +└── wechatpay_public_key.pem +``` + +**数据库配置**: +```sql +UPDATE sys_payment SET + apiclient_key = '/wechat/10547/apiclient_key.pem', + apiclient_cert = '/wechat/10547/apiclient_cert.pem', + pub_key = '/wechat/10547/wechatpay_public_key.pem', + pub_key_id = 'PUB_KEY_ID_0112422897022025011300326200001208' +WHERE tenant_id = 10547 AND type = 0; +``` + +## 日志输出 + +配置成功后,您会在日志中看到: + +``` +=== 检测到公钥配置,使用RSA公钥模式 === +公钥文件: wechatpay_public_key.pem +公钥ID: PUB_KEY_ID_0112422897022025011300326200001208 +公钥文件路径: /path/to/wechatpay_public_key.pem +✅ 开发环境RSA公钥配置成功 +``` + +如果没有公钥配置,系统会尝试自动证书配置: + +``` +=== 尝试创建自动证书配置 === +商户号: 1723321338 +私钥路径: /path/to/apiclient_key.pem +序列号: 2B933F7C35014A1C363642623E4A62364B34C4EB +API密钥长度: 32 +``` + +## 故障排除 + +### 1. 公钥文件不存在 + +**错误信息**: +``` +❌ 公钥文件不存在: dev/wechat/10547/wechatpay_public_key.pem +``` + +**解决方案**: +- 检查文件路径是否正确 +- 确认文件已放置在正确位置 +- 验证文件名与数据库配置一致 + +### 2. 公钥ID错误 + +**错误信息**: +``` +❌ RSA公钥配置失败: Invalid public key ID +``` + +**解决方案**: +- 检查公钥ID是否正确 +- 确认公钥ID与公钥文件匹配 +- 联系微信支付技术支持获取正确的公钥ID + +### 3. 数据库配置缺失 + +**现象**:系统跳过公钥配置,直接尝试自动证书配置 + +**解决方案**: +```sql +-- 检查当前配置 +SELECT pub_key, pub_key_id FROM sys_payment WHERE tenant_id = 10547 AND type = 0; + +-- 如果字段为空,进行配置 +UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem', + pub_key_id = 'YOUR_PUBLIC_KEY_ID' +WHERE tenant_id = 10547 AND type = 0; +``` + +## 优势对比 + +| 配置方式 | 稳定性 | 网络依赖 | 配置复杂度 | 推荐度 | +|---------|--------|----------|------------|--------| +| RSA公钥配置 | 高 | 无 | 低 | ⭐⭐⭐⭐⭐ | +| RSA自动证书配置 | 中 | 高 | 低 | ⭐⭐⭐ | +| RSA手动证书配置 | 中 | 无 | 高 | ⭐⭐ | + +## 总结 + +使用公钥模式可以有效避免 `X509Certificate.getSerialNumber() null` 错误,因为它不依赖自动下载平台证书。建议优先使用此配置方式。 + +如果您已经有公钥文件和公钥ID,按照本指南配置后,系统会自动使用更稳定的公钥模式。 diff --git a/docs/fix_public_key_path.sql b/docs/fix_public_key_path.sql new file mode 100644 index 0000000..48128c8 --- /dev/null +++ b/docs/fix_public_key_path.sql @@ -0,0 +1,31 @@ +-- 修复租户10547的公钥路径配置 + +-- 1. 查看当前配置 +SELECT + id, + tenant_id, + mch_id, + pub_key, + pub_key_id +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; + +-- 2. 修复公钥路径配置 +UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem' +WHERE tenant_id = 10547 AND type = 0; + +-- 3. 验证修复结果 +SELECT + id, + tenant_id, + mch_id, + pub_key, + pub_key_id, + CASE + WHEN pub_key = 'wechatpay_public_key.pem' + THEN '✅ 路径已修复' + ELSE '❌ 路径仍有问题' + END AS path_status +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; diff --git a/docs/update_payment_public_key.sql b/docs/update_payment_public_key.sql new file mode 100644 index 0000000..a0b8a84 --- /dev/null +++ b/docs/update_payment_public_key.sql @@ -0,0 +1,63 @@ +-- 微信支付公钥配置SQL脚本 +-- 适用于租户ID: 10547 + +-- 1. 查看当前支付配置 +SELECT + id, + tenant_id, + mch_id, + app_id, + merchant_serial_number, + pub_key, + pub_key_id, + api_key, + apiclient_key, + apiclient_cert +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; + +-- 2. 更新公钥配置(请根据实际情况修改公钥ID) +UPDATE sys_payment SET + pub_key = 'wechatpay_public_key.pem', + pub_key_id = 'PUB_KEY_ID_0112422897022025011300326200001208' -- 请替换为实际的公钥ID +WHERE tenant_id = 10547 AND type = 0; + +-- 3. 验证更新结果 +SELECT + id, + tenant_id, + mch_id, + app_id, + merchant_serial_number, + pub_key, + pub_key_id, + CASE + WHEN pub_key IS NOT NULL AND pub_key != '' AND pub_key_id IS NOT NULL AND pub_key_id != '' + THEN '✅ 公钥配置完整' + ELSE '❌ 公钥配置不完整' + END AS config_status +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; + +-- 4. 如果需要清除公钥配置(回退到自动证书模式) +-- UPDATE sys_payment SET +-- pub_key = NULL, +-- pub_key_id = NULL +-- WHERE tenant_id = 10547 AND type = 0; + +-- 5. 检查所有租户的公钥配置状态 +SELECT + tenant_id, + mch_id, + CASE + WHEN pub_key IS NOT NULL AND pub_key != '' AND pub_key_id IS NOT NULL AND pub_key_id != '' + THEN '公钥模式' + WHEN merchant_serial_number IS NOT NULL AND merchant_serial_number != '' + THEN '自动证书模式' + ELSE '配置不完整' + END AS payment_mode, + pub_key, + pub_key_id +FROM sys_payment +WHERE type = 0 +ORDER BY tenant_id; diff --git a/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java b/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java new file mode 100644 index 0000000..627c86e --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java @@ -0,0 +1,319 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.utils.WechatPayCertificateDiagnostic; +import com.gxwebsoft.common.core.utils.WechatPayConfigChecker; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.system.entity.Payment; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 微信支付诊断控制器 + * 用于诊断和解决微信支付证书相关问题 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@RestController +@RequestMapping("/system/wechat-pay-diagnostic") +@Tag(name = "微信支付诊断", description = "微信支付证书诊断和问题排查") +public class WechatPayDiagnosticController extends com.gxwebsoft.common.core.web.BaseController { + + @Autowired + private WechatPayCertificateDiagnostic certificateDiagnostic; + + @Autowired + private com.gxwebsoft.common.core.service.PaymentCacheService paymentCacheService; + + @Autowired + private com.gxwebsoft.common.core.utils.WechatPayConfigChecker configChecker; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + @Operation(summary = "诊断租户微信支付证书配置") + @GetMapping("/diagnose/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> diagnoseTenantCertificate( + @Parameter(description = "租户ID", example = "10550") @PathVariable Integer tenantId) { + + try { + log.info("开始诊断租户 {} 的微信支付证书配置", tenantId); + + // 获取支付配置 (微信支付类型为0) + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("error", "支付配置不存在"); + return fail("租户 " + tenantId + " 的微信支付配置不存在", errorResponse); + } + + // 执行诊断 + WechatPayCertificateDiagnostic.DiagnosticResult result = + certificateDiagnostic.diagnoseCertificateConfig(payment, tenantId, activeProfile); + + Map response = new HashMap<>(); + response.put("tenantId", tenantId); + response.put("environment", activeProfile); + response.put("hasErrors", result.hasErrors()); + response.put("errors", result.getErrors()); + response.put("warnings", result.getWarnings()); + response.put("info", result.getInfo()); + response.put("recommendations", result.getRecommendations()); + response.put("fullReport", result.getFullReport()); + + if (result.hasErrors()) { + return fail("诊断发现问题", response); + } else { + return success("诊断完成", response); + } + + } catch (Exception e) { + log.error("诊断租户 {} 证书配置时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("诊断过程中发生异常: " + e.getMessage(), errorResponse); + } + } + + @Operation(summary = "获取证书问题解决方案") + @GetMapping("/solutions") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> getCertificateSolutions() { + Map solutions = new HashMap<>(); + + solutions.put("commonIssues", Map.of( + "X509Certificate.getSerialNumber() null", "证书对象为空,通常是自动证书配置失败导致", + "404错误", "商户平台未开启API安全功能或未申请使用微信支付公钥", + "证书序列号错误", "请检查商户平台中的证书序列号是否正确", + "APIv3密钥错误", "请确认APIv3密钥是否正确设置", + "私钥文件不存在", "请检查私钥文件路径是否正确", + "网络连接问题", "请检查网络连接是否正常" + )); + + solutions.put("stepByStepSolutions", Map.of( + "开启API安全", "登录微信商户平台 -> 账户中心 -> API安全 -> 申请使用微信支付公钥", + "获取证书序列号", "在API安全页面查看或重新下载证书", + "设置APIv3密钥", "在API安全页面设置APIv3密钥(32位字符串)", + "检查私钥文件", "确保apiclient_key.pem文件存在且路径正确", + "使用自动证书配置", "推荐使用RSAAutoCertificateConfig,可自动管理平台证书" + )); + + solutions.put("configurationAdvice", Map.of( + "开发环境", "证书文件放在 src/main/resources/dev/wechat/{tenantId}/ 目录下", + "生产环境", "证书文件放在 Docker 挂载卷或指定的文件系统路径", + "证书文件要求", "需要 apiclient_key.pem(私钥)和 apiclient_cert.pem(商户证书)", + "自动配置优势", "无需手动管理微信支付平台证书,自动下载和更新" + )); + + solutions.put("troubleshootingSteps", new String[]{ + "1. 检查商户平台是否已开启API安全功能", + "2. 确认已申请使用微信支付公钥", + "3. 验证商户证书序列号是否正确", + "4. 检查APIv3密钥是否为32位字符串", + "5. 确认私钥文件路径正确且文件存在", + "6. 测试网络连接是否正常", + "7. 查看详细错误日志进行进一步诊断" + }); + + return success("获取解决方案成功", solutions); + } + + @Operation(summary = "测试证书配置") + @PostMapping("/test/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:edit')") + public ApiResult> testCertificateConfig( + @Parameter(description = "租户ID", example = "10550") @PathVariable Integer tenantId) { + + try { + log.info("开始测试租户 {} 的证书配置", tenantId); + + // 获取支付配置 (微信支付类型为0) + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("error", "支付配置不存在"); + return fail("租户 " + tenantId + " 的微信支付配置不存在", errorResponse); + } + + // 执行诊断 + WechatPayCertificateDiagnostic.DiagnosticResult result = + certificateDiagnostic.diagnoseCertificateConfig(payment, tenantId, activeProfile); + + Map testResult = new HashMap<>(); + testResult.put("tenantId", tenantId); + testResult.put("configurationValid", !result.hasErrors()); + testResult.put("testTime", System.currentTimeMillis()); + testResult.put("environment", activeProfile); + + if (result.hasErrors()) { + testResult.put("status", "FAILED"); + testResult.put("errors", result.getErrors()); + testResult.put("recommendations", result.getRecommendations()); + return fail("证书配置测试失败", testResult); + } else { + testResult.put("status", "SUCCESS"); + testResult.put("message", "证书配置正常"); + return success("证书配置测试通过", testResult); + } + + } catch (Exception e) { + log.error("测试租户 {} 证书配置时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("测试过程中发生异常: " + e.getMessage(), errorResponse); + } + } + + @Operation(summary = "获取环境信息") + @GetMapping("/environment") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> getEnvironmentInfo() { + Map envInfo = new HashMap<>(); + envInfo.put("activeProfile", activeProfile); + envInfo.put("javaVersion", System.getProperty("java.version")); + envInfo.put("osName", System.getProperty("os.name")); + envInfo.put("userDir", System.getProperty("user.dir")); + envInfo.put("timestamp", System.currentTimeMillis()); + + return success("获取环境信息成功", envInfo); + } + + @Operation(summary = "获取证书配置指南") + @GetMapping("/guide") + public ApiResult> getCertificateGuide() { + Map guide = new HashMap<>(); + + guide.put("overview", "微信支付证书配置完整指南"); + + guide.put("prerequisites", new String[]{ + "1. 拥有微信支付商户账号", + "2. 已完成商户入驻和资质审核", + "3. 具备开发者权限" + }); + + guide.put("merchantPlatformSteps", new String[]{ + "1. 登录微信商户平台 (pay.weixin.qq.com)", + "2. 进入【账户中心】->【API安全】", + "3. 点击【申请使用微信支付公钥】", + "4. 下载商户证书(apiclient_cert.pem 和 apiclient_key.pem)", + "5. 设置APIv3密钥(32位字符串)", + "6. 记录商户证书序列号" + }); + + guide.put("developmentSetup", new String[]{ + "1. 在项目中创建证书目录:src/main/resources/dev/wechat/{tenantId}/", + "2. 将 apiclient_key.pem 放入该目录", + "3. 将 apiclient_cert.pem 放入该目录(可选,自动配置不需要)", + "4. 在数据库中配置支付信息:商户号、应用ID、证书序列号、APIv3密钥" + }); + + guide.put("productionSetup", new String[]{ + "1. 将证书文件上传到服务器指定目录", + "2. 确保应用有读取证书文件的权限", + "3. 在数据库中配置正确的证书文件路径", + "4. 测试证书配置是否正常" + }); + + guide.put("bestPractices", new String[]{ + "1. 使用RSAAutoCertificateConfig自动证书配置", + "2. 定期检查证书有效期", + "3. 妥善保管私钥文件", + "4. 使用HTTPS传输敏感信息", + "5. 定期更新微信支付SDK版本" + }); + + return success("获取配置指南成功", guide); + } + + @Operation(summary = "快速检查租户配置状态") + @GetMapping("/check/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> quickCheckConfig( + @Parameter(description = "租户ID", example = "10547") @PathVariable Integer tenantId) { + + try { + log.info("快速检查租户 {} 的配置状态", tenantId); + + WechatPayConfigChecker.ConfigStatus status = configChecker.checkTenantConfig(tenantId); + + Map response = new HashMap<>(); + response.put("tenantId", status.tenantId); + response.put("environment", status.environment); + response.put("configMode", status.configMode); + response.put("configComplete", status.configComplete); + response.put("hasError", status.hasError); + response.put("errorMessage", status.errorMessage); + response.put("recommendation", status.recommendation); + response.put("issues", status.issues); + + // 详细配置信息 + Map configDetails = new HashMap<>(); + configDetails.put("merchantId", status.merchantId); + configDetails.put("appId", status.appId); + configDetails.put("serialNumber", status.serialNumber); + configDetails.put("hasApiKey", status.hasApiKey); + configDetails.put("apiKeyLength", status.apiKeyLength); + configDetails.put("hasPublicKey", status.hasPublicKey); + configDetails.put("publicKeyFile", status.publicKeyFile); + configDetails.put("publicKeyId", status.publicKeyId); + configDetails.put("publicKeyExists", status.publicKeyExists); + configDetails.put("privateKeyExists", status.privateKeyExists); + configDetails.put("merchantCertExists", status.merchantCertExists); + response.put("configDetails", configDetails); + + if (status.hasError || !status.configComplete) { + return fail("配置检查发现问题", response); + } else { + return success("配置检查通过", response); + } + + } catch (Exception e) { + log.error("检查租户 {} 配置状态时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("检查过程中发生异常: " + e.getMessage(), errorResponse); + } + } + + @Operation(summary = "获取配置建议") + @GetMapping("/advice/{tenantId}") + @PreAuthorize("hasAuthority('system:payment:view')") + public ApiResult> getConfigAdvice( + @Parameter(description = "租户ID", example = "10547") @PathVariable Integer tenantId) { + + try { + String advice = configChecker.generateConfigAdvice(tenantId); + + Map response = new HashMap<>(); + response.put("tenantId", tenantId); + response.put("advice", advice); + response.put("timestamp", System.currentTimeMillis()); + + return success("获取配置建议成功", response); + + } catch (Exception e) { + log.error("获取租户 {} 配置建议时发生异常", tenantId, e); + Map errorResponse = new HashMap<>(); + errorResponse.put("tenantId", tenantId); + errorResponse.put("exception", e.getMessage()); + return fail("获取建议过程中发生异常: " + e.getMessage(), errorResponse); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java new file mode 100644 index 0000000..ebe8189 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateDiagnostic.java @@ -0,0 +1,314 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; + + +/** + * 微信支付证书诊断工具 + * 专门用于诊断和解决证书相关问题 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@Component +public class WechatPayCertificateDiagnostic { + + private final CertificateProperties certConfig; + private final CertificateLoader certificateLoader; + + public WechatPayCertificateDiagnostic(CertificateProperties certConfig, CertificateLoader certificateLoader) { + this.certConfig = certConfig; + this.certificateLoader = certificateLoader; + } + + /** + * 全面诊断微信支付证书配置 + * + * @param payment 支付配置 + * @param tenantId 租户ID + * @param environment 环境(dev/prod) + * @return 诊断结果 + */ + public DiagnosticResult diagnoseCertificateConfig(Payment payment, Integer tenantId, String environment) { + DiagnosticResult result = new DiagnosticResult(); + + log.info("=== 开始微信支付证书诊断 ==="); + log.info("租户ID: {}, 环境: {}", tenantId, environment); + + try { + // 1. 检查基本配置 + checkBasicConfig(payment, result); + + // 2. 检查证书文件 + checkCertificateFiles(payment, tenantId, environment, result); + + // 3. 检查证书内容 + validateCertificateContent(payment, tenantId, environment, result); + + // 4. 生成建议 + generateRecommendations(result); + + } catch (Exception e) { + result.addError("诊断过程中发生异常: " + e.getMessage()); + log.error("证书诊断异常", e); + } + + log.info("=== 证书诊断完成 ==="); + return result; + } + + /** + * 检查基本配置 + */ + private void checkBasicConfig(Payment payment, DiagnosticResult result) { + if (payment == null) { + result.addError("支付配置为空"); + return; + } + + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + result.addError("商户号未配置"); + } else { + result.addInfo("商户号: " + payment.getMchId()); + } + + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + result.addError("应用ID未配置"); + } else { + result.addInfo("应用ID: " + payment.getAppId()); + } + + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + result.addError("商户证书序列号未配置"); + } else { + result.addInfo("商户证书序列号: " + payment.getMerchantSerialNumber()); + } + + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + result.addWarning("数据库中APIv3密钥未配置,将使用配置文件默认值"); + } else { + result.addInfo("APIv3密钥: 已配置(" + payment.getApiKey().length() + "位)"); + } + } + + /** + * 检查证书文件 + */ + private void checkCertificateFiles(Payment payment, Integer tenantId, String environment, DiagnosticResult result) { + if ("dev".equals(environment)) { + // 开发环境证书检查 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + // 检查私钥文件 + if (certificateLoader.certificateExists(privateKeyPath)) { + result.addInfo("✅ 私钥文件存在: " + privateKeyPath); + try { + String privateKeyFile = certificateLoader.loadCertificatePath(privateKeyPath); + result.addInfo("私钥文件路径: " + privateKeyFile); + } catch (Exception e) { + result.addError("私钥文件加载失败: " + e.getMessage()); + } + } else { + result.addError("❌ 私钥文件不存在: " + privateKeyPath); + } + + // 检查商户证书文件 + if (certificateLoader.certificateExists(apiclientCertPath)) { + result.addInfo("✅ 商户证书文件存在: " + apiclientCertPath); + } else { + result.addWarning("⚠️ 商户证书文件不存在: " + apiclientCertPath + " (自动证书配置不需要此文件)"); + } + + } else { + // 生产环境证书检查 + if (payment.getApiclientKey() != null) { + result.addInfo("私钥文件配置: " + payment.getApiclientKey()); + } else { + result.addError("生产环境私钥文件路径未配置"); + } + + if (payment.getApiclientCert() != null) { + result.addInfo("商户证书文件配置: " + payment.getApiclientCert()); + } else { + result.addWarning("生产环境商户证书文件路径未配置 (自动证书配置不需要此文件)"); + } + } + } + + /** + * 验证证书内容 + */ + private void validateCertificateContent(Payment payment, Integer tenantId, String environment, DiagnosticResult result) { + try { + if ("dev".equals(environment)) { + String tenantCertPath = "dev/wechat/" + tenantId; + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + if (certificateLoader.certificateExists(apiclientCertPath)) { + validateX509Certificate(apiclientCertPath, payment.getMerchantSerialNumber(), result); + } + } + } catch (Exception e) { + result.addWarning("证书内容验证失败: " + e.getMessage()); + } + } + + /** + * 验证X509证书 + */ + private void validateX509Certificate(String certPath, String expectedSerialNumber, DiagnosticResult result) { + try { + String actualCertPath = certificateLoader.loadCertificatePath(certPath); + + try (InputStream inputStream = new FileInputStream(new File(actualCertPath))) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + + if (cert != null) { + String actualSerialNumber = cert.getSerialNumber().toString(16).toUpperCase(); + result.addInfo("证书序列号: " + actualSerialNumber); + result.addInfo("证书有效期: " + cert.getNotBefore() + " 至 " + cert.getNotAfter()); + result.addInfo("证书主体: " + cert.getSubjectX500Principal().toString()); + + // 检查序列号是否匹配 + if (expectedSerialNumber != null && !expectedSerialNumber.equalsIgnoreCase(actualSerialNumber)) { + result.addError("证书序列号不匹配! 配置: " + expectedSerialNumber + ", 实际: " + actualSerialNumber); + } else { + result.addInfo("✅ 证书序列号匹配"); + } + + // 检查证书是否过期 + long now = System.currentTimeMillis(); + if (now < cert.getNotBefore().getTime()) { + result.addError("证书尚未生效"); + } else if (now > cert.getNotAfter().getTime()) { + result.addError("证书已过期"); + } else { + result.addInfo("✅ 证书在有效期内"); + } + } else { + result.addError("无法解析证书文件"); + } + } + } catch (Exception e) { + result.addError("证书验证失败: " + e.getMessage()); + } + } + + /** + * 生成建议 + */ + private void generateRecommendations(DiagnosticResult result) { + if (result.hasErrors()) { + result.addRecommendation("🔧 修复建议:"); + + String errorText = result.getErrors(); + if (errorText.contains("商户号")) { + result.addRecommendation("1. 请在支付配置中设置正确的商户号"); + } + + if (errorText.contains("序列号")) { + result.addRecommendation("2. 请检查商户证书序列号是否正确,可在微信商户平台查看"); + } + + if (errorText.contains("证书文件")) { + result.addRecommendation("3. 请确保证书文件已正确放置在指定目录"); + } + + if (errorText.contains("过期")) { + result.addRecommendation("4. 请更新过期的证书文件"); + } + + result.addRecommendation("5. 建议使用RSAAutoCertificateConfig自动证书配置,可避免手动管理证书"); + result.addRecommendation("6. 确保在微信商户平台开启API安全功能并申请使用微信支付公钥"); + } else { + result.addRecommendation("✅ 证书配置正常,建议使用自动证书配置以获得最佳体验"); + } + } + + /** + * 诊断结果类 + */ + public static class DiagnosticResult { + private final StringBuilder errors = new StringBuilder(); + private final StringBuilder warnings = new StringBuilder(); + private final StringBuilder info = new StringBuilder(); + private final StringBuilder recommendations = new StringBuilder(); + + public void addError(String error) { + if (errors.length() > 0) errors.append("\n"); + errors.append(error); + } + + public void addWarning(String warning) { + if (warnings.length() > 0) warnings.append("\n"); + warnings.append(warning); + } + + public void addInfo(String information) { + if (info.length() > 0) info.append("\n"); + info.append(information); + } + + public void addRecommendation(String recommendation) { + if (recommendations.length() > 0) recommendations.append("\n"); + recommendations.append(recommendation); + } + + public boolean hasErrors() { + return errors.length() > 0; + } + + public String getErrors() { + return errors.toString(); + } + + public String getWarnings() { + return warnings.toString(); + } + + public String getInfo() { + return info.toString(); + } + + public String getRecommendations() { + return recommendations.toString(); + } + + public String getFullReport() { + StringBuilder report = new StringBuilder(); + report.append("=== 微信支付证书诊断报告 ===\n\n"); + + if (info.length() > 0) { + report.append("📋 基本信息:\n").append(info).append("\n\n"); + } + + if (warnings.length() > 0) { + report.append("⚠️ 警告:\n").append(warnings).append("\n\n"); + } + + if (errors.length() > 0) { + report.append("❌ 错误:\n").append(errors).append("\n\n"); + } + + if (recommendations.length() > 0) { + report.append("💡 建议:\n").append(recommendations).append("\n\n"); + } + + report.append("=== 诊断报告结束 ==="); + return report.toString(); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java new file mode 100644 index 0000000..af6e357 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayCertificateFixer.java @@ -0,0 +1,312 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +/** + * 微信支付证书修复工具 + * 自动检测和修复常见的证书配置问题 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@Component +public class WechatPayCertificateFixer { + + private final CertificateProperties certConfig; + private final CertificateLoader certificateLoader; + + public WechatPayCertificateFixer(CertificateProperties certConfig, CertificateLoader certificateLoader) { + this.certConfig = certConfig; + this.certificateLoader = certificateLoader; + } + + /** + * 自动修复证书配置问题 + * + * @param payment 支付配置 + * @param tenantId 租户ID + * @param environment 环境 + * @return 修复结果 + */ + public FixResult autoFixCertificateIssues(Payment payment, Integer tenantId, String environment) { + FixResult result = new FixResult(); + + log.info("开始自动修复租户 {} 的证书配置问题", tenantId); + + try { + // 1. 检查并修复基本配置 + fixBasicConfiguration(payment, result); + + // 2. 检查并修复证书文件问题 + fixCertificateFiles(payment, tenantId, environment, result); + + // 3. 检查并修复序列号问题 + fixSerialNumberIssues(payment, tenantId, environment, result); + + // 4. 生成修复建议 + generateFixRecommendations(result); + + } catch (Exception e) { + result.addError("修复过程中发生异常: " + e.getMessage()); + log.error("证书修复异常", e); + } + + log.info("证书配置修复完成,成功修复 {} 个问题", result.getFixedIssues().size()); + return result; + } + + /** + * 修复基本配置问题 + */ + private void fixBasicConfiguration(Payment payment, FixResult result) { + if (payment == null) { + result.addError("支付配置为空,无法修复"); + return; + } + + // 检查商户号 + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + result.addError("商户号未配置,需要手动设置"); + } + + // 检查应用ID + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + result.addError("应用ID未配置,需要手动设置"); + } + + // 检查APIv3密钥 + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + result.addWarning("APIv3密钥未配置,将使用配置文件默认值"); + } else if (payment.getApiKey().length() != 32) { + result.addError("APIv3密钥长度错误,应为32位,实际为: " + payment.getApiKey().length()); + } + + // 检查商户证书序列号 + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + result.addError("商户证书序列号未配置,需要手动设置"); + } + } + + /** + * 修复证书文件问题 + */ + private void fixCertificateFiles(Payment payment, Integer tenantId, String environment, FixResult result) { + if ("dev".equals(environment)) { + fixDevCertificateFiles(tenantId, result); + } else { + fixProdCertificateFiles(payment, result); + } + } + + /** + * 修复开发环境证书文件问题 + */ + private void fixDevCertificateFiles(Integer tenantId, FixResult result) { + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + // 检查私钥文件 + if (!certificateLoader.certificateExists(privateKeyPath)) { + result.addError("私钥文件不存在: " + privateKeyPath); + result.addRecommendation("请将 apiclient_key.pem 文件放置到 src/main/resources/" + privateKeyPath); + } else { + result.addFixed("私钥文件存在: " + privateKeyPath); + + // 尝试加载私钥文件 + try { + String privateKeyFile = certificateLoader.loadCertificatePath(privateKeyPath); + result.addFixed("私钥文件加载成功: " + privateKeyFile); + } catch (Exception e) { + result.addError("私钥文件加载失败: " + e.getMessage()); + } + } + + // 检查商户证书文件(可选) + if (!certificateLoader.certificateExists(apiclientCertPath)) { + result.addWarning("商户证书文件不存在: " + apiclientCertPath + " (自动证书配置不需要此文件)"); + } else { + result.addFixed("商户证书文件存在: " + apiclientCertPath); + } + } + + /** + * 修复生产环境证书文件问题 + */ + private void fixProdCertificateFiles(Payment payment, FixResult result) { + if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) { + result.addError("生产环境私钥文件路径未配置"); + } else { + result.addFixed("生产环境私钥文件路径已配置: " + payment.getApiclientKey()); + } + + if (payment.getApiclientCert() == null || payment.getApiclientCert().trim().isEmpty()) { + result.addWarning("生产环境商户证书文件路径未配置 (自动证书配置不需要此文件)"); + } else { + result.addFixed("生产环境商户证书文件路径已配置: " + payment.getApiclientCert()); + } + } + + /** + * 修复序列号问题 + */ + private void fixSerialNumberIssues(Payment payment, Integer tenantId, String environment, FixResult result) { + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + result.addError("商户证书序列号未配置"); + return; + } + + // 在开发环境中,尝试从证书文件中提取序列号进行验证 + if ("dev".equals(environment)) { + try { + String tenantCertPath = "dev/wechat/" + tenantId; + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + if (certificateLoader.certificateExists(apiclientCertPath)) { + String actualCertPath = certificateLoader.loadCertificatePath(apiclientCertPath); + + try (InputStream inputStream = new FileInputStream(new File(actualCertPath))) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); + + if (cert != null) { + String actualSerialNumber = cert.getSerialNumber().toString(16).toUpperCase(); + String configuredSerialNumber = payment.getMerchantSerialNumber(); + + if (!configuredSerialNumber.equalsIgnoreCase(actualSerialNumber)) { + result.addError("证书序列号不匹配! 配置: " + configuredSerialNumber + ", 实际: " + actualSerialNumber); + result.addRecommendation("建议将商户证书序列号更新为: " + actualSerialNumber); + } else { + result.addFixed("证书序列号匹配: " + actualSerialNumber); + } + } + } + } + } catch (Exception e) { + result.addWarning("无法验证证书序列号: " + e.getMessage()); + } + } + } + + /** + * 生成修复建议 + */ + private void generateFixRecommendations(FixResult result) { + if (result.hasErrors()) { + result.addRecommendation("=== 修复建议 ==="); + + if (result.getErrors().stream().anyMatch(e -> e.contains("商户号"))) { + result.addRecommendation("1. 请在微信商户平台获取商户号并在系统中配置"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("应用ID"))) { + result.addRecommendation("2. 请在微信开放平台获取应用ID并在系统中配置"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("APIv3密钥"))) { + result.addRecommendation("3. 请在微信商户平台设置32位APIv3密钥"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("证书序列号"))) { + result.addRecommendation("4. 请在微信商户平台查看正确的商户证书序列号"); + } + + if (result.getErrors().stream().anyMatch(e -> e.contains("私钥文件"))) { + result.addRecommendation("5. 请从微信商户平台下载私钥文件并放置到正确位置"); + } + + result.addRecommendation("6. 建议使用RSAAutoCertificateConfig自动证书配置"); + result.addRecommendation("7. 确保在微信商户平台开启API安全功能"); + } + } + + /** + * 修复结果类 + */ + public static class FixResult { + private final List errors = new ArrayList<>(); + private final List warnings = new ArrayList<>(); + private final List fixedIssues = new ArrayList<>(); + private final List recommendations = new ArrayList<>(); + + public void addError(String error) { + errors.add(error); + } + + public void addWarning(String warning) { + warnings.add(warning); + } + + public void addFixed(String fixed) { + fixedIssues.add(fixed); + } + + public void addRecommendation(String recommendation) { + recommendations.add(recommendation); + } + + public boolean hasErrors() { + return !errors.isEmpty(); + } + + public List getErrors() { + return errors; + } + + public List getWarnings() { + return warnings; + } + + public List getFixedIssues() { + return fixedIssues; + } + + public List getRecommendations() { + return recommendations; + } + + public String getFullReport() { + StringBuilder report = new StringBuilder(); + report.append("=== 微信支付证书修复报告 ===\n\n"); + + if (!fixedIssues.isEmpty()) { + report.append("✅ 已修复的问题:\n"); + fixedIssues.forEach(issue -> report.append(" - ").append(issue).append("\n")); + report.append("\n"); + } + + if (!warnings.isEmpty()) { + report.append("⚠️ 警告:\n"); + warnings.forEach(warning -> report.append(" - ").append(warning).append("\n")); + report.append("\n"); + } + + if (!errors.isEmpty()) { + report.append("❌ 需要手动修复的问题:\n"); + errors.forEach(error -> report.append(" - ").append(error).append("\n")); + report.append("\n"); + } + + if (!recommendations.isEmpty()) { + report.append("💡 修复建议:\n"); + recommendations.forEach(rec -> report.append(" ").append(rec).append("\n")); + report.append("\n"); + } + + report.append("=== 修复报告结束 ==="); + return report.toString(); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java new file mode 100644 index 0000000..74a6fa4 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/utils/WechatPayConfigChecker.java @@ -0,0 +1,243 @@ +package com.gxwebsoft.common.core.utils; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.system.entity.Payment; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * 微信支付配置检查器 + * 用于快速检查和验证微信支付配置状态 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@Slf4j +@Component +public class WechatPayConfigChecker { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private CertificateLoader certificateLoader; + + @Autowired + private CertificateProperties certConfig; + + @Value("${spring.profiles.active:dev}") + private String activeProfile; + + /** + * 检查租户的微信支付配置状态 + * + * @param tenantId 租户ID + * @return 配置状态报告 + */ + public ConfigStatus checkTenantConfig(Integer tenantId) { + ConfigStatus status = new ConfigStatus(); + status.tenantId = tenantId; + status.environment = activeProfile; + + try { + // 获取支付配置 + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + status.hasError = true; + status.errorMessage = "支付配置不存在"; + return status; + } + + status.merchantId = payment.getMchId(); + status.appId = payment.getAppId(); + status.serialNumber = payment.getMerchantSerialNumber(); + status.hasApiKey = payment.getApiKey() != null && !payment.getApiKey().isEmpty(); + status.apiKeyLength = payment.getApiKey() != null ? payment.getApiKey().length() : 0; + + // 检查公钥配置 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + + status.hasPublicKey = true; + status.publicKeyFile = payment.getPubKey(); + status.publicKeyId = payment.getPubKeyId(); + status.configMode = "公钥模式"; + + // 检查公钥文件是否存在 + String tenantCertPath = "dev/wechat/" + tenantId; + String pubKeyPath = tenantCertPath + "/" + payment.getPubKey(); + status.publicKeyExists = certificateLoader.certificateExists(pubKeyPath); + + } else { + status.hasPublicKey = false; + status.configMode = "自动证书模式"; + } + + // 检查私钥文件 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + status.privateKeyExists = certificateLoader.certificateExists(privateKeyPath); + + // 检查商户证书文件 + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + status.merchantCertExists = certificateLoader.certificateExists(apiclientCertPath); + + // 评估配置完整性 + evaluateConfigCompleteness(status); + + } catch (Exception e) { + status.hasError = true; + status.errorMessage = "检查配置时发生异常: " + e.getMessage(); + log.error("检查租户 {} 配置时发生异常", tenantId, e); + } + + return status; + } + + /** + * 评估配置完整性 + */ + private void evaluateConfigCompleteness(ConfigStatus status) { + if (status.hasError) { + return; + } + + // 基本配置检查 + if (status.merchantId == null || status.merchantId.isEmpty()) { + status.addIssue("商户号未配置"); + } + + if (status.appId == null || status.appId.isEmpty()) { + status.addIssue("应用ID未配置"); + } + + if (status.serialNumber == null || status.serialNumber.isEmpty()) { + status.addIssue("商户证书序列号未配置"); + } + + if (!status.hasApiKey) { + status.addIssue("APIv3密钥未配置"); + } else if (status.apiKeyLength != 32) { + status.addIssue("APIv3密钥长度错误,应为32位,实际为" + status.apiKeyLength + "位"); + } + + if (!status.privateKeyExists) { + status.addIssue("私钥文件不存在"); + } + + // 公钥模式特定检查 + if (status.hasPublicKey) { + if (!status.publicKeyExists) { + status.addIssue("公钥文件不存在"); + } + } + + // 设置配置状态 + if (status.issues.isEmpty()) { + status.configComplete = true; + status.recommendation = "✅ 配置完整,建议使用当前配置"; + } else { + status.configComplete = false; + if (status.hasPublicKey) { + status.recommendation = "⚠️ 公钥配置不完整,建议修复问题或回退到自动证书模式"; + } else { + status.recommendation = "⚠️ 自动证书配置不完整,建议配置公钥模式或修复当前问题"; + } + } + } + + /** + * 生成配置建议 + */ + public String generateConfigAdvice(Integer tenantId) { + ConfigStatus status = checkTenantConfig(tenantId); + + StringBuilder advice = new StringBuilder(); + advice.append("=== 租户 ").append(tenantId).append(" 微信支付配置建议 ===\n\n"); + + advice.append("当前配置模式: ").append(status.configMode).append("\n"); + advice.append("配置完整性: ").append(status.configComplete ? "完整" : "不完整").append("\n\n"); + + if (status.hasError) { + advice.append("❌ 错误: ").append(status.errorMessage).append("\n\n"); + return advice.toString(); + } + + // 基本信息 + advice.append("📋 基本信息:\n"); + advice.append(" 商户号: ").append(status.merchantId).append("\n"); + advice.append(" 应用ID: ").append(status.appId).append("\n"); + advice.append(" 序列号: ").append(status.serialNumber).append("\n"); + advice.append(" API密钥: ").append(status.hasApiKey ? "已配置(" + status.apiKeyLength + "位)" : "未配置").append("\n\n"); + + // 证书文件状态 + advice.append("📁 证书文件状态:\n"); + advice.append(" 私钥文件: ").append(status.privateKeyExists ? "✅ 存在" : "❌ 不存在").append("\n"); + advice.append(" 商户证书: ").append(status.merchantCertExists ? "✅ 存在" : "⚠️ 不存在").append("\n"); + + if (status.hasPublicKey) { + advice.append(" 公钥文件: ").append(status.publicKeyExists ? "✅ 存在" : "❌ 不存在").append("\n"); + advice.append(" 公钥ID: ").append(status.publicKeyId).append("\n"); + } + advice.append("\n"); + + // 问题列表 + if (!status.issues.isEmpty()) { + advice.append("⚠️ 发现的问题:\n"); + for (String issue : status.issues) { + advice.append(" - ").append(issue).append("\n"); + } + advice.append("\n"); + } + + // 建议 + advice.append("💡 建议:\n"); + advice.append(" ").append(status.recommendation).append("\n\n"); + + if (!status.hasPublicKey) { + advice.append("🔧 配置公钥模式的步骤:\n"); + advice.append(" 1. 获取微信支付平台公钥文件和公钥ID\n"); + advice.append(" 2. 将公钥文件放置到: src/main/resources/dev/wechat/").append(tenantId).append("/\n"); + advice.append(" 3. 执行SQL更新数据库配置:\n"); + advice.append(" UPDATE sys_payment SET \n"); + advice.append(" pub_key = 'wechatpay_public_key.pem',\n"); + advice.append(" pub_key_id = 'YOUR_PUBLIC_KEY_ID'\n"); + advice.append(" WHERE tenant_id = ").append(tenantId).append(" AND type = 0;\n\n"); + } + + advice.append("=== 配置建议结束 ==="); + return advice.toString(); + } + + /** + * 配置状态类 + */ + public static class ConfigStatus { + public Integer tenantId; + public String environment; + public String merchantId; + public String appId; + public String serialNumber; + public boolean hasApiKey; + public int apiKeyLength; + public boolean hasPublicKey; + public String publicKeyFile; + public String publicKeyId; + public boolean publicKeyExists; + public boolean privateKeyExists; + public boolean merchantCertExists; + public String configMode; + public boolean configComplete; + public boolean hasError; + public String errorMessage; + public String recommendation; + public java.util.List issues = new java.util.ArrayList<>(); + + public void addIssue(String issue) { + issues.add(issue); + } + } +} diff --git a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java index e104f52..2f3cf4e 100644 --- a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java @@ -10,6 +10,7 @@ import com.gxwebsoft.common.core.utils.CertificateLoader; import com.gxwebsoft.common.core.service.PaymentCacheService; import com.gxwebsoft.common.core.utils.WechatPayDiagnostic; + import com.gxwebsoft.common.core.utils.WechatPayCertificateDiagnostic; import com.gxwebsoft.common.system.entity.Payment; import lombok.extern.slf4j.Slf4j; import com.gxwebsoft.common.system.service.PaymentService; @@ -73,6 +74,8 @@ import com.gxwebsoft.common.core.service.PaymentCacheService; private WechatCertAutoConfig wechatCertAutoConfig; @Resource private WechatPayDiagnostic wechatPayDiagnostic; + @Resource + private WechatPayCertificateDiagnostic certificateDiagnostic; @Override public PageResult pageRel(ShopOrderParam param) { @@ -289,6 +292,12 @@ import com.gxwebsoft.common.core.service.PaymentCacheService; System.out.println("=== 运行微信支付配置诊断 ==="); wechatPayDiagnostic.diagnosePaymentConfig(payment, null, active); + // 运行证书诊断 + System.out.println("=== 运行证书诊断 ==="); + WechatPayCertificateDiagnostic.DiagnosticResult diagnosticResult = + certificateDiagnostic.diagnoseCertificateConfig(payment, order.getTenantId(), active); + System.out.println(diagnosticResult.getFullReport()); + // 开发环境配置 - 使用自动证书配置 if (active.equals("dev")) { // 构建包含租户号的证书路径: dev/wechat/{tenantId}/ @@ -377,67 +386,243 @@ import com.gxwebsoft.common.core.service.PaymentCacheService; System.out.println("=== 注意:使用RSA配置替代自动证书配置 ==="); System.out.println("原因:商户平台可能未开启API安全功能或未申请微信支付公钥"); - try { - config = wechatCertAutoConfig.createAutoConfig( - payment.getMchId(), - privateKey, - payment.getMerchantSerialNumber(), - payment.getApiKey() - ); - } catch (Exception e) { - System.err.println("自动证书配置失败: " + e.getMessage()); - System.err.println("请在微信支付商户平台完成以下配置:"); - System.err.println("1. 开启API安全功能"); - System.err.println("2. 申请使用微信支付公钥"); - System.err.println("3. 参考文档:https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); - throw new RuntimeException("微信支付配置失败,请先在商户平台开启API安全功能", e); + // 首先检查是否配置了公钥,如果有则优先使用公钥模式 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + + System.out.println("=== 检测到公钥配置,使用RSA公钥模式 ==="); + System.out.println("公钥文件: " + payment.getPubKey()); + System.out.println("公钥ID: " + payment.getPubKeyId()); + + try { + // 开发环境固定使用 wechatpay_public_key.pem + String tenantCertPath = "dev/wechat/" + order.getTenantId(); + String pubKeyPath = tenantCertPath + "/wechatpay_public_key.pem"; + + System.out.println("开发环境公钥文件路径: " + pubKeyPath); + + if (certificateLoader.certificateExists(pubKeyPath)) { + String pubKeyFile = certificateLoader.loadCertificatePath(pubKeyPath); + System.out.println("✅ 开发环境公钥文件加载成功: " + pubKeyFile); + + config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .publicKeyFromPath(pubKeyFile) + .publicKeyId(payment.getPubKeyId()) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + System.out.println("✅ 开发环境RSA公钥配置成功"); + } else { + System.err.println("❌ 公钥文件不存在: " + pubKeyPath); + throw new RuntimeException("公钥文件不存在: " + pubKeyPath); + } + } catch (Exception pubKeyException) { + System.err.println("❌ RSA公钥配置失败: " + pubKeyException.getMessage()); + pubKeyException.printStackTrace(); + throw new RuntimeException("RSA公钥配置失败: " + pubKeyException.getMessage(), pubKeyException); + } + } else { + // 没有公钥配置,尝试自动证书配置 + try { + System.out.println("=== 尝试创建自动证书配置 ==="); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("私钥路径: " + privateKey); + System.out.println("序列号: " + payment.getMerchantSerialNumber()); + System.out.println("API密钥长度: " + (payment.getApiKey() != null ? payment.getApiKey().length() : 0)); + + config = wechatCertAutoConfig.createAutoConfig( + payment.getMchId(), + privateKey, + payment.getMerchantSerialNumber(), + payment.getApiKey() + ); + System.out.println("✅ 开发环境自动证书配置成功"); + } catch (Exception e) { + System.err.println("❌ 自动证书配置失败: " + e.getMessage()); + System.err.println("错误类型: " + e.getClass().getName()); + e.printStackTrace(); + + // 检查是否是证书相关的错误 + if (e.getMessage() != null && ( + e.getMessage().contains("certificate") || + e.getMessage().contains("X509Certificate") || + e.getMessage().contains("getSerialNumber") || + e.getMessage().contains("404") || + e.getMessage().contains("API安全"))) { + + System.err.println("🔍 证书问题诊断:"); + System.err.println("1. 检查商户平台是否已开启API安全功能"); + System.err.println("2. 检查是否已申请使用微信支付公钥"); + System.err.println("3. 检查网络连接是否正常"); + System.err.println("4. 检查商户证书序列号是否正确"); + System.err.println("5. 参考文档:https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); + + // 开发环境回退到基础RSA配置 + System.err.println("⚠️ 开发环境回退到基础RSA配置..."); + try { + // 方案1:尝试使用RSA证书配置(需要商户证书文件) + String tenantCertPath = "dev/wechat/" + order.getTenantId(); + String apiclientCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + if (certificateLoader.certificateExists(apiclientCertPath)) { + String apiclientCertFile = certificateLoader.loadCertificatePath(apiclientCertPath); + System.out.println("方案1: 使用RSA证书配置作为回退方案"); + System.out.println("商户证书路径: " + apiclientCertFile); + + try { + config = new RSAConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .wechatPayCertificatesFromPath(apiclientCertFile) + .build(); + System.out.println("✅ 开发环境RSA证书配置成功"); + } catch (Exception rsaException) { + System.err.println("RSA证书配置失败: " + rsaException.getMessage()); + throw rsaException; + } + } else { + System.err.println("❌ 商户证书文件不存在: " + apiclientCertPath); + System.err.println("⚠️ 尝试方案2: 使用最小化配置..."); + + // 方案2:使用最小化的RSA配置(仅私钥和序列号) + try { + // 创建一个最基础的配置,不依赖平台证书 + config = new com.wechat.pay.java.core.RSAConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .build(); + System.out.println("✅ 开发环境最小化RSA配置成功"); + } catch (Exception minimalException) { + System.err.println("最小化配置也失败: " + minimalException.getMessage()); + throw new RuntimeException("所有配置方案都失败,请检查私钥文件和商户配置", e); + } + } + } catch (Exception fallbackException) { + System.err.println("❌ 手动证书配置失败: " + fallbackException.getMessage()); + fallbackException.printStackTrace(); + + // 最后的回退:抛出详细错误信息 + System.err.println("=== 最终错误诊断 ==="); + System.err.println("1. 自动证书配置失败原因: " + e.getMessage()); + System.err.println("2. 手动证书配置失败原因: " + fallbackException.getMessage()); + System.err.println("3. 建议解决方案:"); + System.err.println(" - 检查微信商户平台是否开启API安全功能"); + System.err.println(" - 确认已申请使用微信支付公钥"); + System.err.println(" - 验证商户证书序列号是否正确"); + System.err.println(" - 检查私钥文件是否完整且格式正确"); + + throw new RuntimeException("微信支付配置失败,请检查商户平台API安全设置和证书配置。原始错误: " + e.getMessage() + ", 回退错误: " + fallbackException.getMessage(), e); + } + } else { + throw new RuntimeException("微信支付配置失败:" + e.getMessage(), e); + } + } } } else { - // 生产环境优先使用自动证书配置 - System.out.println("=== 生产环境使用自动证书配置 ==="); - System.out.println("商户号: " + payment.getMchId()); - System.out.println("序列号: " + payment.getMerchantSerialNumber()); - System.out.println("API密钥: 已配置(长度:" + payment.getApiKey().length() + ")"); + // 生产环境也优先检查公钥配置 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { - try { - // 优先使用自动证书配置,避免证书过期和序列号不匹配问题 - config = wechatCertAutoConfig.createAutoConfig( - payment.getMchId(), - privateKey, - payment.getMerchantSerialNumber(), - payment.getApiKey() - ); - System.out.println("✅ 生产环境自动证书配置成功"); - } catch (Exception autoConfigException) { - System.err.println("⚠️ 自动证书配置失败,回退到手动证书配置"); - System.err.println("自动配置错误: " + autoConfigException.getMessage()); + System.out.println("=== 生产环境检测到公钥配置,使用RSA公钥模式 ==="); + System.out.println("公钥文件路径: " + payment.getPubKey()); + System.out.println("公钥ID: " + payment.getPubKeyId()); + + try { + // 生产环境直接使用数据库中存储的完整路径 + String pubKeyFile = certificateLoader.loadCertificatePath(payment.getPubKey()); + System.out.println("✅ 生产环境公钥文件加载成功: " + pubKeyFile); - // 回退到手动证书配置 - if (payment.getPubKey() != null && !payment.getPubKey().isEmpty()) { - System.out.println("使用RSA公钥配置"); config = new RSAPublicKeyConfig.Builder() .merchantId(payment.getMchId()) .privateKeyFromPath(privateKey) - .publicKeyFromPath(pubKey) + .publicKeyFromPath(pubKeyFile) .publicKeyId(payment.getPubKeyId()) .merchantSerialNumber(payment.getMerchantSerialNumber()) .apiV3Key(payment.getApiKey()) .build(); - } else { - System.out.println("使用RSA证书配置"); - config = new RSAConfig.Builder() - .merchantId(payment.getMchId()) - .privateKeyFromPath(privateKey) - .merchantSerialNumber(payment.getMerchantSerialNumber()) - .wechatPayCertificatesFromPath(apiclientCert) - .build(); + System.out.println("✅ 生产环境RSA公钥配置成功"); + } catch (Exception pubKeyException) { + System.err.println("❌ 生产环境RSA公钥配置失败: " + pubKeyException.getMessage()); + pubKeyException.printStackTrace(); + throw new RuntimeException("生产环境RSA公钥配置失败: " + pubKeyException.getMessage(), pubKeyException); + } + } else { + // 没有公钥配置,使用自动证书配置 + System.out.println("=== 生产环境使用自动证书配置 ==="); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("序列号: " + payment.getMerchantSerialNumber()); + System.out.println("API密钥: 已配置(长度:" + payment.getApiKey().length() + ")"); + + try { + // 优先使用自动证书配置,避免证书过期和序列号不匹配问题 + config = wechatCertAutoConfig.createAutoConfig( + payment.getMchId(), + privateKey, + payment.getMerchantSerialNumber(), + payment.getApiKey() + ); + System.out.println("✅ 生产环境自动证书配置成功"); + } catch (Exception autoConfigException) { + System.err.println("⚠️ 自动证书配置失败,回退到手动证书配置"); + System.err.println("自动配置错误: " + autoConfigException.getMessage()); + System.err.println("错误类型: " + autoConfigException.getClass().getName()); + autoConfigException.printStackTrace(); + + // 检查是否是证书相关的错误 + if (autoConfigException.getMessage() != null && ( + autoConfigException.getMessage().contains("certificate") || + autoConfigException.getMessage().contains("X509Certificate") || + autoConfigException.getMessage().contains("getSerialNumber") || + autoConfigException.getMessage().contains("404") || + autoConfigException.getMessage().contains("API安全"))) { + + System.err.println("🔍 生产环境证书问题诊断:"); + System.err.println("1. 检查商户平台是否已开启API安全功能"); + System.err.println("2. 检查是否已申请使用微信支付公钥"); + System.err.println("3. 检查网络连接是否正常"); + System.err.println("4. 检查商户证书序列号是否正确"); + } + + try { + // 回退到手动证书配置 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty()) { + System.out.println("使用RSA公钥配置作为回退方案"); + config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .publicKeyFromPath(pubKey) + .publicKeyId(payment.getPubKeyId()) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + System.out.println("✅ 生产环境RSA公钥配置成功"); + } else if (apiclientCert != null && !apiclientCert.isEmpty()) { + System.out.println("使用RSA证书配置作为回退方案"); + config = new RSAConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .wechatPayCertificatesFromPath(apiclientCert) + .build(); + System.out.println("✅ 生产环境RSA证书配置成功"); + } else { + throw new RuntimeException("生产环境缺少必要的证书文件,无法创建手动证书配置", autoConfigException); + } + } catch (Exception fallbackException) { + System.err.println("❌ 生产环境手动证书配置也失败: " + fallbackException.getMessage()); + throw new RuntimeException("生产环境微信支付配置失败,请检查商户平台API安全设置和证书配置", autoConfigException); } } + } } // 构建service return new JsapiServiceExtension.Builder().config(config).build(); - } catch (Exception e) { + } catch (Exception e) { System.err.println("=== 构建微信支付服务失败 ==="); System.err.println("错误信息: " + e.getMessage()); System.err.println("错误类型: " + e.getClass().getName()); diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 73f933f..6a12baa 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -53,7 +53,7 @@ mqtt: # 框架配置 config: # 开发环境接口 - server-url: http://127.0.0.1:9200/api + server-url: https://server.websoft.top/api upload-path: /Users/gxwebsoft/Documents/uploads/ # window(D:\Temp) # 开发环境证书配置 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 333a170..57406f0 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -44,7 +44,7 @@ mqtt: # 框架配置 config: # 生产环境接口 - server-url: https://server.s209.websoft.top/api + server-url: https://server.websoft.top/api upload-path: /www/wwwroot/file.ws/ # 阿里云OSS云存储 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c29ee92..4d94f34 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -101,7 +101,7 @@ config: swagger-version: 2.0 token-key: WLgNsWJ8rPjRtnjzX/Gx2RGS80Kwnm/ZeLbvIL+NrBs= # 主服务器 - server-url: https://server.s209.websoft.top/api + server-url: https://server.websoft.top/api # 文件服务器 file-server: https://file.wsdns.cn upload-path: /Users/gxwebsoft/Documents/uploads/ diff --git a/src/main/resources/dev/wechat/10398/01ac632fea184e248d0375e9917063a4.pem b/src/main/resources/dev/wechat/10398/01ac632fea184e248d0375e9917063a4.pem deleted file mode 100644 index a08bc93..0000000 --- a/src/main/resources/dev/wechat/10398/01ac632fea184e248d0375e9917063a4.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCUhkLLcEzSnxFF -hhjUXcwBuvdTypKWu/CFe0jQGgFnQVKIb0hkAR8YNiXE45zYn6sVgFY99NrtPjhD -xE0PrXky3i4EXxqeG/FgSnYm5/Uc2Q/euMRoTDZHDBMFMXanwStvJBmimFtMwYn5 -M0tSwxrhFZlKPYxO6QLsw8+ZicHHdX2bODbIw+zutEC8OEmQlyDVuiGFXONS5/kZ -WNlz5Jv5HgcnY637zCO2fg6lv0xFjlrITcnGK94alXoqc9hZ1MCuK7hsRDqk14WL -Z9ELb2GjHK33FydVPs7sLWX/pId0SPy5PstqyZH57sWWfj+StqBvoXAWqWEt6teZ -Jn/UaTB9AgMBAAECggEAb1Nvj5OeUaUfShBoXg3sU0O0DR9i3w8CCttMyYcklCO3 -XEKlbSgWCYzUpI7DSu/rSdOHUStOSdOAUvM5m824cbNtpKMwjWB+fWFyzFjDNhtR -NO0jctXlPT3Ep/jaaoV1K/pQKLqwfIj5BUw4YlGRvTL2Ulpt59vp8FQZMIm8MOcw -rNwYcUbMPaNKk4q3GF0LGvzW8k+S6wAWcAbx1KINRsLE0127o+shjGIlBiZgMJGZ -nTMz4xdvVbojsMhdM8aEhq6GtmSHgBFKjETQPXiOjRDCGOM5yC/9R/9WsMGJmJ4m -6Ec/RM4k9TZlnMZFsOZYO8S/kM+xgQUcAD8uGT1UgQKBgQDDGVZiqsDjudFcRkG/ -5pJN9PAC/Dk0Wzt6uRPZIhyFo2tDC/uL210Z5QR4hhB2nUSK8ANfAnepTotNzPHO -DC/sO2NzLuZz5EZTLeg9ij9BZDK+0/6AiBT2XdBKR/uGZAffjFCDh+ujm44lbrRK -7MUb9LtvDjPru1WVR0WhpFIwXQKBgQDC4xTQv6x3cPSW2SEglLVrl9CA68yO1g4T -MphCav64Cl9UDk1ov5C2SCvshFbWlIBv2g7tqb/bUk8nj42GuZWBu1spkUt2y7HS -eO89BmnaRNkVtWT8GtSMYYrYYAd23IGiOHPQqMnw/6HXkpjonpBa9c9CfEPwNtdq -84pgqed+oQKBgC6rV/PAPuX6pC87iyzZffPx/JvqM9DnZgIEVdAiDcqV/emK60BY -WBwCoaAnCbcmBahqo5PNpkw0wrP4q3sLhUcwKaj69huQ5pWtLJnUAS+mRVFKqt2a -L9GDPXkXYP6T3SJHkVb1Y5O+eTFRGwW1P61hTJjTP+5K4L0V0H1LLnHtAoGAEDBU -1lJVvUZAyxcWTWKM/3cI9uyffW4ClU2qoDnLFvalnJHjlEP1fW7ZVzhXDlQfpyrx -+oQTT+CyepLOKtbXuIMbu4Q6RI//IYCyPtt9h4gYkFkVHmwMI+0mX3r6o8EFc7hE -xpx+yeoyQ3oGAazKSQQKR3eTHS0xD81TPVxfwoECgYEAvBi3fPvIQ08pxk6kxj+S -bypHo06JHT1Fi8pmKtKCGLduK85dCeBZqHmsorWC/qg4RgCFWFFKfrFTGTxC4nf8 -MRQHmKxq+SAh4SvFgRDA0lyaUWmw7H/JpolbBDIGnXhoDI0CmQU3s2xsQdJnNPIL -azgaJXtOu+wr1MPR7Ij5OTU= ------END PRIVATE KEY----- diff --git a/src/main/resources/dev/wechat/10398/bac91dfb3ef143328dde489004c6d002.pem b/src/main/resources/dev/wechat/10398/bac91dfb3ef143328dde489004c6d002.pem deleted file mode 100644 index 50afe22..0000000 --- a/src/main/resources/dev/wechat/10398/bac91dfb3ef143328dde489004c6d002.pem +++ /dev/null @@ -1,24 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEFDCCAvygAwIBAgIUSjIxWE6Ttq53ggB00H6t6sy34iMwDQYJKoZIhvcNAQEL -BQAwXjELMAkGA1UEBhMCQ04xEzARBgNVBAoTClRlbnBheS5jb20xHTAbBgNVBAsT -FFRlbnBheS5jb20gQ0EgQ2VudGVyMRswGQYDVQQDExJUZW5wYXkuY29tIFJvb3Qg -Q0EwHhcNMjMwOTE5MTUxNTQ4WhcNMjgwOTE3MTUxNTQ4WjBuMRgwFgYDVQQDDA9U -ZW5wYXkuY29tIHNpZ24xEzARBgNVBAoMClRlbnBheS5jb20xHTAbBgNVBAsMFFRl -bnBheS5jb20gQ0EgQ2VudGVyMQswCQYDVQQGDAJDTjERMA8GA1UEBwwIU2hlblpo -ZW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5AOeoNuRReVPfOodE -TTE/wq96OK/7MI1lkwgtRlOIqwFGbGPxPx+wiLweWOWAeGrd0h+YuktIZezLhMhB -8pkJBzxz4U+zoQ9n3yY4CgDUBeAO8eNHhEQTTOTFUIvIlRxyr0EVuTHNP7pIkxA6 -gUvajWjJFbvU393vDHA9RflWrBNw1qVjkPPUx6axizD3wS8f77bwuspEWGTza/pS -g96HWUwFh9OSlQNPOjPG4km2YRQwGhoRMlLeZsUB+a0PtV0HI/B0TbY5u2EmPt0R -uNZLmcwImtiANYmySww6gp5uo4+im0P/kHKynPrW1SkS+8M9JP5T7Xck3bnHNv4S -9UOPAgMBAAGjgbkwgbYwCQYDVR0TBAIwADALBgNVHQ8EBAMCA/gwgZsGA1UdHwSB -kzCBkDCBjaCBiqCBh4aBhGh0dHA6Ly9ldmNhLml0cnVzLmNvbS5jbi9wdWJsaWMv -aXRydXNjcmw/Q0E9MUJENDIyMEU1MERCQzA0QjA2QUQzOTc1NDk4NDZDMDFDM0U4 -RUJEMiZzZz1IQUNDNDcxQjY1NDIyRTEyQjI3QTlEMzNBODdBRDFDREY1OTI2RTE0 -MDM3MTANBgkqhkiG9w0BAQsFAAOCAQEAHd42YyvxvvjYEIkCURHweCSQpWQrdsnP -AU2rcVzOx0kDxjq7+W2HkIkYeAZfE8pyBs5c39tgK+qospiDsKa9WYeBNyIRcCvN -q9ObLqSV4Cy/8lQMNh4Q37cdc3X+pAlTr7MtKka8ZcXYvbMBqus0dfJZayZvW7Ak -nnaXXJ4k7urgHOGYsZlZ+HC+DC/sYoN3DXzvg3XPlL0SNEQH0cWrRbaQnpOKVMk5 -TptbeNHKJfUxVW5jh4GtBFqvLeiOruY+gFYg7UkCKWo9ZIYe7/mEvJeHYh3acTTl -DOl3L0fR5KR7vqFMwZEUZxVOs6K2ANSCr57OQPBV++MG4DPr3yOhCQ== ------END CERTIFICATE----- \ No newline at end of file diff --git a/src/test/java/com/gxwebsoft/test/WechatPayPathTest.java b/src/test/java/com/gxwebsoft/test/WechatPayPathTest.java new file mode 100644 index 0000000..dae0d97 --- /dev/null +++ b/src/test/java/com/gxwebsoft/test/WechatPayPathTest.java @@ -0,0 +1,158 @@ +package com.gxwebsoft.test; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.core.utils.CertificateLoader; +import com.gxwebsoft.common.system.entity.Payment; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 微信支付路径处理测试 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@SpringBootTest +public class WechatPayPathTest { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private CertificateLoader certificateLoader; + + @Autowired + private CertificateProperties certConfig; + + @Test + public void testPublicKeyPathHandling() { + try { + System.out.println("=== 微信支付公钥路径处理测试 ==="); + + // 测试租户ID + Integer tenantId = 10547; + + // 获取支付配置 + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + if (payment == null) { + System.err.println("❌ 支付配置不存在"); + return; + } + + System.out.println("数据库配置信息:"); + System.out.println("租户ID: " + tenantId); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("公钥文件配置: " + payment.getPubKey()); + System.out.println("公钥ID: " + payment.getPubKeyId()); + + // 测试路径处理逻辑 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty()) { + System.out.println("\n=== 路径处理测试 ==="); + + String pubKeyPath; + if (payment.getPubKey().startsWith("dev/wechat/") || payment.getPubKey().startsWith("/")) { + // 如果数据库中存储的是完整路径,直接使用 + pubKeyPath = payment.getPubKey(); + System.out.println("✅ 检测到完整路径,直接使用: " + pubKeyPath); + } else { + // 如果是相对路径,需要拼接租户目录 + String tenantCertPath = "dev/wechat/" + tenantId; + pubKeyPath = tenantCertPath + "/" + payment.getPubKey(); + System.out.println("✅ 检测到相对路径,拼接后: " + pubKeyPath); + } + + // 测试文件是否存在 + System.out.println("\n=== 文件存在性检查 ==="); + if (certificateLoader.certificateExists(pubKeyPath)) { + try { + String actualPath = certificateLoader.loadCertificatePath(pubKeyPath); + System.out.println("✅ 公钥文件存在: " + actualPath); + + // 检查文件大小 + java.io.File file = new java.io.File(actualPath); + if (file.exists()) { + System.out.println("文件大小: " + file.length() + " 字节"); + System.out.println("文件可读: " + file.canRead()); + } + } catch (Exception e) { + System.err.println("❌ 加载公钥文件失败: " + e.getMessage()); + } + } else { + System.err.println("❌ 公钥文件不存在: " + pubKeyPath); + + // 提供修复建议 + System.out.println("\n=== 修复建议 ==="); + if (payment.getPubKey().contains("/")) { + System.out.println("1. 检查数据库中的路径是否正确"); + System.out.println("2. 确认文件是否已上传到指定位置"); + System.out.println("3. 当前配置的路径: " + payment.getPubKey()); + } else { + System.out.println("1. 将公钥文件放置到: src/main/resources/dev/wechat/" + tenantId + "/"); + System.out.println("2. 或者更新数据库配置为完整路径"); + } + } + + // 测试其他证书文件 + System.out.println("\n=== 其他证书文件检查 ==="); + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + String merchantCertPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getApiclientCertFile(); + + System.out.println("私钥文件: " + privateKeyPath + " - " + + (certificateLoader.certificateExists(privateKeyPath) ? "✅ 存在" : "❌ 不存在")); + System.out.println("商户证书: " + merchantCertPath + " - " + + (certificateLoader.certificateExists(merchantCertPath) ? "✅ 存在" : "❌ 不存在")); + + } else { + System.out.println("⚠️ 未配置公钥信息"); + } + + System.out.println("\n=== 测试完成 ==="); + + } catch (Exception e) { + System.err.println("❌ 测试失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + @Test + public void testCreateCorrectDirectoryStructure() { + System.out.println("=== 创建正确的目录结构建议 ==="); + + try { + Integer tenantId = 10547; + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + + if (payment != null && payment.getPubKey() != null) { + System.out.println("当前数据库配置的公钥路径: " + payment.getPubKey()); + + if (payment.getPubKey().contains("/")) { + // 包含路径分隔符,说明是完整路径 + System.out.println("\n建议的文件放置位置:"); + System.out.println("src/main/resources/" + payment.getPubKey()); + + // 创建目录的命令 + String dirPath = payment.getPubKey().substring(0, payment.getPubKey().lastIndexOf("/")); + System.out.println("\n创建目录的命令:"); + System.out.println("mkdir -p src/main/resources/" + dirPath); + + // 复制文件的命令 + System.out.println("\n如果您有公钥文件,可以这样复制:"); + System.out.println("cp your_public_key.pem src/main/resources/" + payment.getPubKey()); + } else { + // 只是文件名,需要放在租户目录下 + System.out.println("\n建议的文件放置位置:"); + System.out.println("src/main/resources/dev/wechat/" + tenantId + "/" + payment.getPubKey()); + + System.out.println("\n或者更新数据库配置为:"); + System.out.println("UPDATE sys_payment SET pub_key = 'dev/wechat/" + tenantId + "/" + payment.getPubKey() + "' WHERE tenant_id = " + tenantId + " AND type = 0;"); + } + } + + } catch (Exception e) { + System.err.println("获取配置失败: " + e.getMessage()); + } + } +} diff --git a/src/test/java/com/gxwebsoft/test/WechatPayPublicKeyTest.java b/src/test/java/com/gxwebsoft/test/WechatPayPublicKeyTest.java new file mode 100644 index 0000000..b6bfc12 --- /dev/null +++ b/src/test/java/com/gxwebsoft/test/WechatPayPublicKeyTest.java @@ -0,0 +1,150 @@ +package com.gxwebsoft.test; + +import com.gxwebsoft.common.core.config.CertificateProperties; +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.core.utils.CertificateLoader; +import com.gxwebsoft.common.core.utils.WechatCertAutoConfig; +import com.gxwebsoft.common.system.entity.Payment; +import com.wechat.pay.java.core.Config; +import com.wechat.pay.java.core.RSAPublicKeyConfig; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +/** + * 微信支付公钥配置测试 + * + * @author 科技小王子 + * @since 2025-07-29 + */ +@SpringBootTest +public class WechatPayPublicKeyTest { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private CertificateLoader certificateLoader; + + @Autowired + private CertificateProperties certConfig; + + @Autowired + private WechatCertAutoConfig wechatCertAutoConfig; + + @Test + public void testPublicKeyConfiguration() { + try { + System.out.println("=== 微信支付公钥配置测试 ==="); + + // 测试租户ID + Integer tenantId = 10547; + + // 获取支付配置 + Payment payment = paymentCacheService.getWechatPayConfig(tenantId); + System.out.println("支付配置获取成功:"); + System.out.println("商户号: " + payment.getMchId()); + System.out.println("应用ID: " + payment.getAppId()); + System.out.println("序列号: " + payment.getMerchantSerialNumber()); + System.out.println("API密钥: " + (payment.getApiKey() != null ? "已配置(长度:" + payment.getApiKey().length() + ")" : "未配置")); + System.out.println("公钥文件: " + payment.getPubKey()); + System.out.println("公钥ID: " + payment.getPubKeyId()); + + // 测试证书文件加载 + String tenantCertPath = "dev/wechat/" + tenantId; + String privateKeyPath = tenantCertPath + "/" + certConfig.getWechatPay().getDev().getPrivateKeyFile(); + + System.out.println("\n=== 证书文件检查 ==="); + System.out.println("私钥路径: " + privateKeyPath); + + if (certificateLoader.certificateExists(privateKeyPath)) { + String privateKey = certificateLoader.loadCertificatePath(privateKeyPath); + System.out.println("✅ 私钥文件加载成功: " + privateKey); + + // 检查是否配置了公钥 + if (payment.getPubKey() != null && !payment.getPubKey().isEmpty() && + payment.getPubKeyId() != null && !payment.getPubKeyId().isEmpty()) { + + System.out.println("\n=== 公钥配置测试 ==="); + String pubKeyPath = tenantCertPath + "/" + payment.getPubKey(); + System.out.println("公钥路径: " + pubKeyPath); + + if (certificateLoader.certificateExists(pubKeyPath)) { + String pubKeyFile = certificateLoader.loadCertificatePath(pubKeyPath); + System.out.println("✅ 公钥文件加载成功: " + pubKeyFile); + + // 测试RSA公钥配置 + try { + Config config = new RSAPublicKeyConfig.Builder() + .merchantId(payment.getMchId()) + .privateKeyFromPath(privateKey) + .publicKeyFromPath(pubKeyFile) + .publicKeyId(payment.getPubKeyId()) + .merchantSerialNumber(payment.getMerchantSerialNumber()) + .apiV3Key(payment.getApiKey()) + .build(); + + System.out.println("✅ RSA公钥配置创建成功"); + System.out.println("配置类型: " + config.getClass().getSimpleName()); + + } catch (Exception e) { + System.err.println("❌ RSA公钥配置失败: " + e.getMessage()); + e.printStackTrace(); + } + + } else { + System.err.println("❌ 公钥文件不存在: " + pubKeyPath); + System.out.println("💡 建议: 请将公钥文件放置到指定位置"); + } + + } else { + System.out.println("\n⚠️ 未配置公钥信息"); + System.out.println("💡 建议: 在数据库中配置 pubKey 和 pubKeyId 字段"); + + // 测试自动证书配置 + System.out.println("\n=== 自动证书配置测试 ==="); + try { + Config autoConfig = wechatCertAutoConfig.createAutoConfig( + payment.getMchId(), + privateKey, + payment.getMerchantSerialNumber(), + payment.getApiKey() + ); + System.out.println("✅ 自动证书配置创建成功"); + System.out.println("配置类型: " + autoConfig.getClass().getSimpleName()); + } catch (Exception e) { + System.err.println("❌ 自动证书配置失败: " + e.getMessage()); + System.err.println("错误类型: " + e.getClass().getName()); + + if (e.getMessage() != null && e.getMessage().contains("certificate")) { + System.err.println("🔍 这是证书相关错误,建议使用公钥模式"); + } + } + } + + } else { + System.err.println("❌ 私钥文件不存在: " + privateKeyPath); + } + + System.out.println("\n=== 测试完成 ==="); + + } catch (Exception e) { + System.err.println("❌ 测试失败: " + e.getMessage()); + e.printStackTrace(); + } + } + + @Test + public void testCreateSamplePublicKeyConfig() { + System.out.println("=== 创建示例公钥配置 ==="); + System.out.println("如果您的后台使用公钥模式,请在数据库中配置以下字段:"); + System.out.println("1. pubKey: 公钥文件名,例如 'wechatpay_public_key.pem'"); + System.out.println("2. pubKeyId: 公钥ID,例如 'PUB_KEY_ID_0112422897022025011300326200001208'"); + System.out.println("3. 将公钥文件放置到: src/main/resources/dev/wechat/{tenantId}/"); + System.out.println("\n示例SQL更新语句:"); + System.out.println("UPDATE sys_payment SET "); + System.out.println(" pub_key = 'wechatpay_public_key.pem',"); + System.out.println(" pub_key_id = 'PUB_KEY_ID_0112422897022025011300326200001208'"); + System.out.println("WHERE tenant_id = 10547 AND type = 0;"); + } +}