From 21347ba3bb614e713d8f00f1e083cdb61e2b5d3d Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期三, 15 七月 2026 11:18:40 +0800
Subject: [PATCH] 支付回调fix

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java         |   21 +++++++---
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/service/impl/IcbcPayServiceImpl.java       |   19 ++++++++-
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupport.java     |   26 ++++++++++++-
 ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/SiteController.java        |    3 +
 ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupportTest.java |   23 +++++++++++
 5 files changed, 81 insertions(+), 11 deletions(-)

diff --git a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/SiteController.java b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/SiteController.java
index 478eff9..49c4fbf 100644
--- a/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/SiteController.java
+++ b/ruoyi-service/ruoyi-chargingPile/src/main/java/com/ruoyi/chargingPile/controller/SiteController.java
@@ -676,6 +676,9 @@
 	@ResponseBody
 	@PostMapping("/getSiteByIds")
 	public R<List<Site>> getSiteByIds(@RequestBody List<Integer> ids){
+		if (CollectionUtils.isEmpty(ids)) {
+			return R.ok(Collections.emptyList());
+		}
 		List<Site> sites = siteService.list(new LambdaQueryWrapper<Site>().in(Site::getId, ids).eq(Site::getDelFlag, 0));
 		return R.ok(sites);
 	}
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupport.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupport.java
index c6d8f5b..45cd128 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupport.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupport.java
@@ -1,7 +1,10 @@
 package com.ruoyi.order.service.impl;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.ruoyi.order.api.model.TChargingOrder;
 import com.ruoyi.order.api.model.TChargingOrderShareRecord;
+import com.ruoyi.payment.api.vo.IcbcPaymentResp;
 import com.ruoyi.payment.api.vo.IcbcRefundReq;
 import com.ruoyi.payment.api.vo.IcbcSubOrderQueryReq;
 import com.ruoyi.payment.api.vo.IcbcSubOrderRefundReq;
