Web

Integrating the Paytm Payment Gateway in a React App in Five Steps

Web · Published 2026-10-27

Paytm's JS Checkout lets a React app open Paytm's payment page in an overlay. The integration always has a server part and a browser part: your server calls Paytm's Initiate Transaction API with your merchant key to get a short-lived transaction token, the React component loads Paytm's merchant-specific script and invokes the checkout with that token, and your server confirms the final status with the Transaction Status API before fulfilling the order.

This article follows Paytm's developer documentation at the time of writing. Paytm has changed its hostnames more than once, so take endpoints, parameter names and SDK versions from the current official docs. All identifiers below are placeholders.

How the pieces fit together

  1. The buyer clicks "Pay" in React. The browser asks your server to create a payment for an order.
  2. Your server looks up the order total, signs a request with the merchant key and calls Initiate Transaction. Paytm returns a transaction token (txnToken), valid for a short time.
  3. The server sends the order ID, the token and the amount back to the browser. The merchant key never leaves the server.
  4. React loads Paytm's checkout script, initialises it with that data and opens the payment page.
  5. After payment, Paytm reports the result to your callback URL and to the page; your server verifies the signature and asks the Transaction Status API for the authoritative status.

Step 1: get test credentials

Create a merchant account on Paytm's merchant dashboard, switch to test mode and generate API keys under the developer section. You get a test Merchant ID (MID) and merchant key; production credentials are issued after Paytm verifies and activates the account. Paytm documents separate staging and production hosts: keep them in configuration so switching environments does not require code changes.

# server environment (.env, never committed)
PAYTM_MID=your-test-mid
PAYTM_MERCHANT_KEY=your-test-merchant-key
PAYTM_WEBSITE=your-website-name
PAYTM_HOST=staging-host-from-paytm-docs
PAYTM_CALLBACK_URL=https://your-site.example/api/paytm/callback

Only the MID and host are needed in the browser. The merchant key stays server-side: whoever holds it can sign requests as you.

Step 2: initiate the transaction on the server

The server builds a JSON body with, among other fields, the request type, MID, website name, order ID, the transaction amount and currency, a customer ID and the callback URL. It signs the body with Paytm's checksum library (available for Node.js, Python, Java, PHP and .NET) and posts it to the Initiate Transaction endpoint. A Node.js sketch:

// server-side only
import PaytmChecksum from "paytmchecksum";

export async function initiatePayment(order) {
  const body = {
    requestType: "Payment",
    mid: process.env.PAYTM_MID,
    websiteName: process.env.PAYTM_WEBSITE,
    orderId: order.id,
    callbackUrl: process.env.PAYTM_CALLBACK_URL,
    txnAmount: { value: order.totalAsString, currency: order.currency },
    userInfo: { custId: order.customerId },
  };
  const signature = await PaytmChecksum.generateSignature(
    JSON.stringify(body), process.env.PAYTM_MERCHANT_KEY);

  const url = `https://${process.env.PAYTM_HOST}/theia/api/v1/initiateTransaction` +
              `?mid=${body.mid}&orderId=${encodeURIComponent(order.id)}`;
  const res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ body, head: { signature } }),
  });
  const data = await res.json();
  if (data.body?.resultInfo?.resultStatus !== "S") {
    throw new Error(data.body?.resultInfo?.resultMsg ?? "Initiate failed");
  }
  return { orderId: order.id, token: data.body.txnToken, amount: body.txnAmount.value };
}

Rules that prevent real problems:

Expose this through an endpoint such as POST /api/paytm/initiate that takes only your internal order ID.

Step 3: load the checkout script in React

JS Checkout is delivered as a script URL that contains your MID, under the host for the current environment. Load it once, on demand, with a small hook:

import { useEffect, useState } from "react";

export function usePaytmScript(host, mid) {
  const [ready, setReady] = useState(Boolean(window.Paytm?.CheckoutJS));
  useEffect(() => {
    if (ready) return;
    const script = document.createElement("script");
    script.src = `https://${host}/merchantpgpui/checkoutjs/merchants/${mid}.js`;
    script.crossOrigin = "anonymous";
    script.onload = () => window.Paytm.CheckoutJS.onLoad(() => setReady(true));
    document.body.appendChild(script);
  }, [host, mid, ready]);
  return ready;
}

Paytm recommends loading the script before it is called; loading it when the checkout page mounts does that. With a Content Security Policy, allow Paytm's host for scripts and frames.

Step 4: initialise and invoke the checkout

export function PayButton({ orderId, host, mid }) {
  const ready = usePaytmScript(host, mid);
  const [error, setError] = useState(null);

  async function pay() {
    setError(null);
    const res = await fetch("/api/paytm/initiate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ orderId }),
    });
    if (!res.ok) { setError("Could not start the payment."); return; }
    const { orderId: paytmOrderId, token, amount } = await res.json();

    const config = {
      root: "",
      flow: "DEFAULT",
      data: { orderId: paytmOrderId, token, tokenType: "TXN_TOKEN", amount },
      handler: {
        notifyMerchant(eventName, data) {
          console.log("Paytm event", eventName, data);   // e.g. the user closed the page
        },
      },
    };
    try {
      await window.Paytm.CheckoutJS.init(config);
      window.Paytm.CheckoutJS.invoke();
    } catch (e) {
      setError("The payment page could not be opened.");
    }
  }

  return (
    <>
      <button onClick={pay} disabled={!ready}>Pay with Paytm</button>
      {error && <p role="alert">{error}</p>}
    </>
  );
}

The configuration object follows the structure in Paytm's JS Checkout documentation: data carries the order ID, token and amount returned by your server, and handler.notifyMerchant receives events such as the buyer closing the payment page. By default Paytm then posts the result to your callback URL; the docs also describe a redirect: false option with a transactionStatus handler if you prefer to stay on the page.

Step 5: verify the payment on the server

Whatever the browser shows, the order is paid only when your server says so:

  1. Validate the callback. The callback carries a signature; verify it with the checksum library and your merchant key. Paytm warns that skipping this can lead to fraudulent transactions.
  2. Call the Transaction Status API with the MID and order ID (signed the same way) and use its status (success, failure or pending) as the source of truth. Check that the amount matches your order.
  3. Handle "pending". Some payment methods settle later. Keep the order pending and query again, or subscribe to Paytm's webhook.
  4. Make updates idempotent, since the callback, the webhook and your own polling may all report the same payment.

Then show a confirmation page that reads the order status from your server. Modelling each attempt as a record with explicit states (created, pending, paid, failed) is the create/read/update cycle described in what a CRUD application is.

Testing and going live

Summary

Keep the merchant key and amount calculation on the server, which calls Initiate Transaction and returns an order ID, token and amount. In React, load Paytm's script once, call CheckoutJS.init(config) then invoke(). Confirm every payment by validating the callback signature and querying the Transaction Status API before fulfilling the order.

Sources

Read next

More in Web