Chat on WhatsApp
Payments & Gateways 13 min read

Razorpay Magento Integration — A Complete India + INR Setup Guide

Razorpay ships an official Magento module — razorpay/razorpay-magento on Marketplace and GitHub — that covers Cards, NetBanking, UPI Collect, UPI Intent, Wallets, EMI, and PayLater. The defaults work; production does not. This is the install command, the system.xml API-key config, the webhook signature check for payment.captured and refund.processed, the RBI 2-factor authentication redirect, the UPI AutoPay e-mandate flow for subscriptions, and the T+1 to T+3 settlement timing every Indian merchant needs to plan around. Magento 2.4.4 — 2.4.9, INR-only stores.

Razorpay Magento Integration — A Complete India + INR Setup Guide

The Razorpay Magento integration is the official razorpay/razorpay-magento module that ships every India-focused payment method — Cards, NetBanking, UPI Collect, UPI Intent, Wallets, EMI, PayLater — as one Magento payment gateway, wired against Razorpay's REST API at api.razorpay.com/v1[1]. This guide is the production checklist: install command, system.xml API config, webhook signature verification, RBI 2-factor authentication redirect, UPI AutoPay subscriptions, and T+1 to T+3 settlement timing.

Razorpay's official module covers seven payment methods, but only three of them work out of the box for production.

Of the 11 Razorpay-on-Magento engagements shipped from kishansavaliya.com in 2025-2026, every one needed work beyond the README — webhook verification, RBI 2FA testing, UPI AutoPay, refund reconciliation. Production-ready takes a working day.

The Razorpay module's defaults are correct for sandbox. They are unsafe for production until the webhook, the 2FA callback timeout, and the settlement reconciliation cron are in place.

Six sections — install, config, webhook signature, RBI 2FA, subscriptions, settlement reconciliation. Magento 2.4.4 — 2.4.9, PHP 8.1+, INR-only stores.

1. Install the official Razorpay Magento module

Razorpay's razorpay/razorpay-magento package is on Packagist and mirrored on the Magento Marketplace[2]. The Composer route is the only one to use in 2026 — Marketplace ZIP installs are deprecated and skip the lock-file pinning that keeps the SDK version stable across deploys.

Composer install

composer require razorpay/razorpay-magento
bin/magento module:enable Razorpay_Magento
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush

The package pulls razorpay/razorpay (PHP SDK) and razorpay/curl as transitive dependencies. Force the latest with composer require razorpay/razorpay-magento:^4.6.

What ships in the module

ComponentPath
Payment method (MethodInterface)Model/PaymentMethod.php
Admin config (keys, status, capture mode)etc/adminhtml/system.xml
Webhook controller — /razorpay/webhookController/Webhook/Index.php
Order-place observer (creates Razorpay order)Observer/SalesOrderPlaceAfter.php
Knockout payment block (Luma)view/frontend/web/js/view/payment/method-renderer

The Hyvä Checkout adapter is a separate package — hyva-themes/magento2-razorpay[3]. Install it after the official module on Hyvä storefronts.

2. Configure API keys and capture mode

Razorpay issues two key pairs per account — Test mode (rzp_test_) and Live mode (rzp_live_). Both live in the dashboard under Settings → API Keys. Never commit the Live secret.

Admin path

Stores → Configuration → Sales → Payment Methods → Razorpay. The fields that matter:

FieldProduction rule
TitleList the methods the customer expects — UPI first for India
Key ID + Key SecretInject via env.php, never the admin UI
Webhook Secret32+ char random string, pasted to the Razorpay dashboard verbatim
Payment ActionAuthorize and Capture — auto-capture matches T+1 settlement
New Order StatusProcessing — Pending only fits manual-capture B2B workflows
<?php
// app/etc/env.php
'system' => [
    'default' => [
        'payment' => [
            'razorpay' => [
                'key_id' => getenv('RAZORPAY_KEY_ID'),
                'key_secret' => getenv('RAZORPAY_KEY_SECRET'),
                'webhook_secret' => getenv('RAZORPAY_WEBHOOK_SECRET'),
            ],
        ],
    ],
],

Magento reads from env.php first when the same path exists in core_config_data. The deploy script reads from the CI vault and exports the env vars before bin/magento runs.

Sandbox smoke test

curl -X POST https://api.razorpay.com/v1/orders \
  -u rzp_test_xxxxxxxxxxxxxx:test_secret_xxxxxxxxxxxxxx \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 50000,
    "currency": "INR",
    "receipt": "order_smoke_001"
  }'

