ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

InsForge Razorpay 支付接入指南:订单、订阅、Webhook 与履约触发器实战

InsForge Razorpay 支付接入指南:订单、订阅、Webhook 与履约触发器实战 InsForge Razorpay 支付接入指南订单、订阅、Webhook 与履约触发器实战【免费下载链接】InsForgeThe all-in-one, open-source backend platform for agentic coding. InsForge gives your coding agent database, auth, storage, compute, hosting, and AI gateway to ship full-stack apps end-to-end.项目地址: https://gitcode.com/GitHub_Trending/in/InsForge导读本篇技术指南以 InsForge 开源后端平台的 Razorpay 支付模块为核心系统讲解如何在该平台上接入印度主流支付网关 Razorpay从一次性订单Razorpay Checkout到订阅Plans/Subscriptions、再到手工 Webhook 配置与基于 PostgreSQL 触发器的履约Fulfillment链路。读完本文你将掌握 InsForge Payments API 中 Razorpay 的完整调用方式、签名验证原理、RLS 权限模型以及如何用一条安全的 SQL 触发器把已支付变成已履约。一、Razorpay 在 InsForge 中的适用边界Razorpay 与 Stripe 是两套并行的支付提供商InsForge 的 Payments 模块为二者提供了独立但同构的 API 表面。在 Razorpay 流程中你应当使用Razorpay Orders Razorpay Checkout处理一次性付款Razorpay Subscriptions Checkout 授权处理周期性订阅Razorpay Items 与 Plans管理商品目录与订阅计划手动配置的 Razorpay Webhook接收支付、退款、订阅状态事件后端路由完成订阅的取消cancel、暂停pause、恢复resume。需要特别注意的是不要在 Razorpay 流程中使用 Stripe Checkout、Stripe Prices 或 Billing Portal 概念。Razorpay Checkout 是在应用内通过checkout.js运行的它不会返回一个托管式 Checkout URL——这一点与 Stripe 的托管支付页有本质区别集成时最容易踩坑。二、动手前的前置检查按照 InsForge 的 Agent 文档要求接入 Razorpay 前必须逐条确认以下六项默认使用environment: test除非用户明确批准切换 live 环境确认目标环境test/live的Key ID 和 Key Secret 都已配置订阅流程要求Items 和 Plans 已存在于同一环境Webhook 必须在 Razorpay Dashboard 手工配置并指向公网 HTTPS URLCheckout 回调校验只代表客户端回跳是真实的持久化履约必须依赖 Webhook一次性商品优先创建 Razorpay ItemsOrders 虽可只传金额但 Items 能在同步后保持目录可见Orders 本质只是支付尝试记录。底层支撑这些前置校验不是文档口号而是被源码强制的。在 razorpay.provider.ts 中validateRazorpayKey会校验 Key ID 必须以rzp_test_test 环境或rzp_live_live 环境开头否则直接抛出RazorpayKeyValidationError在 config.service.ts 中setRazorpayKeys会调用retrieveAccount()发起一次真实的 Orders 探测请求来验证密钥有效性并把密钥以加密形式写入system.secrets同时把连接状态快照写入payments.provider_connections。如果更换了 Key 指向的 Razorpay 账号provider_account_id发生变化平台会自动清理该环境下的历史支付数据避免数据串号。三、Webhook 手工配置Razorpay 的 Webhook 是手动管理的InsForge 不会自动注册 endpoint。你需要在Razorpay Dashboard → Payments → Settings → Webhooks中生成/查看 Webhook URL 与 Secret。平台为管理员提供了两个 Webhook 运维路由GET /api/payments/razorpay/test/webhook POST /api/payments/razorpay/test/webhook/rotate-secretGET .../webhook返回该环境的 webhook URL 与 Secret若未设置会自动生成 32 字节随机 token 并加密存储POST .../webhook/rotate-secret则强制轮换 Secret。这两个路由实现在 config.routes.ts 中webhook URL 的形态由getWebhookUrl拼装${API_BASE_URL}/api/webhooks/razorpay/{environment}见 config.service.ts环境名只能是test或live。推荐启用的事件清单以下 19 个事件是 InsForge 的 Webhook 处理器实际会消费的事件未在清单内的事件会被标记为ignored。请在 Dashboard 中勾选订阅类别事件支付payment.authorized、payment.captured、payment.failed订单order.paid发票invoice.paid、invoice.expired退款refund.created、refund.processed、refund.failed订阅subscription.created、subscription.activated、subscription.charged、subscription.updated、subscription.cancelled、subscription.paused、subscription.resumed、subscription.halted、subscription.completed、subscription.expiredRazorpay 只能向公网 HTTPS URL 投递 Webhook。Webhook 接收端的实现原理InsForge 的 Webhook 接收端位于/api/webhooks/razorpay/:environmentrazorpay.routes.ts处理链路在 webhook.service.ts读取x-razorpay-signature请求头缺失直接 401使用 HMAC-SHA256 对原始请求体字节Buffer计算签名并与头值做timingSafeEqual恒时比较见 razorpay.provider.ts——注意必须对未解码的原始字节做哈希任何 JSON 重序列化都会导致验签失败事件以provider_event_id优先使用x-razorpay-event-id头否则由account_id.event.entity_id.created_at拼装作为幂等键先记录payments.webhook_events的pending状态重复事件直接跳过保证 at-least-once 投递下的幂等性按事件类型分发到 payment/refund/subscription/invoice/order 处理器统一写入payments.transactions镜像表并把订单/订阅状态同步到各自的 provider 镜像表。四、一次性订单从创建到验证4.1 创建 Razorpay Order正确姿势是先在应用侧创建一笔待支付的内部订单pending order再通过 provider-scoped SDK 创建 Razorpay Orderconst { data, error } await insforge.payments.razorpay.createOrder(test, { amount: 50000, // 最小货币单位INR 的 paise这里是 ₹500.00 currency: INR, receipt: order_123, subject: { type: team, id: team_123 }, // 账单归属主体 customerEmail: buyerexample.com, notes: { order_id: order_123 } // 履约触发器依赖此键 }); if (error) throw error;如果履约触发器读取notes.order_id创建 Order或 Subscription时必须传入notes: { order_id: ... }。从源码看这一流程比表面上更严谨order.service.ts 的createOrder会先在payments.razorpay_orders插入一条statusinitialized的记录同时生成 receipt再把 InsForge 内部记录 ID 以保留键insforge_order_id写入 notes 一并传给 RazorpayRazorpay 创建成功后回写order_id/amount/status若调用失败该内部记录会被标记为failed并记录last_error而不是静默消失。这保证了应用侧始终有一份可对账的订单记录。4.2 打开 Checkout 并验证回调在前端用data.checkoutOptions打开 Razorpay Checkout。Checkout 回调会返回三个值razorpay_order_id、razorpay_payment_id、razorpay_signature。随后通过 SDK 验证await insforge.payments.razorpay.verifyOrder(test, { orderId: response.razorpay_order_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature });验证的底层逻辑是对${orderId}|${paymentId}字符串用该环境的 Key Secret 做 HMAC-SHA256与签名做恒时比较见 razorpay.provider.ts 与verifyCheckoutSignature。验证通过后内部订单状态会被更新为attempted若已是paid则保持并记录verified_payment_id与verified_at见 order.service.ts。关键认知验证成功只证明客户端回跳是真实的、签名有效不能据此把订单标记为已支付或授予访问权限。持久的履约必须来自经过验证的 Razorpay Webhook 事件。五、订阅Plans、创建与管理5.1 Plan 是订阅的基础Razorpay 订阅使用Plans不是 Stripe 的 Prices。一个 Plan 是围绕一个 Razorpay Item 的周期性定价定义字段包含perioddaily/weekly/monthly/yearly、interval以及内嵌的 item名称、金额、币种。Plans 与 Items 由RazorpayCatalogService管理catalog.service.ts创建 Plan 时会同步落库到payments.razorpay_plans同时把 Plan 内嵌的 item 同步到payments.razorpay_items并通过 advisory lockpayments_razorpay_environment_{env}防止并发目录操作互相覆盖。5.2 创建订阅先创建/同步 Plan再通过 provider-scoped SDK 创建订阅const { data, error } await insforge.payments.razorpay.createSubscription(test, { planId: plan_123, totalCount: 12, // 总扣款周期数 subject: { type: team, id: team_123 }, customerEmail: buyerexample.com }); if (error) throw error;创建成功后前端用data.checkoutOptions.subscription_id打开 Checkout回调拿到订阅支付签名后验证await insforge.payments.razorpay.verifySubscription(test, { subscriptionId: response.razorpay_subscription_id, paymentId: response.razorpay_payment_id, signature: response.razorpay_signature });注意订阅签名的拼接顺序与订单不同订阅是对${paymentId}|${subscriptionId}做 HMAC见 razorpay.provider.ts混用订单的orderId|paymentId顺序必然验签失败。验证通过后订阅状态由created变为authenticated并记录authorization_payment_id。5.3 订阅生命周期管理// 取消cancelAtCycleEndfalse 表示立即取消 await insforge.payments.razorpay.cancelSubscription(test, sub_123, { cancelAtCycleEnd: false }); // 暂停 / 恢复 await insforge.payments.razorpay.pauseSubscription(test, sub_123); await insforge.payments.razorpay.resumeSubscription(test, sub_123);5.4 底层权限模型RLS 探测订阅相关操作不是谁都能干的。源码在 subscription.service.ts 中实现了两层防护创建订阅会先以当前用户上下文执行一次INSERT探测写入一条sub_rls_probe_*的临时订阅记录借由payments.razorpay_subscriptions表上的 RLS 策略判断该用户是否有权为这个 billing subject 建订阅随后ROLLBACK TO SAVEPOINT回滚探测写入取消/暂停/恢复会先执行UPDATE ... RETURNING subject_type, subject_id探测验证用户对该订阅有UPDATE权限并顺带取回账单归属主体subject用于回写镜像记录PostgreSQL 还会对INSERT/UPDATE ... RETURNING返回的行施加SELECT策略因此当策略探测需要返回行时必须为同一 billing subject 配置匹配的SELECT可见性。同时不要让用户提交任意的 subject——应用必须自行校验当前用户确实能管理该账单主体否则任何人都可能给别人的 team 开订阅。这正对应常见故障表中的最后一行User can start a subscription for another team → Add RLS or server-side membership checks。六、履约Fulfillment用 Webhook 驱动业务动作履约是支付接入最核心的业务环节原则只有一条Checkout 回调验证不能作为标记已支付/授予访问的依据必须以验证过的 Webhook 事件为准。同时不要把履约触发器挂到payments.razorpay_subscriptions这类 provider 镜像表上——镜像表由平台写入业务逻辑应挂在自己应用的表或payments.webhook_events上。6.1 一次性订单履约触发器以下触发器监听payments.webhook_events当 Razorpay 的payment.captured、order.paid、invoice.paid事件处理完成processing_statusprocessed时从 payload 的 notes 中解析order_id把应用侧public.orders中对应pending订单置为paidCREATE OR REPLACE FUNCTION public.fulfill_razorpay_order() RETURNS TRIGGER AS $$ BEGIN IF NEW.provider razorpay AND NEW.event_type IN (payment.captured, order.paid, invoice.paid) AND NEW.processing_status processed AND COALESCE( NEW.payload - payload - payment - entity - notes - order_id, NEW.payload - payload - invoice - entity - notes - order_id ) IS NOT NULL THEN UPDATE public.orders SET status paid, paid_at COALESCE(NEW.processed_at, NOW()) WHERE id::text COALESCE( NEW.payload - payload - payment - entity - notes - order_id, NEW.payload - payload - invoice - entity - notes - order_id ) AND status pending; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER fulfill_razorpay_order_from_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.fulfill_razorpay_order();6.2 订阅履约与撤销订阅场景需要从订阅实体的 notes 中解析账单归属主体。InsForge 在创建订阅时会把insforge_subject_type和insforge_subject_id写入 notes并同时创建payments.customer_mappings行因此客户映射customer mapping也是安全的兜底方案CREATE OR REPLACE FUNCTION public.grant_razorpay_subscription_access() RETURNS TRIGGER AS $$ DECLARE v_subject_type TEXT; v_subject_id TEXT; BEGIN IF NEW.provider razorpay AND NEW.event_type subscription.charged AND NEW.processing_status processed THEN v_subject_type : NEW.payload - payload - subscription - entity - notes - insforge_subject_type; v_subject_id : NEW.payload - payload - subscription - entity - notes - insforge_subject_id; IF v_subject_id IS NULL THEN SELECT m.subject_type, m.subject_id INTO v_subject_type, v_subject_id FROM payments.customer_mappings m WHERE m.provider NEW.provider AND m.environment NEW.environment AND m.provider_customer_id NEW.payload - payload - subscription - entity - customer_id; END IF; IF v_subject_id IS NULL THEN RAISE WARNING Razorpay event % has no resolvable billing subject, NEW.provider_event_id; RETURN NEW; END IF; -- Branch on the subject type sent at creation; team_id is a UUID here, -- so the type check also guards the cast. IF v_subject_type team THEN INSERT INTO public.team_entitlements (team_id, plan, active, updated_at) VALUES (v_subject_id::uuid, pro, true, NOW()) ON CONFLICT (team_id) DO UPDATE SET plan EXCLUDED.plan, active true, updated_at NOW(); END IF; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql SECURITY DEFINER; CREATE TRIGGER grant_razorpay_subscription_access_from_webhook AFTER INSERT OR UPDATE ON payments.webhook_events FOR EACH ROW EXECUTE FUNCTION public.grant_razorpay_subscription_access();撤销访问用同样的方式从subscription.cancelled、subscription.halted、subscription.expired事件处理把active置为 false 或按业务删除权益。最后请按应用自身 schema 和事件形态调整 payload 路径为应用自有的账单表启用 RLSpayments.transactions只用于 Dashboard 与报表展示不要把它当作业务状态源。七、安全清单对共享主体shared subject暴露订单/订阅流程前必须添加 RLS 或服务端成员关系校验考虑为payments.razorpay_orders和payments.razorpay_subscriptions启用 RLS不要把payments.customers、payments.transactions、payments.razorpay_subscriptions直接暴露给终端用户不要直接写 provider 托管的支付表一切写入走 Payments API、Razorpay Webhook 或应用自有触发器的目标表notes 中insforge_前缀的键是保留键如insforge_subject_type、insforge_subject_id、insforge_order_id应用侧不可占用。八、调试五条诊断 SQL怀疑支付链路出问题时按顺序检查这几张表最近的 Razorpay 订单尝试SELECT id, environment, status, subject_type, subject_id, order_id, receipt, amount, currency, verified_payment_id, last_error, created_at, updated_at FROM payments.razorpay_orders ORDER BY created_at DESC LIMIT 20;最近的 Razorpay 订阅SELECT environment, subscription_id, plan_id, customer_id, status, subject_type, subject_id, authorization_payment_id, current_start, current_end, created_at, updated_at FROM payments.razorpay_subscriptions ORDER BY created_at DESC LIMIT 20;客户映射确认 subject ↔ customer 关联SELECT provider, environment, subject_type, subject_id, provider_customer_id, created_at, updated_at FROM payments.customer_mappings WHERE provider razorpay ORDER BY updated_at DESC LIMIT 20;Razorpay 交易流水报表/对账用SELECT provider, environment, type, status, subject_type, subject_id, provider_object_type, provider_object_id, amount, currency, paid_at, failed_at, refunded_at, created_at FROM payments.transactions WHERE provider razorpay ORDER BY created_at DESC LIMIT 20;Webhook 失败与待处理事件排查履约不生效的第一站SELECT provider, environment, provider_event_id, event_type, processing_status, attempt_count, last_error, received_at, processed_at FROM payments.webhook_events WHERE provider razorpay AND processing_status IN (failed, pending) ORDER BY received_at DESC LIMIT 20;九、常见故障排查表症状排查方向订单创建失败确认该环境的 Key ID / Key Secret 已配置且金额是最小货币单位如 INR 的 paiseCheckout 打不开确认https://checkout.razorpay.com/v1/checkout.js已加载且checkoutOptions.key存在签名验证失败必须原样传入 Razorpay Checkout 返回的 order/subscription ID、payment ID 与 signature注意订单与订阅的签名拼接顺序不同Webhook 签名无效确认 Razorpay Dashboard 的 webhook secret 与同一test/live环境的配置一致且 URL 以/api/webhooks/razorpay/{environment}结尾订阅创建失败确认 Plan 存在于同一 Razorpay 环境且关联了有效的 ItemRazorpay 已扣款但 InsForge 无记录检查手工 Webhook 配置是否完成并查询payments.webhook_events中该事件的状态用户能给别的 team 开订阅为账单主体补充 RLS 或服务端成员关系校验十、小结InsForge 的 Razorpay 集成可以概括为一条完整链路配置密钥system.secrets 连接快照→ 目录同步Items/Plans→ 创建内部订单/订阅并打上insforge_*notes → 前端 Checkout → 回调验签仅证明回跳真实→ 手工 WebhookHMAC 验签 幂等落库→ 触发器履约订单/订阅→ 诊断 SQL 兜底。把握住Checkout 回调只验证、Webhook 才履约这一原则再结合 RLS 权限探测与调试 SQL就能在生产环境中稳定、安全地运行 Razorpay 支付。相关源码可进一步研读razorpay.provider.ts底层 SDK 封装与签名、order.service.ts 与 subscription.service.ts业务流程与 RLS 探测、webhook.service.ts事件分发与幂等、config.service.ts密钥与 Webhook 配置、catalog.service.tsItems/Plans 同步。【免费下载链接】InsForgeThe all-in-one, open-source backend platform for agentic coding. InsForge gives your coding agent database, auth, storage, compute, hosting, and AI gateway to ship full-stack apps end-to-end.项目地址: https://gitcode.com/GitHub_Trending/in/InsForge创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表