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); } 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) { 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()); } 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"); 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); }