Vaadin 24+Spring Boot 3集成Razorpay支付网关求助
集成Razorpay支付网关到Spring Boot 3 + Vaadin 24应用
前置条件
- 已获取Razorpay的
key_id和key_secret - 已在POM中添加
com.razorpay:razorpay-java:1.4.4依赖 - 已编写基础的
PaymentView视图类
解决方案步骤
1. 改造PaymentView,加载脚本并唤起结账弹窗
修改原PaymentView,添加动态加载Razorpay脚本、生成订单后唤起结账弹窗的逻辑,同时处理支付回调:
@PageTitle("Payment") @PermitAll @Route(value = "payment", layout = MainLayout.class) public class PaymentView extends VerticalLayout { RazorpayClient razorpayClient; private static final String RAZORPAY_KEY_ID = "myKeyId"; private static final String RAZORPAY_KEY_SECRET = "myKeySecret"; public PaymentView() { PaymentUI paymentUI = new PaymentUI(); add(paymentUI); // 动态加载Razorpay Checkout脚本 Page.getCurrent().addJavaScript("https://checkout.razorpay.com/v1/checkout.js"); try { razorpayClient = new RazorpayClient(RAZORPAY_KEY_ID, RAZORPAY_KEY_SECRET); } catch (RazorpayException ex) { throw new RuntimeException(ex); } paymentUI.btnPay.addClickListener(e -> { try { final Order razorPayOrder = createRazorPayOrder(); // 唤起Razorpay结账弹窗 triggerRazorpayCheckout(razorPayOrder); } catch (RazorpayException ex) { throw new RuntimeException(ex); } }); } private Order createRazorPayOrder() throws RazorpayException { JSONObject options = new JSONObject(); options.put("amount", 100); // 单位:货币 subunits,此处100代表1 INR options.put("currency", "INR"); options.put("receipt", "txn_" + System.currentTimeMillis()); // 用时间戳生成唯一收据号 return razorpayClient.orders.create(options); } // 唤起Razorpay结账弹窗的核心方法 private void triggerRazorpayCheckout(Order razorPayOrder) { // 构造JS所需参数,可根据业务调整商家名称、描述等 String optionsJson = String.format(""" { "key": "%s", "amount": "%s", "currency": "INR", "name": "你的商家名称", "description": "测试支付订单", "order_id": "%s", "handler": function(response) { // 支付成功后请求后端验证签名 fetch('/payment/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ paymentId: response.razorpay_payment_id, orderId: response.razorpay_order_id, signature: response.razorpay_signature }) }).then(res => res.json()) .then(data => { data.success ? alert('支付成功!') : alert('支付验证失败,请联系客服'); // 此处可添加UI更新、页面跳转等逻辑 }).catch(() => alert('支付结果处理出错')); }, "prefill": { "name": "默认用户名", "email": "默认邮箱@example.com", "contact": "1000000000" }, "theme": { "color": "#3399cc" }, "modal": { "ondismiss": function() { alert('支付已取消'); } } }""", RAZORPAY_KEY_ID, razorPayOrder.get("amount"), razorPayOrder.get("id")); // 执行JS代码唤起弹窗 String jsCode = String.format(""" var rzpInstance = new Razorpay(%s); rzpInstance.open(); rzpInstance.on('payment.failed', function(res) { alert('支付失败:' + res.error.description); }); """, optionsJson); UI.getCurrent().getPage().executeJs(jsCode); } }
2. 新增后端签名验证接口
创建REST控制器处理支付结果的签名验证,防止前端篡改数据:
@RestController @RequestMapping("/payment") public class PaymentValidationController { private static final String RAZORPAY_KEY_SECRET = "myKeySecret"; @PostMapping("/verify") public ResponseEntity<Map<String, Boolean>> validatePayment(@RequestBody PaymentValidationRequest request) { Map<String, Boolean> result = new HashMap<>(); try { // 用Razorpay SDK验证签名 JSONObject validationParams = new JSONObject(); validationParams.put("razorpay_order_id", request.getOrderId()); validationParams.put("razorpay_payment_id", request.getPaymentId()); validationParams.put("razorpay_signature", request.getSignature()); boolean isValid = Utils.verifyPaymentSignature(validationParams, RAZORPAY_KEY_SECRET); result.put("success", isValid); return ResponseEntity.ok(result); } catch (Exception e) { result.put("success", false); return ResponseEntity.badRequest().body(result); } } // 请求体DTO public static class PaymentValidationRequest { private String paymentId; private String orderId; private String signature; // Getter & Setter public String getPaymentId() { return paymentId; } public void setPaymentId(String paymentId) { this.paymentId = paymentId; } public String getOrderId() { return orderId; } public void setOrderId(String orderId) { this.orderId = orderId; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } } }
3. 关键注意事项
- 签名验证必须在后端执行:前端回调结果存在被篡改风险,必须通过Razorpay官方SDK在后端完成签名验证,确保支付真实有效。
- 金额单位规则:Razorpay的
amount参数为货币最小单位(如INR的单位是paise),1元人民币对应100的amount值。 - 订单唯一性:建议用时间戳或UUID生成唯一收据号,避免重复订单。
- 用户信息优化:可从Vaadin当前登录用户中获取姓名、邮箱等信息,替换
prefill中的默认值,提升支付体验。
内容的提问来源于stack exchange,提问作者Renoj Joseph
相关产品推荐
相关产品推荐

