docs.checkout.payments

Payment modes

QuantaRoute supports three payment paths. Mode A and Mode B are available now. Mode C (QuantaRoute-hosted payments via Razorpay Route) is coming soon.

Three payment modes

Choose the mode that matches your existing payment stack. QuantaRoute never holds merchant funds long-term in any mode — customer payment settlement stays with your PG or Razorpay account.

Mode A — External PGMode B — BYO RazorpayMode C — Hosted
ProfilePayU, Cashfree, Paytm, ShopifyOwn Razorpay accountNo PG — wants simplicity
Widget paymentNo `enablePayment``enablePayment` + own keys`enablePayment` — no keys (coming soon)
Portal setupBYO PG guidePaste Razorpay keysKYC + bank only (coming soon)
SettlementYour PG → your bankYour Razorpay → your bankRoute Linked Account → bank (coming soon)
`session.completed` webhookNoYesYes (coming soon)
Ship statusAvailable nowAvailable nowComing soon
Need help choosing? Use Mode A if you already have PayU, Cashfree, Paytm, or Shopify checkout. Use Mode B if you have your own Razorpay account and want payment inside the widget.

Mode A — External payment gateway

QuantaRoute handles address capture, buyer identity, and DigiPin verification. Your existing PG handles payment and settlement.

Omit enablePayment (default is false). No QuantaRoute payment keys or Razorpay configuration required. Funds flow entirely through your existing PG to your bank account.

Widget embed

tsx
import { CheckoutWidget } from '@quantaroute/checkout';
import type { CompleteAddress } from '@quantaroute/checkout';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/style.css';

function buildMerchantCheckoutUrl(params: {
  sessionId: string;
  digipin: string;
  formattedAddress: string;
  pincode: string;
}) {
  const url = new URL('https://yourstore.com/checkout/pay');
  url.searchParams.set('session', params.sessionId);
  url.searchParams.set('digipin', params.digipin);
  url.searchParams.set('address', params.formattedAddress);
  url.searchParams.set('pincode', params.pincode);
  return url.toString();
}

export default function AddressStep() {
  return (
    <CheckoutWidget
      apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
      merchantId={process.env.NEXT_PUBLIC_MERCHANT_ID!}
      supabaseFunctionBaseUrl={process.env.NEXT_PUBLIC_SUPABASE_FUNCTION_URL!}
      enableEmailAuth
      // enablePayment omitted — merchant handles PG externally
      onComplete={(address: CompleteAddress) => {
        window.location.href = buildMerchantCheckoutUrl({
          sessionId: address.sessionId ?? '',
          digipin: address.digipin,
          formattedAddress: address.formattedAddress,
          pincode: address.pincode,
        });
      }}
    />
  );
}

onComplete handoff

When the buyer submits a verified delivery address, onComplete fires with a CompleteAddress object. Use sessionId as the correlation key between QuantaRoute events and your draft order.

text
https://merchant.com/checkout/pay?session=<sessionId>&digipin=<digipin>&address=<formattedAddress>&pincode=<pincode>

Lazy DigiPin verify

If a buyer selects a saved address imported without a DigiPin, the widget shows a map pin step before onComplete fires. Do not create a shippable order until handoff completes with a verified DigiPin.

Address stateBuyer experience
Has DigiPinOTP → pick address → onComplete immediately
Missing DigiPin (imported)OTP → pick address → map pin + confirm form → onComplete
Same address on next orderSkips verification — DigiPin already stored
session.completed is not fired in Mode A. Mark orders paid after your PG confirms payment. See Webhooks & events for Mode A webhook events.

Mode B — BYO Razorpay

Buyer pays inside the QuantaRoute widget using your Razorpay account. Settlement goes directly to your bank.

Set enablePayment={true} and configure Razorpay keys server-side via the checkout-admin API. The widget calls checkout-payment to create orders and verify payments — keys never ship to the browser bundle.

