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页面只会短暂加载后消失。
解决步骤
修改后端接口返回类型
将/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; }前端处理跳转逻辑
在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; } ); }修正服务类的请求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); }回调URL参数传递优化
后端/payment-response重定向到Angular页面时,用查询参数传递结果,确保前端能正确接收:// 编码参数避免乱码 String encodedResult = URLEncoder.encode(result, "UTF-8"); modelAndView.setViewName("redirect:http://localhost:8080/#/payment?result=" + encodedResult);
内容的提问来源于stack exchange,提问作者Shashi Ranjan
相关产品推荐
相关产品推荐

