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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:54:58