liujie
2026-08-04 e9e42b621afdf6ac94f5327ee843b32b10a401b4
backend/src/main/java/com/tourguide/common/huifu/DouGongNotifyController.java
@@ -1,17 +1,14 @@
package com.tourguide.common.huifu;
import com.alibaba.fastjson.JSONObject;
import com.huifu.bspay.sdk.opps.core.utils.RsaUtils;
import com.tourguide.common.huifu.properties.HuiFuProperties;
import com.tourguide.modules.order.service.HuifuPaymentCallbackService;
import com.tourguide.modules.order.service.HuifuRefundCallbackService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
 * 斗拱回调入口。
@@ -23,53 +20,71 @@
public class DouGongNotifyController {
    private static final Logger log = LoggerFactory.getLogger(DouGongNotifyController.class);
    @Autowired
    private HuiFuProperties huiFuProperties;
    private final HuifuCallbackVerifier callbackVerifier;
    private final HuifuPaymentCallbackService paymentCallbackService;
    private final HuifuRefundCallbackService refundCallbackService;
    public DouGongNotifyController(HuifuCallbackVerifier callbackVerifier,
                                   HuifuPaymentCallbackService paymentCallbackService,
                                   HuifuRefundCallbackService refundCallbackService) {
        this.callbackVerifier = callbackVerifier;
        this.paymentCallbackService = paymentCallbackService;
        this.refundCallbackService = refundCallbackService;
    }
    @PostMapping("/pay-notify")
    public String payNotify(HttpServletRequest request) {
        return handleNotify("dougong-pay", request);
    public String payNotify(@RequestParam(value = "sign", required = false) String sign,
                            @RequestParam(value = "resp_data", required = false) String respData) {
        try {
            JSONObject body = callbackVerifier.verify(sign, respData);
            paymentCallbackService.handle(body);
            return "200";
        } catch (Exception exception) {
            log.warn("dougong-pay callback failed: {}", exception.getMessage());
            return "FAIL";
        }
    }
    @PostMapping("/refund-notify")
    public String refundNotify(@RequestParam(value = "sign", required = false) String sign,
                               @RequestParam(value = "resp_data", required = false) String respData) {
        try {
            JSONObject body = callbackVerifier.verify(sign, respData);
            refundCallbackService.handle(body);
            return "200";
        } catch (Exception exception) {
            log.warn("dougong-refund callback failed: {}", exception.getMessage());
            return "FAIL";
        }
    }
    @PostMapping("/refund-logistics")
    public String refundLogistics(HttpServletRequest request) {
        return handleNotify("dougong-refund-logistics", request);
    public String refundLogistics(@RequestParam(value = "sign", required = false) String sign,
                                  @RequestParam(value = "resp_data", required = false) String respData) {
        return handleNotify("dougong-refund-logistics", sign, respData);
    }
    @PostMapping("/refund-crosscity")
    public String refundCrossCity(HttpServletRequest request) {
        return handleNotify("dougong-refund-crosscity", request);
    public String refundCrossCity(@RequestParam(value = "sign", required = false) String sign,
                                  @RequestParam(value = "resp_data", required = false) String respData) {
        return handleNotify("dougong-refund-crosscity", sign, respData);
    }
    @PostMapping("/encashment")
    public String encashment(HttpServletRequest request) {
        return handleNotify("dougong-encashment", request);
    public String encashment(@RequestParam(value = "sign", required = false) String sign,
                             @RequestParam(value = "resp_data", required = false) String respData) {
        return handleNotify("dougong-encashment", sign, respData);
    }
    private String handleNotify(String bizName, HttpServletRequest request) {
        String sign = request.getParameter("sign");
        String respData = request.getParameter("resp_data");
        if (!StringUtils.hasText(sign) || !StringUtils.hasText(respData)) {
            log.warn("{} missing params, sign={}, resp_data={}", bizName, sign, respData);
            return "FAIL";
        }
        // 备注:斗拱回调可能把括号转义为带空格的 HTML 实体,验签前必须恢复原文,否则签名会不一致。
        String normalizedRespData = respData.replaceAll("& #40;", "(").replaceAll("& #41;", ")");
        boolean verified = RsaUtils.verify(normalizedRespData, huiFuProperties.getRsaPublicKey(), sign);
        if (!verified) {
            log.warn("{} signature verify failed, resp_data={}", bizName, normalizedRespData);
            return "FAIL";
        }
        JSONObject body = JSONObject.parseObject(normalizedRespData);
        // 备注:当前阶段先记录回调关键流水,后续订单支付确认、退款确认、提现确认都应从这里分发到幂等服务。
        log.info("{} verified, req_seq_id={}, org_req_seq_id={}, trans_stat={}, body={}",
                bizName,
                body.getString("req_seq_id"),
                body.getString("org_req_seq_id"),
                body.getString("trans_stat"),
                body);
    private String handleNotify(String bizName, String sign, String respData) {
        try {
            JSONObject body = callbackVerifier.verify(sign, respData);
            log.info("{} verified, req_seq_id={}, org_req_seq_id={}, trans_stat={}", bizName,
                    body.getString("req_seq_id"), body.getString("org_req_seq_id"), body.getString("trans_stat"));
        return "200";
        } catch (Exception exception) {
            log.warn("{} callback failed: {}", bizName, exception.getMessage());
            return "FAIL";
        }
    }
}