You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular与Spring Boot集成Paytm支付网关跳转异常求助

Paytm支付网关集成问题(Spring Boot + Angular)

尝试将Paytm支付网关集成到Spring Boot后端+Angular前端的项目中,目前能成功调用后端控制器,但无法跳转到Paytm页面完成支付——Paytm的UI会在router outlet div中短暂加载后立即消失。控制台报CORS相关错误,推测和Angular的HTTP请求或路由有关,但不确定具体问题点。


Spring Boot控制器代码

@Log4j2
@RestController
@RequestMapping("/booking")
public class BookingController {

    @Autowired
    private PaytmDetails paytmDetails;

    @Value("${paytm.mobile}")
    private String paytmMobile;
    @Value("${paytm.email}")
    private String paytmEmail;

    @PostMapping(value = "/make-payment")
    public ModelAndView getPaymentRedirect(@RequestParam String orderId, @RequestParam String txnAmount, @RequestParam String customerId) throws Exception {
        log.info("1=========");
        ModelAndView modelAndView = new ModelAndView("redirect:" + paytmDetails.getPaytmUrl());
        log.info("2=========");
        TreeMap<String, String> parameters = new TreeMap<>();
        paytmDetails.getDetails().forEach((k, v) -> parameters.put(k, v));
        parameters.put("MOBILE_NO", paytmMobile);
        parameters.put("EMAIL", paytmEmail);
        parameters.put("ORDER_ID", orderId);
        parameters.put("TXN_AMOUNT", txnAmount);
        parameters.put("CUST_ID", customerId);
        String checkSum = getCheckSum(parameters);
        parameters.put("CHECKSUMHASH", checkSum);
        log.info("3=========");
        modelAndView.addAllObjects(parameters);
        return modelAndView;
    }

    @PostMapping(value = "/payment-response")
    public ModelAndView getPaymentResponseRedirect(HttpServletRequest request) {
        ModelAndView modelAndView = new ModelAndView("redirect:http://localhost:8080/#/payment");//my angular payment response landing url
        Map<String, String[]> mapData = request.getParameterMap();
        TreeMap<String, String> parameters = new TreeMap<String, String>();
        String paytmChecksum = "";
        for (Entry<String, String[]> requestParamsEntry : mapData.entrySet()) {
            if ("CHECKSUMHASH".equalsIgnoreCase(requestParamsEntry.getKey())) {
                paytmChecksum = requestParamsEntry.getValue()[0];
            } else {
                parameters.put(requestParamsEntry.getKey(), requestParamsEntry.getValue()[0]);
            }
        }
        String result;
        boolean isValideChecksum = false;
        try {
            isValideChecksum = validateCheckSum(parameters, paytmChecksum);
            if (isValideChecksum && parameters.containsKey("RESPCODE")) {
                if (parameters.get("RESPCODE").equals("01")) {
                    result = "Payment Successful";
                } else {
                    result = "Payment Failed";
                }
            } else {
                result = "Checksum mismatched";
            }
        } catch (Exception e) {
            result = e.toString();
        }
        modelAndView.addObject("result", result);
        parameters.remove("CHECKSUMHASH");
        modelAndView.addObject("parameters", parameters);
        return modelAndView;
    }

    private boolean validateCheckSum(TreeMap<String, String> parameters, String paytmChecksum) throws Exception {
        return PaytmChecksum.verifySignature(parameters, paytmDetails.getMerchantKey(), paytmChecksum);
    }

    private String getCheckSum(TreeMap<String, String> parameters) throws Exception {
        return PaytmChecksum.generateSignature(parameters, paytmDetails.getMerchantKey());
    }

}

Paytm实体类代码

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Component
@ConfigurationProperties("paytm.payment.sandbox")
public class PaytmDetails {

    private String merchantId;
    private String merchantKey;
    private String channelId;
    private String website;
    private String industryTypeId;
    private String paytmUrl;
    private String callbackUrl;
    private Map<String, String> details;

}

配置文件内容

paytm.payment.sandbox.merchantId=<test valid paytm MID>
paytm.payment.sandbox.merchantKey=<tes valid paytm MK>
paytm.payment.sandbox.channelId=WEB
paytm.payment.sandbox.industryTypeId=Retail
paytm.payment.sandbox.website=WEBSTAGING
paytm.payment.sandbox.paytmUrl=https://securegw-stage.paytm.in/order/process
paytm.payment.sandbox.callbackUrl=http://localhost:8080/user/booking/payment-response
paytm.payment.sandbox.details.MID=${paytm.payment.sandbox.merchantId}
paytm.payment.sandbox.details.CHANNEL_ID=${paytm.payment.sandbox.channelId}
paytm.payment.sandbox.details.INDUSTRY_TYPE_ID=${paytm.payment.sandbox.industryTypeId}
paytm.payment.sandbox.details.WEBSITE=${paytm.payment.sandbox.website}
paytm.payment.sandbox.details.CALLBACK_URL=${paytm.payment.sandbox.callbackUrl}
paytm.mobile=<paytm login mobile>
paytm.email=<paytm login email>