A 200 response with an id field starting order_ means the key pair is valid. Amount is in paise — 50000 paise is ₹500. Currency must be INR for Indian-registered Razorpay accounts.

3. Webhook signature verification — the part that is not optional

Razorpay fires three webhooks every production storefront must handle — payment.captured, payment.failed, refund.processed. Each arrives with an X-Razorpay-Signature header computed as HMAC-SHA256(webhook_secret, raw_body)[4]. The official verifier is correct — the trap is custom observers stacked on top that capture the order before the verifier runs, marking failed payments as paid.

Verifier signature pattern

<?php
// app/code/Panth/RazorpayHardening/Controller/Webhook/Verify.php
namespace Panth\RazorpayHardening\Controller\Webhook;

use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Request\Http as HttpRequest;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Store\Model\ScopeInterface;
use Psr\Log\LoggerInterface;

class Verify implements HttpPostActionInterface
{
    public function __construct(
        private HttpRequest $request,
        private ScopeConfigInterface $config,
        private JsonFactory $jsonFactory,
        private LoggerInterface $logger
    ) {}

    public function execute()
    {
        $rawBody = $this->request->getContent();
        $signature = $this->request->getHeader('X-Razorpay-Signature');
        $secret = $this->config->getValue(
            'payment/razorpay/webhook_secret',
            ScopeInterface::SCOPE_STORE
        );
        $expected = hash_hmac('sha256', $rawBody, (string)$secret);

        // Constant-time comparison — never use ===
        if (!hash_equals($expected, (string)$signature)) {
            $this->logger->warning('Razorpay webhook signature mismatch');
            $result = $this->jsonFactory->create();
            $result->setHttpResponseCode(400);
            return $result->setData(['status' => 'invalid_signature']);
        }

        $payload = json_decode($rawBody, true);
        $event = $payload['event'] ?? '';
        // Dispatch by event — payment.captured, payment.failed, refund.processed
        return $this->jsonFactory->create()->setData(['status' => 'ok']);
    }
}

The three events to dispatch on

EventMagento action
payment.capturedMark processing, create invoice, send confirmation email
payment.failedMark canceled, restock items, send failure notice
refund.processedFlip credit memo to refunded, not open

Webhook URL registration and retries

In the Razorpay dashboard under Settings → Webhooks → Add New Webhook, set the URL to https://your-store.example/razorpay/webhook and tick only the three events above. order.paid and payment.authorized fire alongside payment.captured and cause double processing on auto-capture configs.

Razorpay retries failed deliveries on exponential backoff for 24 hours — 5s, 30s, 5m, 30m, 2h, 12h[4]. The controller must respond with HTTP 200 inside 5 seconds. Move slow work (email, inventory adjust) into a Magento queue consumer.

4. RBI's 2-factor authentication mandate and the 3-step redirect

The Reserve Bank of India has mandated 2-factor authentication on all domestic card-not-present transactions since 2021[5]. Razorpay handles the OTP for cards, bank login for NetBanking, and UPI PIN prompt for UPI — the storefront keeps the order pending across redirects and only finalizes on the webhook.

The 3-step flow

  1. Customer clicks Place Order. Magento POSTs to /v1/orders and gets a razorpay_order_id, then opens the Razorpay Checkout iframe.
  2. Customer picks UPI or Cards. Razorpay redirects to the bank's 2FA page (OTP for cards, UPI PIN for UPI).
  3. Bank confirms. Razorpay redirects back to Magento's callback route with razorpay_payment_id and razorpay_signature. Magento verifies the signature, then waits for the payment.captured webhook to flip the order to processing.

Callback signature verification — different from webhook

The callback signature is HMAC-SHA256 of razorpay_order_id|razorpay_payment_id using the API key secret (not the webhook secret). Reusing the wrong secret is the most common silent failure.

<?php
// app/code/Panth/RazorpayHardening/Helper/CallbackVerifier.php
public function verify(string $orderId, string $paymentId, string $signature): bool
{
    $secret = $this->config->getValue(
        'payment/razorpay/key_secret',
        ScopeInterface::SCOPE_STORE
    );
    $expected = hash_hmac('sha256', $orderId . '|' . $paymentId, (string)$secret);
    return hash_equals($expected, $signature);
}

What can go wrong in the 2FA window

  • Customer closes the tab during OTP entry — Magento has a pending order with no payment. The reconciliation cron must call /v1/payments?order_id=... after 15 minutes and cancel if no successful payment exists.
  • OTP times out at the bank — Razorpay fires payment.failed. The webhook handler must move the order to canceled.
  • Redirect URL fails but bank confirmed — money is debited, customer lands on a Magento 404. The webhook still arrives — the order is completed from the webhook path, not the callback path. Keep both routes working.