@@ -13,15 +16,34 @@
     }
 
     static void validateOrder(TChargingOrder order) {
+        validateIcbcOrder(order);
+        if (!Integer.valueOf(2).equals(order.getRechargePaymentStatus())) {
+            throw new IllegalArgumentException("订单尚未支付成功");
+        }
+    }
+
+    static void validateIcbcOrder(TChargingOrder order) {
         if (order == null) {
             throw new IllegalArgumentException("充电订单不存在");
         }
         if (!"ICBC".equals(order.getSourceName())) {
             throw new IllegalArgumentException("仅支持工商银行支付订单");
         }
-        if (!Integer.valueOf(2).equals(order.getRechargePaymentStatus())) {
-            throw new IllegalArgumentException("订单尚未支付成功");
+    }
+
+    static boolean isBankPaid(IcbcPaymentResp payment) {
+        if (payment == null) {
+            return false;
         }
+        try {
+            JSONObject response = JSON.parseObject(payment.getRawResponse());
+            if (response != null && response.getIntValue("returnCode") == 0
+                    && "0".equals(response.getString("pay_status"))) {
+                return true;
+            }
+        } catch (Exception ignored) {
+        }
+        return "0".equals(payment.getStatus());
     }
 
     static BigDecimal requireAmount(BigDecimal amount) {
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index 4d6455b..27a9ac5 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -1105,7 +1105,7 @@
 
 	@Override
 	public IcbcPaymentResp queryIcbcAdminPayment(String orderCode) {
-		TChargingOrder order = loadIcbcAdminOrder(orderCode);
+		TChargingOrder order = loadIcbcAdminOrder(orderCode, false);
 		return requireFeignData(icbcPaymentClient.query(order.getCode()), "工行支付查询失败");
 	}
 
@@ -1114,7 +1114,7 @@
 		if (request == null) {
 			throw new ServiceException("请求参数不能为空");
 		}
-		TChargingOrder order = loadIcbcAdminOrder(request.getOrderCode());
+		TChargingOrder order = loadIcbcAdminOrder(request.getOrderCode(), false);
 		BigDecimal amount;
 		try {
 			amount = IcbcAdminFundOperationSupport.requireAmount(request.getAmount());
@@ -1132,8 +1132,13 @@
 		}
 		try {
 			IcbcPaymentResp payment = requireFeignData(icbcPaymentClient.query(order.getCode()), "工行支付查询失败");
+			if (!IcbcAdminFundOperationSupport.isBankPaid(payment)) {
+				throw new ServiceException("工行查询未确认该订单支付成功,禁止退款");
+			}
 			String orderId = extractIcbcOrderId(payment);
 			order.setRechargeSerialNumber(orderId);
+			// 仅用于本次请求通过退款参数校验,不更新本地订单状态。
+			order.setRechargePaymentStatus(2);
 			RBucket<String> pendingRefund = redissonClient.getBucket("icbcAdminFund:pending:main:" + order.getCode());
 			String generatedRefundNo = "ICBCADMINREFUND" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
 			String pendingValue = pendingRefund.get();
@@ -1163,7 +1168,7 @@
 
 	@Override
 	public List<IcbcAdminSubOrderVO> queryIcbcAdminSubOrders(String orderCode) {
-		TChargingOrder order = loadIcbcAdminOrder(orderCode);
+		TChargingOrder order = loadIcbcAdminOrder(orderCode, true);
 		List<TChargingOrderShareRecord> records = chargingOrderShareRecordMapper.selectList(
 				new LambdaQueryWrapper<TChargingOrderShareRecord>()
 						.eq(TChargingOrderShareRecord::getChargingOrderId, order.getId())
@@ -1190,7 +1195,7 @@
 		if (request == null || request.getShareRecordId() == null) {
 			throw new ServiceException("请选择需要退款的分账子订单");
 		}
-		TChargingOrder order = loadIcbcAdminOrder(request.getOrderCode());
+		TChargingOrder order = loadIcbcAdminOrder(request.getOrderCode(), true);
 		TChargingOrderShareRecord shareRecord = chargingOrderShareRecordMapper.selectById(request.getShareRecordId());
 		if (shareRecord == null || !order.getId().equals(shareRecord.getChargingOrderId())) {
 			throw new ServiceException("分账记录不属于当前订单");
@@ -1248,7 +1253,7 @@
 		}
 	}
 
-	private TChargingOrder loadIcbcAdminOrder(String orderCode) {
+	private TChargingOrder loadIcbcAdminOrder(String orderCode, boolean requireLocalPaid) {
 		if (!StringUtils.hasLength(orderCode)) {
 			throw new ServiceException("订单号不能为空");
 		}
@@ -1257,7 +1262,11 @@
 				.eq(TChargingOrder::getDelFlag, 0)
 				.last("limit 1"));
 		try {
-			IcbcAdminFundOperationSupport.validateOrder(order);
+			if (requireLocalPaid) {
+				IcbcAdminFundOperationSupport.validateOrder(order);
+			} else {
+				IcbcAdminFundOperationSupport.validateIcbcOrder(order);
+			}
 		} catch (IllegalArgumentException e) {
 			throw new ServiceException(e.getMessage());
 		}
diff --git a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupportTest.java b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupportTest.java
index 41d9cb3..f69f7d4 100644
--- a/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupportTest.java
+++ b/ruoyi-service/ruoyi-order/src/test/java/com/ruoyi/order/service/impl/IcbcAdminFundOperationSupportTest.java
@@ -3,6 +3,7 @@
 import com.ruoyi.order.api.model.TChargingOrder;
 import com.ruoyi.order.api.model.TChargingOrderShareRecord;
 import com.ruoyi.payment.api.vo.IcbcRefundReq;
+import com.ruoyi.payment.api.vo.IcbcPaymentResp;
 import com.ruoyi.payment.api.vo.IcbcSubOrderQueryReq;
 import com.ruoyi.payment.api.vo.IcbcSubOrderRefundReq;
 import org.junit.jupiter.api.Test;
@@ -29,6 +30,28 @@
     }
 
     @Test
+    void acceptsBankPaidOrderWhenLocalCallbackStatusIsStillPending() {
+        TChargingOrder order = paidOrder();
+        order.setRechargePaymentStatus(1);
+        IcbcAdminFundOperationSupport.validateIcbcOrder(order);
+
+        IcbcPaymentResp payment = new IcbcPaymentResp();
+        payment.setStatus("0");
+        payment.setRawResponse("{\"returnCode\":0,\"pay_status\":\"0\",\"order_id\":\"231011990056000012345\"}");
+
+        assertTrue(IcbcAdminFundOperationSupport.isBankPaid(payment));
+    }
+
+    @Test
+    void rejectsBankQueryThatDoesNotConfirmPayment() {
+        IcbcPaymentResp payment = new IcbcPaymentResp();
+        payment.setStatus("1");
+        payment.setRawResponse("{\"returnCode\":0,\"pay_status\":\"1\"}");
+
+        assertFalse(IcbcAdminFundOperationSupport.isBankPaid(payment));
+    }
+
+    @Test
     void buildsMainOrderRefundFromBusinessOrder() {
         IcbcRefundReq request = IcbcAdminFundOperationSupport.buildRefundRequest(
                 paidOrder(), new BigDecimal("0.12"), "ICBCADMINREFUND001");
diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/service/impl/IcbcPayServiceImpl.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/service/impl/IcbcPayServiceImpl.java
index 20196ab..ccd2a40 100644
--- a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/service/impl/IcbcPayServiceImpl.java
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/icbc/service/impl/IcbcPayServiceImpl.java
@@ -37,6 +37,7 @@
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import java.net.URI;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
@@ -581,7 +582,7 @@
             String signType = firstNotBlank(params.get("sign_type"), properties.getSignType());
             String publicKey = normalizePublicKey(properties.getIcbcPublicKey());
             log.info("工行回调验签参数,signType:{},publicKeyLength:{},content:{}", signType, publicKey == null ? 0 : publicKey.length(), content);
-            return IcbcSignature.verify(content, signType, sign, properties.getCharset(), publicKey);
+            return IcbcSignature.verify(content, signType, publicKey, properties.getCharset(), sign);
         } catch (Exception e) {
             log.error("工行回调验签异常", e);
             return false;
@@ -628,9 +629,8 @@
 
     private String buildSignContent(Map<String, String> params) {
         Map<String, String> sorted = new TreeMap<>(params);
-        String api = sorted.remove("api");
         sorted.remove("sign");
-        StringBuilder content = new StringBuilder(firstNotBlank(api, ""));
+        StringBuilder content = new StringBuilder(resolveNotifyPath());
         content.append('?');
         boolean hasParam = false;
         for (Map.Entry<String, String> entry : sorted.entrySet()) {
@@ -646,6 +646,19 @@
         return content.toString();
     }
 
+    private String resolveNotifyPath() {
+        String notifyUrl = properties.getNotifyUrl();
+        if (StringUtils.isEmpty(notifyUrl)) {
+            return "";
+        }
+        try {
+            return firstNotBlank(URI.create(notifyUrl).getPath(), notifyUrl);
+        } catch (IllegalArgumentException e) {
+            log.warn("工行回调地址格式无效,无法提取验签路径:{}", notifyUrl);
+            return notifyUrl;
+        }
+    }
+
     private boolean isPaidStatus(String status) {
         return Arrays.asList("1", "2", "SUCCESS", "PAID", "PAY_SUCCESS", "TRADE_SUCCESS").contains(status);
     }

--
Gitblit v1.7.1