Integrating the Paytm Payment Gateway in a React App in Five Steps
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
- The buyer clicks "Pay" in React. The browser asks your server to create a payment for an order.
- 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. - The server sends the order ID, the token and the amount back to the browser. The merchant key never leaves the server.
- React loads Paytm's checkout script, initialises it with that data and opens the payment page.
- 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:
- The amount comes from your database, computed from the order, never from the browser request.
- Order IDs must be unique per payment attempt; a retry gets a new attempt ID.
- Check
resultStatusand log the result code on failure.
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:
- 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.
- 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.
- Handle "pending". Some payment methods settle later. Keep the order pending and query again, or subscribe to Paytm's webhook.
- 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
- Use the staging host, test credentials and the test payment details listed in Paytm's documentation; try success, failure, cancellation and pending cases.
- For production, switch host, MID, merchant key and website name together, from configuration only.
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
- Paytm Payment Gateway developer documentation: JS Checkout (prerequisites, invoke payment, verify payment), Initiate Transaction API and Transaction Status API.
- React documentation, useEffect.
- W3C, Content Security Policy Level 3.
WebWhat is a CRUD application?
CRUD means Create, Read, Update, Delete. See how it maps to SQL and REST HTTP methods, with a runnable Python example, validation, soft deletes and idempotency.
WebWooCommerce coupons
How WooCommerce coupons work: discount types, restrictions, usage limits, WC_Coupon in PHP, URL coupons, common problems and when a coupon plugin helps.
WebHTTP requests with urllib3
How to send GET and POST requests with urllib3: PoolManager, query fields, JSON bodies, headers, timeouts, Retry, redirects, TLS, streaming and errors (v2).