Configure Razorpay keys

bash
curl -X PATCH \
  "https://<project>.supabase.co/functions/v1/checkout-admin?action=set_payment_config" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_MERCHANT_ADMIN_KEY" \
  -d '{
    "razorpay_key_id": "rzp_test_...",
    "razorpay_secret_key": "YOUR_TEST_SECRET"
  }'

Verify with GET checkout-admin?action=get_payment_config — returns razorpay_key_id_set: true. The secret is never returned.

Widget embed with payment

tsx
import { CheckoutWidget } from '@quantaroute/checkout';
import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/style.css';

export default function CheckoutPage() {
  const cartTotalPaise = 499_00; // ₹499.00

  return (
    <CheckoutWidget
      apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
      merchantId={process.env.NEXT_PUBLIC_MERCHANT_ID!}
      supabaseFunctionBaseUrl={process.env.NEXT_PUBLIC_SUPABASE_FUNCTION_URL!}
      enableEmailAuth
      enablePayment
      paymentAmountPaise={cartTotalPaise}
      orderConfirmationUrl="https://yourstore.com/order/confirm?sid={{sessionId}}"
      onComplete={(address) => {
        console.log(address.digipin, address.formattedAddress);
      }}
      onPaymentComplete={(result) => {
        console.log(result.paymentMethod);
        console.log(result.razorpayOrderId, result.razorpayPaymentId);
      }}
    />
  );
}
After successful prepaid or COD payment, QuantaRoute fires a session.completed webhook to your backend. See Webhooks & events.

Mode C — QuantaRoute-hosted payments

Coming soon

QuantaRoute Payments

Integrated checkout + payments in one signup, without creating your own Razorpay or PayU account. QuantaRoute acts as the Razorpay platform; each merchant is onboarded as a Route Linked Account. Captured payments route to your bank on Razorpay's Route schedule (typically T+2).

on the roadmap

  • >Simple Shortest Path routing algorithm for India
  • >Turn-by-turn directions and distance matrices
  • >SDK guides and interactive API reference
PrerequisiteStatus
QuantaRoute master Razorpay account with Route enabledIn progress
Backend payment_mode = hosted_razorpayPlanned
Portal KYC form + Linked Account status trackingPlanned

Payment props reference

Payment-related widget props. Core address and auth props are documented on the Widget page.

PropTypeDefaultDescription
enablePaymentbooleanfalseShow Razorpay payment step after address confirmation (Mode B). Omit for Mode A external PG handoff.
paymentAmountPaisereqnumberCart total in paise (integer). Required when enablePayment is true.
enableCodbooleanfalseShow Cash on Delivery option when merchant COD config is enabled.
codCartTotalPaisenumberCart total used for COD eligibility checks (cod_max_cart_value_paise, blocked pincodes).
onPaymentComplete(result: PaymentResult) => voidCalled after prepaid Razorpay success or COD selection. Receives paymentMethod, razorpayOrderId, razorpayPaymentId.
orderConfirmationUrlstring (URL)Browser redirect after successful payment. {{sessionId}} is replaced with the checkout session ID.

Cash on Delivery (COD)

Enable COD on the merchant record, then pass enableCod on the widget. COD eligibility respects cod_blocked_pincodes and cod_max_cart_value_paise configured via checkout-admin.

bash
curl -X PATCH \
  "https://<project>.supabase.co/functions/v1/checkout-admin?action=cod_config" \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_MERCHANT_ADMIN_KEY" \
  -d '{ "enable_cod": true }'
tsx
<CheckoutWidget
  /* ...base props... */
  enableCod
  codCartTotalPaise={cartTotalPaise}
  enablePayment
  paymentAmountPaise={cartTotalPaise}
/>

Selecting COD calls checkout-payment record_cod and fires the same session.completed webhook with payment_method: "cod".

Configure webhooks

Set up signed webhook delivery for checkout lifecycle events.

Webhooks & events