Integrating Razorpay for Credits: Complete Guide

An end-to-end walkthrough for a robust credits purchase flow: client UI, order creation, checkout, verification, schema, webhooks, and edge cases.

Overview

This guide documents the exact implementation used in our platform for one-time credits purchase using Razorpay. It covers the user journey, server APIs, client checkout integration, verification, schema, and operational best practices.

  • User selects a package or custom credits.
  • We open a usage sheet explaining what those credits unlock.
  • On "Proceed to Pay", client calls `/api/payments/create-order`.
  • Server calculates price, creates Razorpay order, stores a PENDING `Payment`, returns `orderId`.
  • Client opens Razorpay Checkout with that `orderId`.
  • On success, client posts identifiers + signature to `/api/payments/verify`.
  • Server verifies signature, checks capture from Razorpay, marks COMPLETED, credits user, records `CreditTransaction`.

Purchase UI (Client)

The purchase page opens a usage sheet, then initializes payment with a processing dialog indicating states (initializing → processing → verifying → redirecting). On success, we redirect to a success page with receipt details.

Create Order API

Server receives credits and currency, validates range, calculates price strictly on the server, creates a Razorpay order, stores a PENDING `Payment`, and returns `orderId`. Do not trust client amounts.

Open Razorpay Checkout

On success, the handler posts `razorpay_payment_id`, `razorpay_order_id`, and `razorpay_signature` to the verify API. For failures, Razorpay suggests retry flows in the widget.

Verify Payment API

Server verifies signature, fetches Razorpay payment, ensures captured status, safely marks `Payment` as COMPLETED, credits the user, and records a `CreditTransaction`—idempotently.

Example Schema

A simplified example showing `Payment` and `CreditTransaction` with useful indices and statuses.

Advanced: Webhooks & Reconciliation

Implement Razorpay webhooks for `payment.captured`/`order.paid`. On capture, verify signature, reconcile by `orderId` and idempotently mark COMPLETED. Add a periodic job to re-check old PENDING payments.

Also consider a callback URL fallback and a resume-on-return banner for robustness against client interruptions.

Edge Cases & Playbook

Below are detailed scenarios and how to handle them in production. Code samples for these will be added soon.

  • Client disconnected after pay: Use webhooks to finalize capture; on next visit, show a banner to resume/confirm status. Callback URL can also help.
  • Duplicate clicks/requests: Use idempotency keys (receipt, orderId) and check existing PENDING/COMPLETED records before creating new ones.
  • Amount tampering: Never trust client amount; compute server-side from SKU/config and re-validate against Razorpay amount in verify step.
  • Order expiry: Detect on verify; recreate order and prompt retry with a fresh orderId.
  • Webhook security: Verify webhook signature; whitelist IPs if applicable; handle only expected events.
  • Partial captures/refunds: Reflect status transitions (FAILED/REFUNDED/CANCELLED) and adjust credits accordingly.
  • Observability: Log state transitions; add metrics and alerts for stuck PENDING or frequent failures.

Coming soon: production-ready webhook handler examples and reconciliation jobs.