Why Teams Add a Second Rail to Razorpay

Razorpay is the backbone for hundreds of thousands of Indian businesses. UPI integration, card support, netbanking, EMI, the merchant dashboard, the disputes flow — all polished. Domestic INR is solved.

The pressure to add a second rail comes from workloads that fall outside that domestic INR core:

The two-rail model preserves everything Razorpay does well and routes the workloads above to a rail built for them.

What Razorpay Handles vs What MoltPe Handles

The split is along buyer type and currency, not feature parity. Razorpay wins for any payer who would naturally pay in INR. MoltPe wins for everything else.

Workload Best Rail Why
Indian customer paying in INR (UPI/card/netbanking) Razorpay Best UX, lowest fees, instant local settlement
EMI on consumer purchases Razorpay Native EMI integration with banks and BNPL providers
International customer paying in USD MoltPe (or both) USDC settles instantly with no FX spread
AI agent paying for an API call MoltPe x402 + isolated wallets + spending policy
Sub-dollar metered usage MoltPe No fixed-fee floor on USDC payments
Subscription billing on Indian cards Razorpay RBI mandate handling and recurring payment rails are mature
Paying overseas contractors MoltPe USDC payout to wallet; faster than international wire

The decision tree at runtime is short: Indian payer? Razorpay. International payer or agent? MoltPe. Anything ambiguous? Default to Razorpay and offer USDC as an option.

Migration Prep Checklist

This is an addition, not a replacement, so prep is lighter than a full migration. Twenty minutes per item is enough.

Routing Logic: Picking the Right Rail Per Request

The router is the heart of the two-rail stack. Keep it dumb and explicit.

// Two-rail router. Picks Razorpay or MoltPe based on payer signals.
// Default to Razorpay so existing flows stay unchanged.
function pickRail({ payerCountry, payerType, amountUsd, currency }) {
  // AI agents always pay through MoltPe.
  if (payerType === "agent") return "moltpe";

  // Sub-dollar charges only work on MoltPe.
  if (amountUsd < 1) return "moltpe";

  // International payers prefer USDC for FX savings.
  if (payerCountry !== "IN" && currency === "USD") return "moltpe";

  // Everyone else (Indian human payers in INR) goes to Razorpay.
  return "razorpay";
}

Then a single checkout entry point dispatches to the right SDK:

// One checkout endpoint. Two backends behind it.
import Razorpay from "razorpay";
const rzp = new Razorpay({
  key_id: process.env.RAZORPAY_KEY_ID,
  key_secret: process.env.RAZORPAY_KEY_SECRET,
});

app.post("/api/checkout", async (req, res) => {
  const rail = pickRail(req.body);

  if (rail === "razorpay") {
    const order = await rzp.orders.create({
      amount: req.body.amountInr * 100, // paise
      currency: "INR",
      receipt: req.body.invoiceId,
    });
    return res.json({ rail, order });
  }

  // MoltPe path: hosted invoice link or x402 endpoint.
  const moltpeRes = await fetch("https://api.moltpe.com/v1/invoices/create", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.MOLTPE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      invoice_number: req.body.invoiceId,
      amount: req.body.amountUsd.toFixed(2),
      token: "USDC",
      network: "polygon",
      recipient_agent_id: process.env.MOLTPE_TREASURY_AGENT_ID,
    }),
  });
  const invoice = await moltpeRes.json();
  res.json({ rail, hosted_payment_url: invoice.hosted_payment_url });
});

The reconciliation job pulls events from both webhooks and writes to one table. Finance sees one ledger; engineering sees two SDKs. Both teams stay happy.

Rollout Plan: Three Phases

Phase 1 — Parallel (week 1 to 2). Add the MoltPe rail behind a feature flag for a single use case — usually international SaaS receipts. Show "Pay with USDC" as an additional option, not a replacement. Track conversion and fee delta against the baseline Razorpay International numbers.

Phase 2 — Primary for matching workloads (week 3 to 6). For workloads where MoltPe wins on fee and speed (international, agent, sub-dollar), default to MoltPe. Razorpay remains the default for INR. Both checkouts coexist on the same site.

Phase 3 — Steady state (week 7 onward). The router becomes the single source of truth. Both rails are first-class. The only thing left to deprecate is internal documentation that says "we use Razorpay for everything" — update the README and onboard the team to think in two rails.

Risks and Rollback

The risks are mostly operational. Webhook double-processing — both rails fire events; idempotency keys at the database layer prevent double-counting. Customer confusion — some buyers do not know what USDC is; have a one-paragraph explainer next to the option. FX accounting — record USDC receipts at the spot rate on receipt date for clean tax filing.

Rollback is per-route, not all-or-nothing. If MoltPe traffic for a specific use case underperforms, change the router to send that case back to Razorpay. The Razorpay integration was never disturbed, so there is nothing to restore.