Angular组件类方法

bookProduct() {
    this.availabilityService.bookAvailabileProduct(this.orderId, this.txnAmount, this.customerId).subscribe(
    res => {
      //nothing
    }, err => {
      this.availabilityErrorMsg = "Backend Error!";
      this.availabilityError = true;
    }
  );
}

Angular服务类方法

public bookAvailabileProduct(orderId:any, txnAmount:any, customerId:any): Observable<any> {
    return this.http.post("/booking//make-payment?orderId="+orderId+"&txnAmount="+txnAmount+"&customerId="+customerId, null);
}

控制台报错信息

Access to XMLHttpRequest at 'https://securegw-stage.paytm.in/order/process?CALLBACK_URL=http%3A%2F%2Flocalhost%3A8080%2Fuser%2Fbooking%2Fpayment-response&CHANNEL_ID=WEB&CHECKSUMHASH=D9DRS%2Bn0TfhKMW%2FY6brrKt5%2BbcTtyuUPlWmQ%2BUNIKr7klkSw%3D&CUST_ID=62f5c339ffc4f516cfeda&EMAIL=shashiranjan.sit%40gmail.com&INDUSTRY_TYPE_ID=Retail&MID=Mo144&MOBILE_NO=9538320000&ORDER_ID=6314be8cff&TXN_AMOUNT=1.0&WEBSITE=WEBSTAGING' (redirected from 'http://localhost:8080/user/booking/make-payment') from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

问题分析与解决办法

核心问题

用Angular的HttpClient.post发起请求时,后端返回的redirect会被XHR请求拦截,而Paytm域名不在CORS允许列表内,导致跨域报错。同时XHR无法处理页面跳转逻辑,所以Paytm页面只会短暂加载后消失。

解决步骤

  1. 修改后端接口返回类型
    将/make-payment接口的返回从ModelAndView改为包含Paytm跳转参数的JSON对象,不再直接重定向:

    @PostMapping(value = "/make-payment")
    public Map<String, String> getPaymentParams(@RequestParam String orderId, @RequestParam String txnAmount, @RequestParam String customerId) throws Exception {
        TreeMap<String, String> parameters = new TreeMap<>();
        paytmDetails.getDetails().forEach((k, v) -> parameters.put(k, v));
        parameters.put("MOBILE_NO", paytmMobile);
        parameters.put("EMAIL", paytmEmail);
        parameters.put("ORDER_ID", orderId);
        parameters.put("TXN_AMOUNT", txnAmount);
        parameters.put("CUST_ID", customerId);
        String checkSum = getCheckSum(parameters);
        parameters.put("CHECKSUMHASH", checkSum);
        return parameters;
    }
    
  2. 前端处理跳转逻辑
    在Angular组件中,拿到后端返回的参数后,动态创建表单并提交到Paytm的URL,替代XHR请求:

    bookProduct() {
        this.availabilityService.bookAvailabileProduct(this.orderId, this.txnAmount, this.customerId).subscribe(
            res => {
                // 动态创建表单提交到Paytm
                const form = document.createElement('form');
                form.method = 'POST';
                form.action = 'https://securegw-stage.paytm.in/order/process';
                // 遍历参数添加到表单
                Object.keys(res).forEach(key => {
                    const input = document.createElement('input');
                    input.type = 'hidden';
                    input.name = key;
                    input.value = res[key];
                    form.appendChild(input);
                });
                document.body.appendChild(form);
                form.submit();
            }, err => {
                this.availabilityErrorMsg = "Backend Error!";
                this.availabilityError = true;
            }
        );
    }
    
  3. 修正服务类的请求URL
    服务类URL存在多余斜杠,将"/booking//make-payment"改为"/booking/make-payment":

    public bookAvailabileProduct(orderId:any, txnAmount:any, customerId:any): Observable<any> {
        return this.http.post("/booking/make-payment?orderId="+orderId+"&txnAmount="+txnAmount+"&customerId="+customerId, null);
    }
    
  4. 回调URL参数传递优化
    后端/payment-response重定向到Angular页面时,用查询参数传递结果,确保前端能正确接收:

    // 编码参数避免乱码
    String encodedResult = URLEncoder.encode(result, "UTF-8");
    modelAndView.setViewName("redirect:http://localhost:8080/#/payment?result=" + encodedResult);
    

内容的提问来源于stack exchange,提问作者Shashi Ranjan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:35:24