From ededc7a5012e451e7a5c6a48b4f1cabb93be351f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 13 Aug 2025 18:03:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(payment):=20=E6=B7=BB=E5=8A=A0=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E9=85=8D=E7=BD=AE=E8=AF=8A=E6=96=AD=E5=92=8C=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增支付配置诊断SQL脚本 - 添加测试控制器和微信支付诊断控制器 - 实现支付配置检查、快速测试和缓存清理等功能 -优化支付服务和订单创建流程- 更新相关实体和DTO以支持新功能 --- docs/payment_config_diagnostic.sql | 140 ++++++++ quick_payment_check.sql | 33 ++ .../core/controller/TestController.java | 302 ++++++++++++++++++ .../controller/WechatCertTestController.java | 211 ++++++++++++ .../WechatPayDiagnosticController.java | 3 +- .../common/core/security/SecurityConfig.java | 1 + .../core/service/PaymentCacheService.java | 47 ++- .../common/system/dto/PaymentCacheDTO.java | 39 +++ .../common/system/entity/Payment.java | 5 +- .../service/impl/ShopOrderServiceImpl.java | 48 ++- 10 files changed, 821 insertions(+), 8 deletions(-) create mode 100644 docs/payment_config_diagnostic.sql create mode 100644 quick_payment_check.sql create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/TestController.java create mode 100644 src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java create mode 100644 src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java diff --git a/docs/payment_config_diagnostic.sql b/docs/payment_config_diagnostic.sql new file mode 100644 index 0000000..ef1363d --- /dev/null +++ b/docs/payment_config_diagnostic.sql @@ -0,0 +1,140 @@ +-- 支付配置诊断SQL脚本 +-- 用于诊断"Value must not be null!"错误 + +-- 1. 检查所有租户的支付配置完整性 +SELECT + tenant_id, + name, + type, + mch_id, + app_id, + merchant_serial_number, + api_key, + apiclient_key, + apiclient_cert, + pub_key, + pub_key_id, + status, + -- 配置完整性检查 + CASE + WHEN mch_id IS NULL OR mch_id = '' THEN '❌ 商户号缺失' + WHEN app_id IS NULL OR app_id = '' THEN '❌ 应用ID缺失' + WHEN merchant_serial_number IS NULL OR merchant_serial_number = '' THEN '❌ 证书序列号缺失' + WHEN api_key IS NULL OR api_key = '' THEN '❌ API密钥缺失' + WHEN LENGTH(api_key) != 32 THEN '❌ API密钥长度错误' + ELSE '✅ 基础配置完整' + END AS basic_config_status, + -- 证书配置模式检查 + CASE + WHEN pub_key IS NOT NULL AND pub_key != '' AND pub_key_id IS NOT NULL AND pub_key_id != '' + THEN '🔑 公钥模式' + WHEN apiclient_key IS NOT NULL AND apiclient_key != '' AND apiclient_cert IS NOT NULL AND apiclient_cert != '' + THEN '📜 证书模式' + ELSE '⚠️ 自动证书模式' + END AS cert_mode, + -- 状态检查 + CASE + WHEN status = 1 THEN '✅ 已启用' + ELSE '❌ 未启用' + END AS status_check +FROM sys_payment +WHERE type = 0 -- 微信支付 +ORDER BY tenant_id; + +-- 2. 检查特定租户的详细配置(请替换为实际的租户ID) +-- 如果您知道具体的租户ID,请取消注释并修改下面的查询 +/* +SELECT + '=== 租户配置详情 ===' as section, + tenant_id, + name, + mch_id as '商户号', + app_id as '应用ID', + merchant_serial_number as '证书序列号', + CASE + WHEN api_key IS NOT NULL AND api_key != '' + THEN CONCAT('已配置(长度:', LENGTH(api_key), ')') + ELSE '未配置' + END as 'API密钥状态', + apiclient_key as '私钥文件', + apiclient_cert as '证书文件', + pub_key as '公钥文件', + pub_key_id as '公钥ID', + status as '状态' +FROM sys_payment +WHERE tenant_id = 10547 AND type = 0; -- 请替换为实际的租户ID +*/ + +-- 3. 查找可能导致"Value must not be null!"的问题 +SELECT + '=== 潜在问题检查 ===' as section, + tenant_id, + CASE + WHEN mch_id IS NULL THEN '商户号为NULL' + WHEN mch_id = '' THEN '商户号为空字符串' + ELSE NULL + END as mch_id_issue, + CASE + WHEN app_id IS NULL THEN '应用ID为NULL' + WHEN app_id = '' THEN '应用ID为空字符串' + ELSE NULL + END as app_id_issue, + CASE + WHEN merchant_serial_number IS NULL THEN '证书序列号为NULL' + WHEN merchant_serial_number = '' THEN '证书序列号为空字符串' + ELSE NULL + END as serial_number_issue, + CASE + WHEN api_key IS NULL THEN 'API密钥为NULL' + WHEN api_key = '' THEN 'API密钥为空字符串' + WHEN LENGTH(api_key) != 32 THEN CONCAT('API密钥长度错误(', LENGTH(api_key), ')') + ELSE NULL + END as api_key_issue +FROM sys_payment +WHERE type = 0 +HAVING mch_id_issue IS NOT NULL + OR app_id_issue IS NOT NULL + OR serial_number_issue IS NOT NULL + OR api_key_issue IS NOT NULL; + +-- 4. 生成修复建议 +SELECT + '=== 修复建议 ===' as section, + tenant_id, + CONCAT( + '-- 租户 ', tenant_id, ' 的修复SQL:\n', + 'UPDATE sys_payment SET \n', + CASE WHEN mch_id IS NULL OR mch_id = '' THEN ' mch_id = ''YOUR_MERCHANT_ID'',\n' ELSE '' END, + CASE WHEN app_id IS NULL OR app_id = '' THEN ' app_id = ''YOUR_APP_ID'',\n' ELSE '' END, + CASE WHEN merchant_serial_number IS NULL OR merchant_serial_number = '' THEN ' merchant_serial_number = ''YOUR_SERIAL_NUMBER'',\n' ELSE '' END, + CASE WHEN api_key IS NULL OR api_key = '' THEN ' api_key = ''YOUR_32_CHAR_API_KEY'',\n' ELSE '' END, + ' status = 1\n', + 'WHERE tenant_id = ', tenant_id, ' AND type = 0;\n' + ) as fix_sql +FROM sys_payment +WHERE type = 0 + AND (mch_id IS NULL OR mch_id = '' + OR app_id IS NULL OR app_id = '' + OR merchant_serial_number IS NULL OR merchant_serial_number = '' + OR api_key IS NULL OR api_key = ''); + +-- 5. 检查证书文件路径配置 +SELECT + '=== 证书文件路径检查 ===' as section, + tenant_id, + apiclient_key as '私钥文件路径', + apiclient_cert as '证书文件路径', + pub_key as '公钥文件路径', + CASE + WHEN apiclient_key IS NOT NULL AND apiclient_key != '' + THEN '✅ 私钥路径已配置' + ELSE '❌ 私钥路径未配置' + END as private_key_status, + CASE + WHEN pub_key IS NOT NULL AND pub_key != '' + THEN '✅ 公钥路径已配置' + ELSE '⚠️ 公钥路径未配置(将使用自动证书)' + END as public_key_status +FROM sys_payment +WHERE type = 0 +ORDER BY tenant_id; diff --git a/quick_payment_check.sql b/quick_payment_check.sql new file mode 100644 index 0000000..f67b301 --- /dev/null +++ b/quick_payment_check.sql @@ -0,0 +1,33 @@ +-- 快速支付配置检查SQL +-- 请在数据库中执行此查询 + +SELECT + '=== 支付配置检查 ===' as title, + tenant_id as '租户ID', + CASE + WHEN mch_id IS NULL OR mch_id = '' THEN '❌ 未配置' + ELSE CONCAT('✅ ', mch_id) + END as '商户号', + CASE + WHEN app_id IS NULL OR app_id = '' THEN '❌ 未配置' + ELSE CONCAT('✅ ', app_id) + END as '应用ID', + CASE + WHEN merchant_serial_number IS NULL OR merchant_serial_number = '' THEN '❌ 未配置' + ELSE '✅ 已配置' + END as '证书序列号', + CASE + WHEN api_key IS NULL OR api_key = '' THEN '❌ 未配置' + WHEN LENGTH(api_key) != 32 THEN CONCAT('❌ 长度错误(', LENGTH(api_key), '位)') + ELSE '✅ 已配置(32位)' + END as 'API密钥', + CASE + WHEN status = 1 THEN '✅ 已启用' + ELSE '❌ 未启用' + END as '状态' +FROM sys_payment +WHERE type = 0 -- 微信支付 +ORDER BY tenant_id; + +-- 如果上面的查询没有返回结果,说明没有微信支付配置,请执行: +-- SELECT COUNT(*) as '微信支付配置数量' FROM sys_payment WHERE type = 0; diff --git a/src/main/java/com/gxwebsoft/common/core/controller/TestController.java b/src/main/java/com/gxwebsoft/common/core/controller/TestController.java new file mode 100644 index 0000000..05c4246 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/TestController.java @@ -0,0 +1,302 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.service.PaymentCacheService; +import com.gxwebsoft.common.system.entity.Payment; +import com.gxwebsoft.common.system.service.PaymentService; +import com.gxwebsoft.common.system.param.PaymentParam; +import com.gxwebsoft.common.core.web.ApiResult; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 测试控制器 + * 用于测试LocalDateTime序列化 + * + * @author WebSoft + * @since 2025-01-12 + */ +@Tag(name = "测试接口") +@RestController +@RequestMapping("/api/test") +public class TestController extends BaseController { + + @Autowired + private PaymentCacheService paymentCacheService; + + @Autowired + private PaymentService paymentService; + + @Operation(summary = "测试LocalDateTime序列化") + @GetMapping("/datetime") + public ApiResult> testDateTime() { + Map result = new HashMap<>(); + // 使用字符串格式避免序列化问题 + result.put("currentTime", LocalDateTime.now().toString()); + result.put("currentTimeFormatted", LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); + result.put("message", "LocalDateTime序列化测试"); + result.put("timestamp", System.currentTimeMillis()); + return success(result); + } + + @Operation(summary = "基础诊断 - 不依赖支付服务") + @GetMapping("/basic-debug/{tenantId}") + public ApiResult basicDebug(@PathVariable Integer tenantId) { + try { + System.out.println("=== 基础诊断开始 ==="); + System.out.println("接收到的租户ID: " + tenantId); + + if (tenantId == null) { + return fail("租户ID为null",null); + } + + return success("基础诊断通过,租户ID: " + tenantId,null); + + } catch (Exception e) { + System.err.println("基础诊断异常: " + e.getMessage()); + e.printStackTrace(); + return fail("基础诊断异常: " + e.getMessage(),null); + } + } + + @Operation(summary = "快速诊断支付配置") + @GetMapping("/payment-debug/{tenantId}") + public ApiResult debugPaymentConfig(@PathVariable Integer tenantId) { + try { + System.out.println("=== 开始诊断租户 " + tenantId + " 的支付配置 ==="); + + // 检查基础参数 + if (tenantId == null) { + return fail("租户ID为null",null); + } + + // 检查服务是否可用 + if (paymentCacheService == null) { + return fail("PaymentCacheService未注入",null); + } + + System.out.println("准备调用 paymentCacheService.getWechatPayConfig(" + tenantId + ")"); + + // 获取支付配置 + Payment payment = null; + try { + payment = paymentCacheService.getWechatPayConfig(tenantId); + System.out.println("成功调用 getWechatPayConfig,结果: " + (payment != null ? "非null" : "null")); + } catch (Exception e) { + System.err.println("调用 getWechatPayConfig 异常: " + e.getMessage()); + e.printStackTrace(); + return fail("获取支付配置异常: " + e.getMessage() + " (类型: " + e.getClass().getName() + ")",null); + } + + if (payment == null) { + System.out.println("❌ 支付配置不存在"); + return fail("支付配置不存在,租户ID: " + tenantId,null); + } + + // 构建诊断信息字符串,避免序列化问题 + StringBuilder diagnosis = new StringBuilder(); + diagnosis.append("=== 支付配置诊断结果 ===\n"); + diagnosis.append("租户ID: ").append(tenantId).append("\n"); + diagnosis.append("商户号: ").append(payment.getMchId()).append("\n"); + diagnosis.append("应用ID: ").append(payment.getAppId()).append("\n"); + diagnosis.append("证书序列号: ").append(payment.getMerchantSerialNumber()).append("\n"); + diagnosis.append("API密钥: ").append(payment.getApiKey() != null ? "已配置(长度:" + payment.getApiKey().length() + ")" : "未配置").append("\n"); + diagnosis.append("状态: ").append(payment.getStatus()).append("\n"); + diagnosis.append("私钥文件: ").append(payment.getApiclientKey()).append("\n"); + diagnosis.append("证书文件: ").append(payment.getApiclientCert()).append("\n"); + diagnosis.append("公钥文件: ").append(payment.getPubKey()).append("\n"); + diagnosis.append("公钥ID: ").append(payment.getPubKeyId()).append("\n"); + + // 检查问题 + StringBuilder issues = new StringBuilder(); + if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) { + issues.append("❌ 商户号为空\n"); + } + if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) { + issues.append("❌ 应用ID为空\n"); + } + if (payment.getMerchantSerialNumber() == null || payment.getMerchantSerialNumber().trim().isEmpty()) { + issues.append("❌ 证书序列号为空\n"); + } + if (payment.getApiKey() == null || payment.getApiKey().trim().isEmpty()) { + issues.append("❌ API密钥为空\n"); + } else if (payment.getApiKey().length() != 32) { + issues.append("❌ API密钥长度错误(").append(payment.getApiKey().length()).append("位)\n"); + } + if (payment.getStatus() == null || !payment.getStatus()) { + issues.append("❌ 支付配置未启用\n"); + } + + if (issues.length() > 0) { + diagnosis.append("\n=== 发现的问题 ===\n"); + diagnosis.append(issues.toString()); + } else { + diagnosis.append("\n✅ 配置检查通过,无问题发现"); + } + + // 打印到控制台 + System.out.println(diagnosis.toString()); + + if (issues.length() > 0) { + return fail(diagnosis.toString(),null); + } else { + return success(diagnosis.toString(),null); + } + + } catch (Exception e) { + String errorMsg = "诊断失败: " + e.getMessage() + " (类型: " + e.getClass().getName() + ")"; + System.err.println(errorMsg); + e.printStackTrace(); + return fail(errorMsg,null); + } + } + + @Operation(summary = "直接数据库查询支付配置") + @GetMapping("/db-payment-check/{tenantId}") + public ApiResult checkPaymentFromDB(@PathVariable Integer tenantId) { + try { + System.out.println("=== 直接数据库查询支付配置 ==="); + System.out.println("租户ID: " + tenantId); + + if (tenantId == null) { + return fail("租户ID为null",null); + } + + if (paymentService == null) { + return fail("PaymentService未注入",null); + } + + // 直接查询数据库,不使用缓存 + PaymentParam param = new PaymentParam(); + param.setType(0); // 微信支付 + param.setTenantId(tenantId); + + System.out.println("准备查询数据库,参数: type=0, tenantId=" + tenantId); + + java.util.List payments = paymentService.listRel(param); + + System.out.println("查询结果数量: " + (payments != null ? payments.size() : "null")); + + if (payments == null || payments.isEmpty()) { + return fail("数据库中没有找到租户 " + tenantId + " 的微信支付配置",null); + } + + Payment payment = payments.get(0); + + StringBuilder result = new StringBuilder(); + result.append("=== 数据库查询结果 ===\n"); + result.append("租户ID: ").append(payment.getTenantId()).append("\n"); + result.append("支付方式: ").append(payment.getName()).append("\n"); + result.append("类型: ").append(payment.getType()).append("\n"); + result.append("商户号: ").append(payment.getMchId()).append("\n"); + result.append("应用ID: ").append(payment.getAppId()).append("\n"); + result.append("证书序列号: ").append(payment.getMerchantSerialNumber()).append("\n"); + result.append("API密钥状态: ").append(payment.getApiKey() != null ? "已配置(长度:" + payment.getApiKey().length() + ")" : "未配置").append("\n"); + result.append("状态: ").append(payment.getStatus()).append("\n"); + result.append("是否删除: ").append(payment.getDeleted()).append("\n"); + + System.out.println(result.toString()); + + return success(result.toString(),null); + + } catch (Exception e) { + String errorMsg = "数据库查询异常: " + e.getMessage() + " (类型: " + e.getClass().getName() + ")"; + System.err.println(errorMsg); + e.printStackTrace(); + return fail(errorMsg,null); + } + } + + @Operation(summary = "清理支付配置缓存") + @GetMapping("/clear-payment-cache/{tenantId}") + public String clearPaymentCache(@PathVariable Integer tenantId) { + try { + System.out.println("=== 清理支付配置缓存 ==="); + System.out.println("租户ID: " + tenantId); + + if (tenantId == null) { + return "错误: 租户ID为null"; + } + + // 清理可能的缓存键 + paymentCacheService.removePaymentConfig("0", tenantId); // 微信支付 + paymentCacheService.removePaymentConfig("wechat", tenantId); // 可能的其他格式 + + String result = "✅ 缓存已清理,租户ID: " + tenantId; + System.out.println(result); + return result; + + } catch (Exception e) { + String errorMsg = "❌ 清理缓存异常: " + e.getMessage(); + System.err.println(errorMsg); + e.printStackTrace(); + return errorMsg; + } + } + + @Operation(summary = "最简单的测试接口") + @GetMapping("/simple-test") + public String simpleTest() { + return "✅ 测试接口正常工作,时间: " + System.currentTimeMillis(); + } + + @Operation(summary = "测试支付配置是否存在") + @GetMapping("/check-payment-exists/{tenantId}") + public String checkPaymentExists(@PathVariable Integer tenantId) { + try { + System.out.println("=== 检查支付配置是否存在 ==="); + System.out.println("租户ID: " + tenantId); + + if (tenantId == null) { + return "❌ 租户ID为null"; + } + + if (paymentService == null) { + return "❌ PaymentService未注入"; + } + + // 使用最简单的查询 + PaymentParam param = new PaymentParam(); + param.setType(0); + param.setTenantId(tenantId); + + java.util.List payments = paymentService.listRel(param); + + if (payments == null) { + return "❌ 查询结果为null"; + } + + if (payments.isEmpty()) { + return "❌ 没有找到支付配置,租户ID: " + tenantId; + } + + Payment payment = payments.get(0); + StringBuilder result = new StringBuilder(); + result.append("✅ 找到支付配置\n"); + result.append("租户ID: ").append(payment.getTenantId()).append("\n"); + result.append("商户号: ").append(payment.getMchId() != null ? payment.getMchId() : "NULL").append("\n"); + result.append("应用ID: ").append(payment.getAppId() != null ? payment.getAppId() : "NULL").append("\n"); + result.append("状态: ").append(payment.getStatus()).append("\n"); + + return result.toString(); + + } catch (Exception e) { + String error = "❌ 检查异常: " + e.getMessage(); + System.err.println(error); + e.printStackTrace(); + return error; + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java b/src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java new file mode 100644 index 0000000..148ece5 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java @@ -0,0 +1,211 @@ +package com.gxwebsoft.common.core.controller; + +import com.gxwebsoft.common.core.utils.WechatCertAutoConfig; +import com.gxwebsoft.common.core.web.ApiResult; +import com.gxwebsoft.common.core.web.BaseController; +import com.wechat.pay.java.core.Config; +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.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * 微信支付证书自动配置测试控制器 + * + * @author 科技小王子 + * @since 2024-07-26 + */ +@Slf4j +@RestController +@RequestMapping("/api/wechat-cert-test") +@Tag(name = "微信支付证书自动配置测试") +public class WechatCertTestController extends BaseController { + + @Autowired + private WechatCertAutoConfig wechatCertAutoConfig; + + @Operation(summary = "测试默认开发环境证书配置") + @PostMapping("/test-default") + public ApiResult> testDefaultConfig() { + Map result = new HashMap<>(); + + try { + log.info("开始测试默认开发环境证书配置..."); + + // 创建自动证书配置 + Config config = wechatCertAutoConfig.createDefaultDevConfig(); + + // 测试配置 + boolean testResult = wechatCertAutoConfig.testConfig(config); + + result.put("success", true); + result.put("configCreated", config != null); + result.put("testPassed", testResult); + result.put("message", "默认证书配置测试完成"); + result.put("instructions", wechatCertAutoConfig.getUsageInstructions()); + + log.info("✅ 默认证书配置测试成功"); + return success("测试成功", result); + + } catch (Exception e) { + log.error("❌ 默认证书配置测试失败: {}", e.getMessage(), e); + + result.put("success", false); + result.put("error", e.getMessage()); + result.put("message", "证书配置测试失败"); + result.put("troubleshooting", getTroubleshootingInfo()); + + return fail("测试失败: " + e.getMessage(), result); + } + } + + @Operation(summary = "测试自定义证书配置") + @PostMapping("/test-custom") + public ApiResult> testCustomConfig( + @Parameter(description = "商户号") @RequestParam String merchantId, + @Parameter(description = "私钥文件路径") @RequestParam String privateKeyPath, + @Parameter(description = "证书序列号") @RequestParam String merchantSerialNumber, + @Parameter(description = "APIv3密钥") @RequestParam String apiV3Key) { + + Map result = new HashMap<>(); + + try { + log.info("开始测试自定义证书配置..."); + log.info("商户号: {}", merchantId); + log.info("私钥路径: {}", privateKeyPath); + + // 创建自动证书配置 + Config config = wechatCertAutoConfig.createAutoConfig( + merchantId, privateKeyPath, merchantSerialNumber, apiV3Key); + + // 测试配置 + boolean testResult = wechatCertAutoConfig.testConfig(config); + + result.put("success", true); + result.put("configCreated", config != null); + result.put("testPassed", testResult); + result.put("message", "自定义证书配置测试完成"); + result.put("merchantId", merchantId); + result.put("privateKeyPath", privateKeyPath); + + log.info("✅ 自定义证书配置测试成功"); + return success("测试成功", result); + + } catch (Exception e) { + log.error("❌ 自定义证书配置测试失败: {}", e.getMessage(), e); + + result.put("success", false); + result.put("error", e.getMessage()); + result.put("message", "证书配置测试失败"); + result.put("troubleshooting", getTroubleshootingInfo()); + + return fail("测试失败: " + e.getMessage(), result); + } + } + + @Operation(summary = "获取使用说明") + @GetMapping("/instructions") + public ApiResult getInstructions() { + String instructions = wechatCertAutoConfig.getUsageInstructions(); + return success("获取使用说明成功", instructions); + } + + @Operation(summary = "获取故障排除信息") + @GetMapping("/troubleshooting") + public ApiResult> getTroubleshooting() { + Map troubleshooting = getTroubleshootingInfo(); + return success("获取故障排除信息成功", troubleshooting); + } + + /** + * 获取故障排除信息 + */ + private Map getTroubleshootingInfo() { + Map info = new HashMap<>(); + + info.put("commonIssues", Map.of( + "404错误", "商户平台未开启API安全功能或未申请使用微信支付公钥", + "证书序列号错误", "请检查商户平台中的证书序列号是否正确", + "APIv3密钥错误", "请确认APIv3密钥是否正确设置", + "私钥文件不存在", "请检查私钥文件路径是否正确", + "网络连接问题", "请检查网络连接是否正常" + )); + + info.put("solutions", Map.of( + "开启API安全", "登录微信商户平台 -> 账户中心 -> API安全 -> 申请使用微信支付公钥", + "获取证书序列号", "在API安全页面查看或重新下载证书", + "设置APIv3密钥", "在API安全页面设置APIv3密钥", + "检查私钥文件", "确保apiclient_key.pem文件存在且路径正确" + )); + + info.put("advantages", Map.of( + "自动下载", "RSAAutoCertificateConfig会自动下载平台证书", + "自动更新", "证书过期时会自动更新", + "简化管理", "无需手动管理wechatpay_cert.pem文件", + "官方推荐", "微信支付官方推荐的证书管理方式" + )); + + info.put("documentation", "https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); + + return info; + } + + @Operation(summary = "检查商户平台配置状态") + @PostMapping("/check-merchant-config") + public ApiResult> checkMerchantConfig( + @RequestParam String merchantId, + @RequestParam String privateKeyPath, + @RequestParam String merchantSerialNumber, + @RequestParam String apiV3Key) { + + Map result = new HashMap<>(); + + try { + log.info("开始检查商户平台配置状态..."); + log.info("商户号: {}", merchantId); + + // 尝试创建自动证书配置 + Config config = wechatCertAutoConfig.createAutoConfig( + merchantId, privateKeyPath, merchantSerialNumber, apiV3Key); + + result.put("success", true); + result.put("configCreated", true); + result.put("message", "商户平台配置正常,自动证书配置创建成功"); + result.put("merchantId", merchantId); + result.put("recommendation", "配置正常,可以正常使用微信支付功能"); + + log.info("✅ 商户平台配置检查成功"); + return success("配置检查成功", result); + + } catch (Exception e) { + log.error("❌ 商户平台配置检查失败: {}", e.getMessage(), e); + + result.put("success", false); + result.put("configCreated", false); + result.put("error", e.getMessage()); + result.put("merchantId", merchantId); + + // 分析错误类型并提供解决方案 + if (e.getMessage().contains("404") || e.getMessage().contains("RESOURCE_NOT_EXISTS")) { + result.put("errorType", "商户平台配置问题"); + result.put("solution", "请在微信支付商户平台完成以下配置:\n" + + "1. 登录商户平台:https://pay.weixin.qq.com/\n" + + "2. 进入:产品中心 → 开发配置 → API安全\n" + + "3. 申请API证书\n" + + "4. 申请使用微信支付公钥\n" + + "5. 确保API证书和微信支付公钥状态为\"已生效\""); + result.put("documentUrl", "https://pay.weixin.qq.com/doc/v3/merchant/4012153196"); + } else { + result.put("errorType", "其他配置问题"); + result.put("solution", "请检查商户号、证书序列号、API密钥等配置是否正确"); + } + + return success("配置检查完成(发现问题)", result); + } + } +} diff --git a/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java b/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java index 627c86e..18c75a1 100644 --- a/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java +++ b/src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java @@ -4,7 +4,6 @@ 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; @@ -37,7 +36,7 @@ public class WechatPayDiagnosticController extends com.gxwebsoft.common.core.web private com.gxwebsoft.common.core.service.PaymentCacheService paymentCacheService; @Autowired - private com.gxwebsoft.common.core.utils.WechatPayConfigChecker configChecker; + private WechatPayConfigChecker configChecker; @Value("${spring.profiles.active:dev}") private String activeProfile; diff --git a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java index d87d09e..8dc15a6 100644 --- a/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java +++ b/src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java @@ -62,6 +62,7 @@ public class SecurityConfig { "/api/mp/mp/component_verify_ticket", "/api/mp/mp/callback", "/api/shop/test/**", + "/api/test/payment-debug/**", "/api/shop/wx-login/**", "/api/shop/wx-native-pay/**", "/api/shop/wx-pay/**", diff --git a/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java b/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java index 4a25f74..17a8e32 100644 --- a/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java +++ b/src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java @@ -70,11 +70,15 @@ public class PaymentCacheService { } Payment dbPayment = payments.get(0); + + // 清理时间字段,避免序列化问题 + Payment cachePayment = cleanPaymentForCache(dbPayment); + // 将查询结果缓存到 Payment:1* 格式 - redisUtil.set(primaryKey, dbPayment); + redisUtil.set(primaryKey, cachePayment); log.debug("支付配置已缓存到: {}", primaryKey); - return dbPayment; + return dbPayment; // 返回原始对象,不影响业务逻辑 } /** @@ -128,4 +132,43 @@ public class PaymentCacheService { public Payment getAlipayConfig(Integer tenantId) { return getPaymentConfig(1, tenantId); } + + /** + * 清理Payment对象用于缓存 + * 移除可能导致序列化问题的时间字段 + */ + private Payment cleanPaymentForCache(Payment original) { + if (original == null) { + return null; + } + + Payment cleaned = new Payment(); + // 复制所有业务相关字段 + cleaned.setId(original.getId()); + cleaned.setName(original.getName()); + cleaned.setType(original.getType()); + cleaned.setCode(original.getCode()); + cleaned.setImage(original.getImage()); + cleaned.setWechatType(original.getWechatType()); + cleaned.setAppId(original.getAppId()); + cleaned.setMchId(original.getMchId()); + cleaned.setApiKey(original.getApiKey()); + cleaned.setApiclientCert(original.getApiclientCert()); + cleaned.setApiclientKey(original.getApiclientKey()); + cleaned.setPubKey(original.getPubKey()); + cleaned.setPubKeyId(original.getPubKeyId()); + cleaned.setMerchantSerialNumber(original.getMerchantSerialNumber()); + cleaned.setNotifyUrl(original.getNotifyUrl()); + cleaned.setComments(original.getComments()); + cleaned.setSortNumber(original.getSortNumber()); + cleaned.setStatus(original.getStatus()); + cleaned.setDeleted(original.getDeleted()); + cleaned.setTenantId(original.getTenantId()); + + // 不设置时间字段,避免序列化问题 + // cleaned.setCreateTime(null); + // cleaned.setUpdateTime(null); + + return cleaned; + } } diff --git a/src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java b/src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java new file mode 100644 index 0000000..8a0cab9 --- /dev/null +++ b/src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java @@ -0,0 +1,39 @@ +package com.gxwebsoft.common.system.dto; + +import lombok.Data; +import java.io.Serializable; + +/** + * 支付配置缓存DTO + * 专门用于Redis缓存,不包含时间字段,避免序列化问题 + * + * @author 科技小王子 + * @since 2025-01-13 + */ +@Data +public class PaymentCacheDTO implements Serializable { + private static final long serialVersionUID = 1L; + + private Integer id; + private String name; + private Integer type; + private String code; + private String image; + private Integer wechatType; + private String appId; + private String mchId; + private String apiKey; + private String apiclientCert; + private String apiclientKey; + private String pubKey; + private String pubKeyId; + private String merchantSerialNumber; + private String notifyUrl; + private String comments; + private Integer sortNumber; + private Boolean status; + private Integer deleted; + private Integer tenantId; + + // 不包含 createTime 和 updateTime 字段,避免序列化问题 +} diff --git a/src/main/java/com/gxwebsoft/common/system/entity/Payment.java b/src/main/java/com/gxwebsoft/common/system/entity/Payment.java index 2b5a66c..819ff36 100644 --- a/src/main/java/com/gxwebsoft/common/system/entity/Payment.java +++ b/src/main/java/com/gxwebsoft/common/system/entity/Payment.java @@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode; import java.io.Serializable; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; /** * 支付方式 @@ -89,11 +90,11 @@ public class Payment implements Serializable { private Integer tenantId; @Schema(description = "注册时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonIgnore // 缓存时忽略此字段 private LocalDateTime createTime; @Schema(description = "修改时间") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonIgnore // 缓存时忽略此字段 private LocalDateTime updateTime; } 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 e28df96..daed37c 100644 --- a/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java +++ b/src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java @@ -122,13 +122,35 @@ public class ShopOrderServiceImpl extends ServiceImpl createWxOrder(ShopOrder order) { try { - // 后台微信支付配置信息 - final Payment payment = getPayment(order); System.out.println("=== 开始创建微信支付订单 ==="); System.out.println("订单号: " + order.getOrderNo()); System.out.println("租户ID: " + order.getTenantId()); + System.out.println("支付类型: " + order.getPayType()); + System.out.println("订单金额: " + order.getTotalPrice()); + System.out.println("用户OpenID: " + order.getOpenid()); + + // 检查订单基本信息 + if (order.getTenantId() == null) { + throw new RuntimeException("订单租户ID为null"); + } + if (order.getOrderNo() == null || order.getOrderNo().trim().isEmpty()) { + throw new RuntimeException("订单号为空"); + } + if (order.getTotalPrice() == null) { + throw new RuntimeException("订单金额为null"); + } + if (order.getOpenid() == null || order.getOpenid().trim().isEmpty()) { + throw new RuntimeException("用户OpenID为空"); + } + + // 后台微信支付配置信息 + final Payment payment = getPayment(order); System.out.println("支付配置: " + (payment != null ? "已获取" : "未获取")); + if (payment == null) { + throw new RuntimeException("支付配置为null,租户ID: " + order.getTenantId()); + } + // 返回的订单数据 final HashMap orderInfo = new HashMap<>(); // 构建service @@ -141,19 +163,41 @@ public class ShopOrderServiceImpl extends ServiceImpl