fix(payment): 修复微信支付退款逻辑与配置问题

- 统一将 WECHAT 支付类型映射为 WECHAT_NATIVE 处理
- 优化退款状态查询条件,增加退款订单非空判断
- 在 ShopOrder 实体中新增 refundReason 字段并添加校验注解
- 控制器中提前校验订单是否存在避免空指针异常
- 简化支付状态检查逻辑,直接使用布尔值判断
- 微信退款策略中截取退款原因长度限制为80字符
- 修改微信退款状态转换方法参数类型为字符串
- 更新开发环境微信支付测试配置信息及证书路径
- 缓存键结构调整,增强租户隔离安全性
- 放宽开发环境对 apiclientKey 的必填校验限制
This commit is contained in:
2025-12-08 14:27:08 +08:00
parent 9eb9282cfe
commit 006c1cabec
6 changed files with 65 additions and 25 deletions

View File

@@ -116,7 +116,7 @@ public class WxPayConfigService {
* 优先从缓存获取,缓存没有则查询数据库,最后兜底到开发环境测试配置 * 优先从缓存获取,缓存没有则查询数据库,最后兜底到开发环境测试配置
*/ */
private Payment getPaymentConfig(Integer tenantId) throws PaymentException { private Payment getPaymentConfig(Integer tenantId) throws PaymentException {
String cacheKey = "Payment:wxPay:" + tenantId; String cacheKey = "Payment:1:" + tenantId;
Payment payment = redisUtil.get(cacheKey, Payment.class); Payment payment = redisUtil.get(cacheKey, Payment.class);
System.out.println("payment = " + payment); System.out.println("payment = " + payment);
if (payment != null) { if (payment != null) {
@@ -136,6 +136,14 @@ public class WxPayConfigService {
if (payment != null) { if (payment != null) {
log.info("从数据库获取支付配置成功租户ID: {},将缓存配置", tenantId); log.info("从数据库获取支付配置成功租户ID: {},将缓存配置", tenantId);
// 开发环境下如果apiclientKey为空设置默认值
if ("dev".equals(activeProfile) &&
(payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty())) {
log.warn("开发环境数据库配置中apiclientKey为空使用默认值租户ID: {}", tenantId);
payment.setApiclientKey("apiclient_key.pem");
}
// 将查询到的配置缓存起来缓存1天 // 将查询到的配置缓存起来缓存1天
redisUtil.set(cacheKey, payment, 1L, TimeUnit.DAYS); redisUtil.set(cacheKey, payment, 1L, TimeUnit.DAYS);
return payment; return payment;
@@ -244,9 +252,10 @@ public class WxPayConfigService {
testPayment.setTenantId(tenantId); testPayment.setTenantId(tenantId);
testPayment.setType(102); // Native支付 testPayment.setType(102); // Native支付
testPayment.setAppId("wxa67c676fc445590e"); // 开发环境测试AppID testPayment.setAppId("wxa67c676fc445590e"); // 开发环境测试AppID
testPayment.setMchId("1246610101"); // 开发环境测试商户号 testPayment.setMchId("1723321338"); // 租户10550的商户号
testPayment.setMerchantSerialNumber("48749613B40AA8F1D768583FC352358E13EB5AF0"); testPayment.setMerchantSerialNumber("2B933F7C35014A1C363642623E4A62364B34C4EB"); // 证书序列号
testPayment.setApiKey(certificateProperties.getWechatPay().getDev().getApiV3Key()); testPayment.setApiKey(certificateProperties.getWechatPay().getDev().getApiV3Key());
testPayment.setApiclientKey("apiclient_key.pem"); // 设置证书文件名
testPayment.setNotifyUrl("http://frps-10550.s209.websoft.top/api/payment/notify"); testPayment.setNotifyUrl("http://frps-10550.s209.websoft.top/api/payment/notify");
testPayment.setName("微信Native支付-开发环境"); testPayment.setName("微信Native支付-开发环境");
testPayment.setStatus(true); // 启用 testPayment.setStatus(true); // 启用
@@ -261,8 +270,8 @@ public class WxPayConfigService {
* 创建开发环境测试配置 * 创建开发环境测试配置
*/ */
private Config createDevTestConfig(String certificatePath) throws PaymentException { private Config createDevTestConfig(String certificatePath) throws PaymentException {
String testMerchantId = "1246610101"; String testMerchantId = "1723321338"; // 租户10550的商户号
String testMerchantSerialNumber = "48749613B40AA8F1D768583FC352358E13EB5AF0"; String testMerchantSerialNumber = "2B933F7C35014A1C363642623E4A62364B34C4EB"; // 证书序列号
String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key(); String testApiV3Key = certificateProperties.getWechatPay().getDev().getApiV3Key();
if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) { if (testApiV3Key == null || testApiV3Key.trim().isEmpty()) {
@@ -270,8 +279,10 @@ public class WxPayConfigService {
} }
log.info("使用开发环境测试配置"); log.info("使用开发环境测试配置");
log.debug("测试商户号: {}", testMerchantId); log.info("测试商户号: {}", testMerchantId);
log.debug("测试序列号: {}", testMerchantSerialNumber); log.info("测试序列号: {}", testMerchantSerialNumber);
log.info("证书路径: {}", certificatePath);
log.info("APIv3密钥长度: {}", testApiV3Key.length());
return new RSAAutoCertificateConfig.Builder() return new RSAAutoCertificateConfig.Builder()
.merchantId(testMerchantId) .merchantId(testMerchantId)
@@ -319,8 +330,14 @@ public class WxPayConfigService {
throw PaymentException.systemError("APIv3密钥(apiKey)未配置", null); throw PaymentException.systemError("APIv3密钥(apiKey)未配置", null);
} }
// 开发环境下如果apiclientKey为空给一个警告但不抛异常
// 生产环境必须有apiclientKey
if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) { if (payment.getApiclientKey() == null || payment.getApiclientKey().trim().isEmpty()) {
throw PaymentException.systemError("证书文件名(apiclientKey)未配置", null); if ("dev".equals(activeProfile)) {
log.warn("开发环境:证书文件名(apiclientKey)未配置,将使用默认值");
} else {
throw PaymentException.systemError("证书文件名(apiclientKey)未配置", null);
}
} }
log.debug("支付配置验证通过租户ID: {}, 商户号: {}", payment.getTenantId(), payment.getMchId()); log.debug("支付配置验证通过租户ID: {}, 商户号: {}", payment.getTenantId(), payment.getMchId());

View File

@@ -457,7 +457,14 @@ public class PaymentServiceImpl implements PaymentService {
* 获取支付策略 * 获取支付策略
*/ */
private PaymentStrategy getPaymentStrategy(PaymentType paymentType) throws PaymentException { private PaymentStrategy getPaymentStrategy(PaymentType paymentType) throws PaymentException {
PaymentStrategy strategy = strategyMap.get(paymentType); // 如果是WECHAT支付类型转换为WECHAT_NATIVE
// 因为WECHAT是一个通用类型实际的支付策略是WECHAT_NATIVE
PaymentType actualPaymentType = paymentType;
if (paymentType == PaymentType.WECHAT) {
actualPaymentType = PaymentType.WECHAT_NATIVE;
}
PaymentStrategy strategy = strategyMap.get(actualPaymentType);
if (strategy == null) { if (strategy == null) {
throw PaymentException.unsupportedPayment("不支持的支付类型: " + paymentType, paymentType); throw PaymentException.unsupportedPayment("不支持的支付类型: " + paymentType, paymentType);
} }

View File

@@ -517,7 +517,7 @@ public class WechatNativeStrategy implements PaymentStrategy {
// 设置退款原因(可选,但建议填写) // 设置退款原因(可选,但建议填写)
if (StringUtils.hasText(reason)) { if (StringUtils.hasText(reason)) {
// 退款原因最多80个字符 // 退款原因最多80个字符
String effectiveReason = CommonUtil.truncateString(reason, 80); String effectiveReason = reason.length() > 80 ? reason.substring(0, 80) : reason;
refundRequest.setReason(effectiveReason); refundRequest.setReason(effectiveReason);
} else { } else {
refundRequest.setReason("用户申请退款"); refundRequest.setReason("用户申请退款");
@@ -570,7 +570,8 @@ public class WechatNativeStrategy implements PaymentStrategy {
response.setTenantId(tenantId); response.setTenantId(tenantId);
// 转换退款状态 // 转换退款状态
PaymentStatus paymentStatus = convertWechatRefundStatus(refund.getStatus()); String statusStr = refund.getStatus() != null ? refund.getStatus().name() : null;
PaymentStatus paymentStatus = convertWechatRefundStatus(statusStr);
response.setPaymentStatus(paymentStatus); response.setPaymentStatus(paymentStatus);
// 设置退款金额(微信返回的金额是分,需要转换为元) // 设置退款金额(微信返回的金额是分,需要转换为元)
@@ -630,7 +631,8 @@ public class WechatNativeStrategy implements PaymentStrategy {
response.setTenantId(tenantId); response.setTenantId(tenantId);
// 转换退款状态 // 转换退款状态
PaymentStatus paymentStatus = convertWechatRefundStatus(refund.getStatus()); String statusStr = refund.getStatus() != null ? refund.getStatus().name() : null;
PaymentStatus paymentStatus = convertWechatRefundStatus(statusStr);
response.setPaymentStatus(paymentStatus); response.setPaymentStatus(paymentStatus);
// 设置退款金额 // 设置退款金额
@@ -645,19 +647,19 @@ public class WechatNativeStrategy implements PaymentStrategy {
/** /**
* 转换微信退款状态 * 转换微信退款状态
*/ */
private PaymentStatus convertWechatRefundStatus(Refund.Status refundStatus) { private PaymentStatus convertWechatRefundStatus(String refundStatus) {
if (refundStatus == null) { if (refundStatus == null) {
return PaymentStatus.REFUNDING; return PaymentStatus.REFUNDING;
} }
switch (refundStatus) { switch (refundStatus) {
case SUCCESS: case "SUCCESS":
return PaymentStatus.REFUNDED; return PaymentStatus.REFUNDED;
case CLOSED: case "CLOSED":
return PaymentStatus.REFUND_FAILED; return PaymentStatus.REFUND_FAILED;
case PROCESSING: case "PROCESSING":
return PaymentStatus.REFUNDING; return PaymentStatus.REFUNDING;
case ABNORMAL: case "ABNORMAL":
return PaymentStatus.REFUND_FAILED; return PaymentStatus.REFUND_FAILED;
default: default:
return PaymentStatus.REFUNDING; return PaymentStatus.REFUNDING;

View File

@@ -179,6 +179,10 @@ public class ShopOrderController extends BaseController {
@Operation(summary = "修改订单") @Operation(summary = "修改订单")
@PutMapping() @PutMapping()
public ApiResult<?> update(@RequestBody ShopOrder shopOrder) throws Exception { public ApiResult<?> update(@RequestBody ShopOrder shopOrder) throws Exception {
// 1. 验证订单是否可以退款
if (shopOrder == null) {
return fail("订单不存在");
}
ShopOrder shopOrderNow = shopOrderService.getById(shopOrder.getOrderId()); ShopOrder shopOrderNow = shopOrderService.getById(shopOrder.getOrderId());
// 申请退款 // 申请退款
if (shopOrder.getOrderStatus().equals(4)) { if (shopOrder.getOrderStatus().equals(4)) {
@@ -201,13 +205,9 @@ public class ShopOrderController extends BaseController {
if(shopOrder.getOrderStatus().equals(6)){ if(shopOrder.getOrderStatus().equals(6)){
// 当订单状态更改为6已退款执行退款操作 // 当订单状态更改为6已退款执行退款操作
try { try {
// 1. 验证订单是否可以退款
if (shopOrderNow == null) {
return fail("订单不存在");
}
// 检查订单是否已支付 // 检查订单是否已支付
if (shopOrderNow.getPayStatus() == null || !shopOrderNow.getPayStatus().equals(1)) { if (!shopOrder.getPayStatus()) {
return fail("订单未支付,无法退款"); return fail("订单未支付,无法退款");
} }
@@ -236,7 +236,14 @@ public class ShopOrderController extends BaseController {
PaymentType paymentType = PaymentType.WECHAT_NATIVE; PaymentType paymentType = PaymentType.WECHAT_NATIVE;
if (shopOrderNow.getPayType() != null) { if (shopOrderNow.getPayType() != null) {
// 根据订单的支付类型确定 // 根据订单的支付类型确定
paymentType = PaymentType.fromCode(shopOrderNow.getPayType()); // 支付方式0余额支付1微信支付2支付宝支付3银联支付4现金支付5POS机支付6免费7积分支付
paymentType = PaymentType.getByCode(shopOrderNow.getPayType());
// 如果是微信支付,需要根据微信支付子类型确定具体的支付方式
if (paymentType == PaymentType.WECHAT) {
// 目前统一使用WECHAT_NATIVE进行退款
paymentType = PaymentType.WECHAT_NATIVE;
}
} }
// 5. 调用统一支付服务的退款接口 // 5. 调用统一支付服务的退款接口
@@ -246,10 +253,10 @@ public class ShopOrderController extends BaseController {
PaymentResponse refundResponse = paymentService.refund( PaymentResponse refundResponse = paymentService.refund(
shopOrderNow.getOrderNo(), // 原订单号 shopOrderNow.getOrderNo(), // 原订单号
refundNo, // 退款单号 refundNo, // 退款单号
paymentType, // 支付方式
shopOrderNow.getTotalPrice(), // 订单总金额 shopOrderNow.getTotalPrice(), // 订单总金额
refundAmount, // 退款金额 refundAmount, // 退款金额
shopOrder.getRefundReason() != null ? shopOrder.getRefundReason() : "用户申请退款", // 退款原因 shopOrder.getRefundReason() != null ? shopOrder.getRefundReason() : "用户申请退款", // 退款原因
paymentType, // 支付方式
shopOrderNow.getTenantId() // 租户ID shopOrderNow.getTenantId() // 租户ID
); );

View File

@@ -204,6 +204,10 @@ public class ShopOrder implements Serializable {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime payTime; private LocalDateTime payTime;
@Schema(description = "退款原因")
@NotBlank(message = "退款原因不能为空")
private String refundReason;
@Schema(description = "退款时间") @Schema(description = "退款时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime refundTime; private LocalDateTime refundTime;

View File

@@ -48,7 +48,10 @@ public class RefundStatusQueryTask {
int totalProcessedCount = 0; int totalProcessedCount = 0;
// 查询所有退款处理中的订单状态为5 // 查询所有退款处理中的订单状态为5
List<ShopOrder> refundProcessingOrders = shopOrderService.getRefundProcessingOrders(); List<ShopOrder> refundProcessingOrders = shopOrderService.lambdaQuery()
.eq(ShopOrder::getOrderStatus, 5)
.isNotNull(ShopOrder::getRefundOrder)
.list();
if (refundProcessingOrders.isEmpty()) { if (refundProcessingOrders.isEmpty()) {
log.info("没有找到退款处理中的订单"); log.info("没有找到退款处理中的订单");