From d10e18bc5b40cbc943736f1d1cb62aa859bb0116 Mon Sep 17 00:00:00 2001
From: longz <longzhaoz@qq.com>
Date: 星期四, 09 七月 2026 10:05:47 +0800
Subject: [PATCH] 支付对接

---
 ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/IcbcPayController.java |  250 ++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 242 insertions(+), 8 deletions(-)

diff --git a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/IcbcPayController.java b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/IcbcPayController.java
index 0e4a833..52bdbb1 100644
--- a/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/IcbcPayController.java
+++ b/ruoyi-service/ruoyi-payment/src/main/java/com/ruoyi/payment/controller/IcbcPayController.java
@@ -1,5 +1,7 @@
 package com.ruoyi.payment.controller;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.ruoyi.common.core.domain.R;
 import com.ruoyi.order.api.feignClient.ChargingOrderClient;
 import com.ruoyi.payment.api.vo.IcbcPaymentReq;
@@ -8,32 +10,178 @@
 import com.ruoyi.payment.api.vo.IcbcRefundResp;
 import com.ruoyi.payment.api.vo.IcbcSubOrderQueryReq;
 import com.ruoyi.payment.api.vo.IcbcSubOrderQueryResp;
+import com.ruoyi.payment.api.vo.IcbcSubOrderRefundReq;
+import com.ruoyi.payment.api.vo.IcbcSubOrderRefundResp;
 import com.ruoyi.payment.api.vo.IcbcSubOrderSplitReq;
 import com.ruoyi.payment.api.vo.IcbcSubOrderSplitResp;
+import com.ruoyi.payment.icbc.config.IcbcPayProperties;
 import com.ruoyi.payment.icbc.service.IcbcPayService;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedReader;
+import java.math.BigDecimal;
 import java.io.PrintWriter;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Enumeration;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
  * 工行统一支付入口。
  */