The reconciliation cron

curl -G https://api.razorpay.com/v1/payments \
  -u rzp_live_xxxxxxxxxxxxxx:live_secret_xxxxxxxxxxxxxx \
  --data-urlencode "from=$(date -v-30M +%s)" \
  --data-urlencode "to=$(date +%s)" \
  --data-urlencode "count=100"

The cron runs every 5 minutes, pulls the last 30 minutes of payments, and cross-checks against Magento's pending orders. Razorpay's Settlements → Reconciliation Report is the source of truth for daily numbers — never the Magento DB alone.

5. Subscriptions — UPI AutoPay and NetBanking recurring via the e-mandate flow

The default module handles single-payment orders. Recurring billing — subscription products, installments, donation auto-debits — needs the Razorpay Subscriptions API plus the e-mandate flow for UPI AutoPay and NetBanking recurring.

The Razorpay Subscriptions object model

ObjectMagento mapping
planCreated admin-side, one per recurring product
customerCreated on first signup, ID stored on customer_entity
subscriptionOne per active recurring product per customer; ID on quote_extension_attributes
tokenCustomer's e-mandate consent, created during initial 2FA
invoiceOne Magento order per Razorpay invoice via subscription.charged

UPI AutoPay e-mandate creation

curl -X POST https://api.razorpay.com/v1/subscriptions \
  -u rzp_live_xxxxxxxxxxxxxx:live_secret_xxxxxxxxxxxxxx \
  -H 'Content-Type: application/json' \
  -d '{
    "plan_id": "plan_MJ3kRzn4Yp8XQK",
    "customer_notify": 1,
    "total_count": 12,
    "start_at": 1748822400,
    "notes": {"magento_customer_id": "4271"}
  }'

The response includes a short_url field — the e-mandate auth page. Redirect the customer there on Magento Subscribe click. The UPI app prompts for an e-mandate approval (PIN + consent screen), and Razorpay fires subscription.activated on success.

The subscription webhooks to handle

{
  "event": "subscription.activated",
  "payload": {
    "subscription": {
      "entity": {
        "id": "sub_MJ3kPQrTzS9XJk",
        "plan_id": "plan_MJ3kRzn4Yp8XQK",
        "customer_id": "cust_MJ2jOPqRyT0WIj",
        "status": "active",
        "charge_at": 1751414400
      }
    }
  }
}

Magento creates the panth_subscription row on this webhook, links it to customer_entity, and schedules the next billing reminder. The recurring charge fires through subscription.charged — each charge generates a fresh Magento order via the same observer that handles one-off payments.

The UPI AutoPay PIN entry race

The UPI e-mandate consent screen has a 5-minute timeout. If the customer takes longer, the mandate fails silently and the subscription stays in created state forever. Poll GET /v1/subscriptions/<id> every 10 minutes for an hour; if still created after 60 minutes, cancel the local row and email the customer.

6. Settlement timing and refund liquidity planning

Razorpay settles to the merchant bank account on a rolling T+1 to T+3 schedule based on KYC tier[6]. New accounts default to T+3; verified merchants drop to T+2; high-volume verified merchants negotiate T+1. The settlement timing affects refund liquidity and the Magento Pending dashboard.

Payment methodSettlement to bankRefund timing
UPI Intent / CollectT+1 (verified) to T+3 (new)Instant to T+0
Credit CardT+2 to T+35-7 working days
Debit CardT+1 to T+25-7 working days
NetBankingT+1 to T+23-5 working days
Wallets (Paytm, Mobikwik)T+1Instant
EMI on CardsT+2 to T+35-7 working days
PayLater (Simpl, LazyPay)T+1 to T+23-5 working days

Refund liquidity

If today's GMV is ₹1,000,000 and a ₹50,000 refund hits at T+0 — before any settlement has arrived — Razorpay funds the refund from the next settlement, debiting the merchant balance. New accounts can run negative if refunds exceed the unsettled balance.

Settlement reconciliation script

curl -G https://api.razorpay.com/v1/settlements/recon/combined \
  -u rzp_live_xxxxxxxxxxxxxx:live_secret_xxxxxxxxxxxxxx \
  --data-urlencode "year=2026" \
  --data-urlencode "month=5" \
  --data-urlencode "day=20" \
  --data-urlencode "count=1000" > rzp-settlement-2026-05-20.json

A nightly Magento cron pulls this JSON, joins to sales_order on razorpay_payment_id, and inserts a row into panth_settlement_reconciliation. The finance team queries that table instead of the live Razorpay dashboard.

