longz
2026-07-09 d10e18bc5b40cbc943736f1d1cb62aa859bb0116
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();
    }
}