+@Slf4j
 @RestController
 @RequestMapping("/icbc")
 public class IcbcPayController {
+    private static final String TEST_ORDER_PREFIX = "ICBCTEST";
+    private static final String TEST_SUB_MER_ID = "231011990055";
+    private static final String TEST_SUB_MER_PRTCL_NO = "2310119900550201";
+
     @Resource
     private IcbcPayService icbcPayService;
+
+    @Resource
+    private IcbcPayProperties icbcPayProperties;
 
     @Resource
     private ChargingOrderClient chargingOrderClient;
 
     @PostMapping("/payment")
     public R<IcbcPaymentResp> payment(@RequestBody IcbcPaymentReq req) {
-        return R.ok(icbcPayService.payment(req));
+        log.info("工行支付请求入参:{}", JSON.toJSONString(req));
+        IcbcPaymentResp resp = icbcPayService.payment(req);
+        log.info("工行支付返回前端:{}", JSON.toJSONString(resp));
+        return R.ok(resp);
+    }
+
+    @PostMapping("/test/payment")
+    public R<IcbcPaymentResp> testPayment(@RequestBody(required = false) IcbcPaymentReq req) {
+        log.info("工行测试支付请求入参:{}", JSON.toJSONString(req));
+        IcbcPaymentReq paymentReq = req == null ? new IcbcPaymentReq() : req;
+        paymentReq.setOutTradeNo(TEST_ORDER_PREFIX + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
+        if (paymentReq.getAmount() == null || paymentReq.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            paymentReq.setAmount(new BigDecimal("0.01"));
+        }
+        if (paymentReq.getSubject() == null || paymentReq.getSubject().trim().isEmpty()) {
+            paymentReq.setSubject("工行测试支付");
+        }
+        if (paymentReq.getBody() == null || paymentReq.getBody().trim().isEmpty()) {
+            paymentReq.setBody("工行测试支付");
+        }
+        if (paymentReq.getPaymentType() == null) {
+            paymentReq.setPaymentType(1);
+        }
+        log.info("工行测试支付实际下单参数:{}", JSON.toJSONString(paymentReq));
+        IcbcPaymentResp resp = icbcPayService.payment(paymentReq);
+        log.info("工行测试支付返回前端:{}", JSON.toJSONString(resp));
+        return R.ok(resp);
+    }
+
+    @PostMapping("/test/query")
+    public R<IcbcPaymentResp> testQuery(@RequestBody Map<String, Object> req) {
+        String outTradeNo = getString(req, "outTradeNo");
+        log.info("工行测试查询请求入参:{}", JSON.toJSONString(req));
+        IcbcPaymentResp resp = icbcPayService.query(outTradeNo);
+        log.info("工行测试查询返回前端:{}", JSON.toJSONString(resp));
+        return R.ok(resp);
+    }
+
+    @PostMapping("/test/refund")
+    public R<IcbcRefundResp> testRefund(@RequestBody(required = false) IcbcRefundReq req) {
+        req = req == null ? new IcbcRefundReq() : req;
+        log.info("工行测试退款请求入参:{}", JSON.toJSONString(req));
+        if (req.getRefundAmount() == null || req.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            req.setRefundAmount(new BigDecimal("0.01"));
+        }
+        if (req.getTotalAmount() == null || req.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            req.setTotalAmount(req.getRefundAmount());
+        }
+        if (req.getOutRefundNo() == null || req.getOutRefundNo().trim().isEmpty()) {
+            req.setOutRefundNo("ICBCREFUND" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
+        }
+        if (req.getReason() == null || req.getReason().trim().isEmpty()) {
+            req.setReason("工行测试退款");
+        }
+        log.info("工行测试退款实际参数:{}", JSON.toJSONString(req));
+        IcbcRefundResp resp = icbcPayService.refund(req);
+        log.info("工行测试退款返回前端:{}", JSON.toJSONString(resp));
+        return R.ok(resp);
+    }
+
+    @PostMapping("/test/sub-order/split")
+    public R<IcbcSubOrderSplitResp> testSplitSubOrder(@RequestBody(required = false) IcbcSubOrderSplitReq req) {
+        req = req == null ? new IcbcSubOrderSplitReq() : req;
+        log.info("工行测试分账请求入参:{}", JSON.toJSONString(req));
+        if (req.getSubOrderId() == null || req.getSubOrderId().trim().isEmpty()) {
+            req.setSubOrderId("ICBCSUB" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
+        }
+        if (req.getSeqNo() == null || req.getSeqNo().trim().isEmpty()) {
+            req.setSeqNo(req.getChargingOrderCode());
+        }
+        if (req.getBusiType() == null || req.getBusiType().trim().isEmpty()) {
+            req.setBusiType("1");
+        }
+        if (req.getOriTrxDate() == null || req.getOriTrxDate().trim().isEmpty()) {
+            req.setOriTrxDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+        }
+        if (req.getClassifyAmt() == null || req.getClassifyAmt().compareTo(BigDecimal.ZERO) <= 0) {
+            req.setClassifyAmt(new BigDecimal("0.01"));
+        }
+        if (req.getSubMerId() == null || req.getSubMerId().trim().isEmpty()) {
+            req.setSubMerId(TEST_SUB_MER_ID);
+        }
+        if (req.getSubMerPrtclNo() == null || req.getSubMerPrtclNo().trim().isEmpty()) {
+            req.setSubMerPrtclNo(TEST_SUB_MER_PRTCL_NO);
+        }
+        if (req.getOriMerId() == null || req.getOriMerId().trim().isEmpty()) {
+            req.setOriMerId(icbcPayProperties.getMerId());
+        }
+        log.info("工行测试分账实际参数:{}", JSON.toJSONString(req));
+        IcbcSubOrderSplitResp resp = icbcPayService.splitSubOrder(req);
+        log.info("工行测试分账返回前端:{}", JSON.toJSONString(resp));
+        return R.ok(resp);
+    }
+
+    @PostMapping("/test/sub-order/refund")
+    public R<IcbcSubOrderRefundResp> testRefundSubOrder(@RequestBody(required = false) IcbcSubOrderRefundReq req) {
+        req = req == null ? new IcbcSubOrderRefundReq() : req;
+        log.info("工行测试分账退款请求入参:{}", JSON.toJSONString(req));
+        if (req.getSeqNo() == null || req.getSeqNo().trim().isEmpty()) {
+            req.setSeqNo(req.getChargingOrderCode());
+        }
+        if (req.getBusiType() == null || req.getBusiType().trim().isEmpty()) {
+            req.setBusiType("1");
+        }
+        if (req.getOriTrxDate() == null || req.getOriTrxDate().trim().isEmpty()) {
+            req.setOriTrxDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
+        }
+        if (req.getRefundAmount() == null || req.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0) {
+            req.setRefundAmount(new BigDecimal("0.01"));
+        }
+        if (req.getRetSubOrderId() == null || req.getRetSubOrderId().trim().isEmpty()) {
+            req.setRetSubOrderId("ICBCSUBREFUND" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
+        }
+        if (req.getOuttrxSerialNo() == null || req.getOuttrxSerialNo().trim().isEmpty()) {
+            req.setOuttrxSerialNo("ICBCSUBREFUNDNO" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")));
+        }
+        if (req.getSubMerId() == null || req.getSubMerId().trim().isEmpty()) {
+            req.setSubMerId(TEST_SUB_MER_ID);
+        }
+        if (req.getSubMerPrtclNo() == null || req.getSubMerPrtclNo().trim().isEmpty()) {
+            req.setSubMerPrtclNo(TEST_SUB_MER_PRTCL_NO);
+        }
+        if (req.getOriMerId() == null || req.getOriMerId().trim().isEmpty()) {
+            req.setOriMerId(icbcPayProperties.getMerId());
+        }
+        log.info("工行测试分账退款实际参数:{}", JSON.toJSONString(req));
+        IcbcSubOrderRefundResp resp = icbcPayService.refundSubOrder(req);
+        log.info("工行测试分账退款返回前端:{}", JSON.toJSONString(resp));
+        return R.ok(resp);
     }
 
     @PostMapping("/query")
@@ -56,25 +204,48 @@
         return R.ok(icbcPayService.splitSubOrder(req));
     }
 
+    @PostMapping("/sub-order/refund")
+    public R<IcbcSubOrderRefundResp> refundSubOrder(@RequestBody IcbcSubOrderRefundReq req) {
+        return R.ok(icbcPayService.refundSubOrder(req));
+    }
+
     @PostMapping("/sub-order/query")
     public R<IcbcSubOrderQueryResp> querySubOrder(@RequestBody IcbcSubOrderQueryReq req) {
         return R.ok(icbcPayService.querySubOrder(req));
     }
 
-    @PostMapping("/callBack")
+    @RequestMapping(value = "/callBack", method = {RequestMethod.GET, RequestMethod.POST})
     public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
-        Map<String, String> callback = icbcPayService.parseNotify(request.getParameterMap());
-        dispatchChargingOrderCallback(callback);
-        PrintWriter writer = response.getWriter();
-        writer.println("success");
-        writer.flush();
-        writer.close();
+        Map<String, String[]> requestParams = new LinkedHashMap<>(request.getParameterMap());
+        String rawBody = requestParams.isEmpty() ? readRequestBody(request) : "";
+        mergeJsonBody(requestParams, rawBody);
+        log.info("工行支付回调原始请求,method:{},uri:{},query:{},contentType:{},params:{},body:{},headers:{}",
+                request.getMethod(),
+                request.getRequestURI(),
+                request.getQueryString(),
+                request.getContentType(),
+                JSON.toJSONString(requestParams),
+                rawBody,
+                summarizeHeaders(request));
+        try {
+            Map<String, String> callback = icbcPayService.parseNotify(requestParams);
+            log.info("工行支付回调解析结果:{}", JSON.toJSONString(callback));
+            dispatchChargingOrderCallback(callback);
+        } catch (Exception e) {
+            log.error("工行支付回调处理失败,已记录原始请求并返回success避免重复通知", e);
+        } finally {
+            writeCallbackResponse(response);
+        }
     }
 
     private void dispatchChargingOrderCallback(Map<String, String> callback) {
         String outTradeNo = callback.get("out_trade_no");
         String transactionId = callback.get("transaction_id");
         String attach = callback.get("attach");
+        if (outTradeNo != null && outTradeNo.startsWith(TEST_ORDER_PREFIX)) {
+            log.info("工行测试支付回调成功,订单号:{},流水号:{},支付类型:{}", outTradeNo, transactionId, attach);
+            return;
+        }
         Integer paymentType = Integer.valueOf(attach);
         if (Integer.valueOf(1).equals(paymentType)) {
             chargingOrderClient.chargingOrderWXCallback(outTradeNo, transactionId, attach);
@@ -90,4 +261,67 @@
         }
         throw new IllegalArgumentException("工行回调支付类型不支持:" + attach);
     }
+
+    private String getString(Map<String, Object> req, String key) {
+        if (req == null || req.get(key) == null) {
+            throw new IllegalArgumentException("缺少参数:" + key);
+        }
+        return String.valueOf(req.get(key));
+    }
+
+    private String readRequestBody(HttpServletRequest request) {
+        StringBuilder body = new StringBuilder();
+        try (BufferedReader reader = request.getReader()) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                body.append(line);
+            }
+        } catch (Exception e) {
+            log.warn("读取工行支付回调body失败", e);
+        }
+        return body.toString();
+    }
+
+    private void mergeJsonBody(Map<String, String[]> requestParams, String rawBody) {
+        if (rawBody == null || rawBody.trim().isEmpty()) {
+            return;
+        }
+        try {
+            JSONObject jsonObject = JSON.parseObject(rawBody);
+            if (jsonObject == null) {
+                return;
+            }
+            for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
+                if (entry.getValue() != null && !requestParams.containsKey(entry.getKey())) {
+                    requestParams.put(entry.getKey(), new String[]{String.valueOf(entry.getValue())});
+                }
+            }
+        } catch (Exception e) {
+            log.warn("工行支付回调body不是JSON格式,body:{}", rawBody);
+        }
+    }
+
+    private String summarizeHeaders(HttpServletRequest request) {
+        Map<String, String> headers = new LinkedHashMap<>();
+        Enumeration<String> names = request.getHeaderNames();
+        while (names != null && names.hasMoreElements()) {
+            String name = names.nextElement();
+            if ("user-agent".equalsIgnoreCase(name)
+                    || "host".equalsIgnoreCase(name)
+                    || "x-forwarded-for".equalsIgnoreCase(name)
+                    || "x-real-ip".equalsIgnoreCase(name)) {
+                headers.put(name, request.getHeader(name));
+            }
+        }
+        return JSON.toJSONString(headers);
+    }
+
+    private void writeCallbackResponse(HttpServletResponse response) throws Exception {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType("text/plain;charset=UTF-8");
+        PrintWriter writer = response.getWriter();
+        writer.println("success");
+        writer.flush();
+        writer.close();
+    }
 }

--
Gitblit v1.7.1