The five things to verify before flipping the Live mode toggle

  1. Webhook URL is HTTPS, replies 200 within 5 seconds, and the verifier uses hash_equals.
  2. Live key_secret is in env.php via a vault env var — never in core_config_data, never in git.
  3. Reconciliation cron registered in crontab.xml at */5 * * * * and run once in staging.
  4. A sandbox test order has cleared all 7 payment methods with exactly one Magento order each — no duplicates from order.paid firing alongside payment.captured.
  5. Refund flow tested end-to-end — credit memo creation, refund API call, refund.processed webhook arrival, credit memo flipped to refunded.

Hyvä Checkout specifics

The Hyvä adapter (hyva-themes/magento2-razorpay) replaces the Luma Knockout payment renderer with a Magewire component. Same admin path, same env vars, same webhook controller — only the frontend block name changes, registered via hyva_checkout_components.xml. Verifiers are shared with the Luma path.

Indian compliance touches you do not need to build yourself

Razorpay handles GST invoice generation for the gateway fees, TDS deduction under Section 194-O for marketplace flows, and the merchant settlement statements the chartered accountant needs at year-end. None of these need Magento code — the merchant downloads them from Account → Documents → Tax Invoices. The order-level GST invoice for the customer is a separate Magento tax setup, unrelated to Razorpay.

FAQ

Is the official razorpay/razorpay-magento module free?

Yes — MIT-licensed on GitHub. Razorpay charges transaction-based fees (2% for domestic cards and NetBanking, 0% for UPI on the standard plan, negotiated rates for high-volume merchants). No Magento licensing fee.

Can I run Razorpay alongside other Magento payment methods like Stripe or PayPal?

Yes. Magento supports unlimited active payment methods. The India pattern is Razorpay for INR, Stripe for international cards, PayPal as fallback — each gateway with its own webhook URL and reconciliation table.

What happens if the customer's UPI app is offline during checkout?

UPI Intent times out at 3 minutes. Razorpay fires payment.failed, the order moves to canceled, and the customer sees a retry button with the cart preserved.

How do I test the webhook locally without exposing my Magento dev box?

Use a reverse tunnel (ngrok, Cloudflare Tunnel) or Razorpay's own webhook simulator under Dashboard → Settings → Webhooks → Test. The simulator POSTs a signed payload — the verifier path is identical to production.

What is the minimum amount Razorpay accepts?

₹1 (100 paise). The amount field in POST /v1/orders is denominated in paise as an integer — ₹1.50 is amount: 150.

Does Razorpay Magento work on Magento 2.4.4 through 2.4.9?

Yes. Module versions 4.x support PHP 7.4 through 8.4 and Magento 2.4.4 — 2.4.9. The Hyvä Checkout adapter requires Magento 2.4.6+ for the registry-based component layout.

References

  1. Razorpay Developer Documentation, API Reference — Orders, Payments, Refunds. Live host api.razorpay.com/v1, basic auth with Key ID and Key Secret.
  2. Razorpay official Magento module on Packagist — razorpay/razorpay-magento — mirrored to the Magento Marketplace. Source on GitHub at razorpay/razorpay-magento.
  3. Hyvä Themes, Magewire payment-method adapter for Razorpayhyva-themes/magento2-razorpay Composer package; Hyvä Checkout 1.1+ on Magento 2.4.6+.
  4. Razorpay Webhooks documentation, Signature verification and retry policy — HMAC-SHA256 with X-Razorpay-Signature header; 24-hour exponential-backoff retry schedule.
  5. Reserve Bank of India, Additional Factor of Authentication for card-not-present transactions — domestic card-not-present 2FA mandate, active since 2021 and continuously enforced through 2026.
  6. Razorpay Help Center, Settlement Cycles by KYC Tier — T+1, T+2, T+3 cycles tied to verification level and monthly GMV.
  7. Production engagement traces from kishansavaliya.com Razorpay-on-Magento engagements, October 2025 — May 2026. Eleven INR-only storefronts shipped on Magento 2.4.4 — 2.4.9.
Need Razorpay shipped on your Magento store?

I am Kishan Savaliya, an Adobe-Certified Magento + Hyvä developer. I install razorpay/razorpay-magento, wire the webhook signature verifier, build the RBI 2FA reconciliation cron, set up UPI AutoPay subscriptions, and hand over the settlement reconciliation table the finance team needs. Fixed quote from $499 audit · $2,499 sprint · ~28h @ $25/hr. See Magento development services or hire me.