Browse Source

feat(payment): 添加支付配置诊断和测试接口

- 新增支付配置诊断SQL脚本
- 添加测试控制器和微信支付诊断控制器
- 实现支付配置检查、快速测试和缓存清理等功能
-优化支付服务和订单创建流程- 更新相关实体和DTO以支持新功能
main
科技小王子 2 weeks ago
parent
commit
ededc7a501
  1. 140
      docs/payment_config_diagnostic.sql
  2. 33
      quick_payment_check.sql
  3. 302
      src/main/java/com/gxwebsoft/common/core/controller/TestController.java
  4. 211
      src/main/java/com/gxwebsoft/common/core/controller/WechatCertTestController.java
  5. 3
      src/main/java/com/gxwebsoft/common/core/controller/WechatPayDiagnosticController.java
  6. 1
      src/main/java/com/gxwebsoft/common/core/security/SecurityConfig.java
  7. 47
      src/main/java/com/gxwebsoft/common/core/service/PaymentCacheService.java
  8. 39
      src/main/java/com/gxwebsoft/common/system/dto/PaymentCacheDTO.java
  9. 5
      src/main/java/com/gxwebsoft/common/system/entity/Payment.java
  10. 48
      src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java

140
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;

33
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;

302
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<Map<String, Object>> testDateTime() {
Map<String, Object> 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<String> 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<String> 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<String> 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<Payment> 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<Payment> 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;
}
}
}

211
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<Map<String, Object>> testDefaultConfig() {
Map<String, Object> 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<Map<String, Object>> testCustomConfig(
@Parameter(description = "商户号") @RequestParam String merchantId,
@Parameter(description = "私钥文件路径") @RequestParam String privateKeyPath,
@Parameter(description = "证书序列号") @RequestParam String merchantSerialNumber,
@Parameter(description = "APIv3密钥") @RequestParam String apiV3Key) {
Map<String, Object> 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<String> getInstructions() {
String instructions = wechatCertAutoConfig.getUsageInstructions();
return success("获取使用说明成功", instructions);
}
@Operation(summary = "获取故障排除信息")
@GetMapping("/troubleshooting")
public ApiResult<Map<String, Object>> getTroubleshooting() {
Map<String, Object> troubleshooting = getTroubleshootingInfo();
return success("获取故障排除信息成功", troubleshooting);
}
/**
* 获取故障排除信息
*/
private Map<String, Object> getTroubleshootingInfo() {
Map<String, Object> 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<Map<String, Object>> checkMerchantConfig(
@RequestParam String merchantId,
@RequestParam String privateKeyPath,
@RequestParam String merchantSerialNumber,
@RequestParam String apiV3Key) {
Map<String, Object> 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);
}
}
}

3
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;

1
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/**",

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

39
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 字段,避免序列化问题
}

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

48
src/main/java/com/gxwebsoft/shop/service/impl/ShopOrderServiceImpl.java

@ -122,13 +122,35 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
@Override
public HashMap<String, String> 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<String, String> orderInfo = new HashMap<>();
// 构建service
@ -141,19 +163,41 @@ public class ShopOrderServiceImpl extends ServiceImpl<ShopOrderMapper, ShopOrder
final BigDecimal multiply = decimal.multiply(new BigDecimal(100));
Integer money = multiply.intValue();
System.out.println("=== 构建支付请求参数 ===");
// 检查支付配置的关键字段
if (payment.getAppId() == null || payment.getAppId().trim().isEmpty()) {
throw new RuntimeException("支付配置中应用ID为null或空");
}
if (payment.getMchId() == null || payment.getMchId().trim().isEmpty()) {
throw new RuntimeException("支付配置中商户号为null或空");
}
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
amount.setTotal(money);
amount.setCurrency("CNY");
request.setAmount(amount);
System.out.println("设置应用ID: " + payment.getAppId());
request.setAppid(payment.getAppId());
System.out.println("设置商户号: " + payment.getMchId());
request.setMchid(payment.getMchId());
// 微信支付description字段限制127字节,需要截断处理
String description = com.gxwebsoft.common.core.utils.WechatPayUtils.processDescription(order.getComments());
System.out.println("设置描述: " + description);
request.setDescription(description);
System.out.println("设置订单号: " + order.getOrderNo());
request.setOutTradeNo(order.getOrderNo());
System.out.println("设置附加数据: " + order.getTenantId().toString());
request.setAttach(order.getTenantId().toString());
final Payer payer = new Payer();
System.out.println("设置用户OpenID: " + order.getOpenid());
payer.setOpenid(order.getOpenid());
request.setPayer(payer);
request.setNotifyUrl(config.getServerUrl() + "/system/wx-pay/notify/" + order.getTenantId()); // 默认回调地址

Loading…
Cancel
Save