Sign In

@quantaroute/checkout

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@quantaroute/checkout

Embeddable DigiPin-powered smart address checkout widget — React (web), React Native & Expo (iOS/Android)

latest
Source
npmnpm
Version
1.7.2
Version published
Weekly downloads
58
45%
Maintainers
1
Weekly downloads
 
Created
Source

@quantaroute/checkout

DigiPin-powered smart address checkout widget for Indian e-commerce. Two-step: Map pin → Auto-filled form. One package. Three platforms: React web · iOS · Android.

Made in India DigiPin Map License

What it does

Replaces broken Indian address forms with a precise two-step pin-drop flow:

Step 1 – Map Pin                    Step 2 – Auto-fill + Details
┌──────────────────────────┐        ┌──────────────────────────┐
│  [DigiPin: 39J-438-TJC7] │        │  📍 Auto-detected         │
│                           │        │  State:    Delhi          │
│    🗺  OSM vector map     │  ──►  │  District: New Delhi     │
│         📍 ← drag         │        │  Pincode:  110011         │
│                           │        │  ✓ Deliverable            │
│  [⊕ Locate Me]            │        │  🏠 Add details           │
│  [Confirm Location →]     │        │  Flat No:  [_________]    │
└──────────────────────────┘        │  [← Adjust] [Save ✓]    │
                                     └──────────────────────────┘

Key features:

  • DigiPin shown offline in real-time as the user drags the pin (~4 m × 4 m precision)
  • No Google Maps. Free Carto Positron vector basemap
  • Auto-fills State, District, Locality, Pincode, Delivery status from QuantaRoute API
  • Manual fields: Flat no., Floor, Building (OSM pre-filled), Street/Area (OSM pre-filled)
  • Mobile-first (full-screen on phones, card on desktop/tablet)
  • Dark mode, reduced-motion, keyboard navigation, ARIA labels
  • TypeScript — zero runtime deps beyond peer dependencies

Platform support

PlatformBundlerMap engineInstall
React / Next.js / Vite / NuxtWebpack / ViteMapLibre GL JSmaplibre-gl
iOS (Expo / React Native)Metroexpo-osm-sdk (MapLibre GL Native)expo-osm-sdk expo-location
Android (Expo / React Native)Metroexpo-osm-sdk (MapLibre GL Native)expo-osm-sdk expo-location

Same import on all platforms. Metro automatically resolves .native.tsx files on mobile; Vite/Webpack use the web .tsx files.

Quick start

0 · Get an API key

Never hard-code or commit API keys to git.

Web (React / Next.js / Vite / Nuxt)

Install

npm install @quantaroute/checkout maplibre-gl

Import CSS

// In your app entry file (main.tsx / _app.tsx / layout.tsx / nuxt.config.ts)
import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/style.css';

Use

import { CheckoutWidget } from '@quantaroute/checkout';

export default function CheckoutPage() {
  return (
    <CheckoutWidget
      apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
      onComplete={(address) => {
        console.log(address.digipin);           // "39J-438-TJC7"
        console.log(address.pincode);           // "110011"
        console.log(address.formattedAddress);  // "Flat 4B, Floor 3rd, ..."
        // → send to your backend / payment gateway
      }}
    />
  );
}

Next.js (App Router)

// app/checkout/page.tsx
'use client';

import dynamic from 'next/dynamic';

const CheckoutWidget = dynamic(
  () => import('@quantaroute/checkout').then((m) => m.CheckoutWidget),
  { ssr: false }
);

export default function CheckoutPage() {
  return (
    <main className="max-w-lg mx-auto p-4">
      <CheckoutWidget
        apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
        onComplete={(addr) => console.log(addr)}
      />
    </main>
  );
}

CSS in app/layout.tsx:

import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/style.css';

Next.js (Pages Router)

import dynamic from 'next/dynamic';
const CheckoutWidget = dynamic(() => import('@quantaroute/checkout'), { ssr: false });

export default function CheckoutPage() {
  return (
    <CheckoutWidget
      apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
      onComplete={(a) => console.log(a)}
    />
  );
}

Nuxt 3

// nuxt.config.ts
export default defineNuxtConfig({
  css: ['maplibre-gl/dist/maplibre-gl.css', '@quantaroute/checkout/style.css'],
});
<!-- components/AddressWidget.client.vue  (.client = browser-only) -->
<script setup lang="ts">
import { CheckoutWidget } from '@quantaroute/checkout';
const { public: { qrApiKey } } = useRuntimeConfig();
</script>
<template>
  <CheckoutWidget :api-key="qrApiKey" map-height="360px" @complete="console.log" />
</template>

Vite + React

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

function App() {
  return (
    <CheckoutWidget
      apiKey={import.meta.env.VITE_QR_API_KEY}
      onComplete={(addr) => console.log('Saved:', addr)}
    />
  );
}

Vanilla JS (UMD / script tag)

<link rel="stylesheet" href="https://unpkg.com/maplibre-gl/dist/maplibre-gl.css" />
<link rel="stylesheet" href="https://unpkg.com/@quantaroute/checkout/dist/style.css" />

<div id="checkout-root"></div>

<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/maplibre-gl/dist/maplibre-gl.js"></script>
<script src="https://unpkg.com/@quantaroute/checkout/dist/lib/quantaroute-checkout.umd.js"></script>
<script>
  ReactDOM.createRoot(document.getElementById('checkout-root')).render(
    React.createElement(QuantaRouteCheckout.CheckoutWidget, {
      apiKey: 'YOUR_KEY',
      onComplete: (addr) => console.log('Done:', addr),
    })
  );
</script>

Native (Expo / React Native — iOS & Android)

Requires a development build. This does NOT work in Expo Go — expo-osm-sdk uses native modules.

1 · Install

npm install @quantaroute/checkout expo-osm-sdk expo-location

2 · Add the config plugin to app.json

{
  "expo": {
    "plugins": [
      ["@quantaroute/checkout/plugin", {
        "locationPermissionText": "Allow access to your location to place the delivery pin on the map."
      }]
    ]
  }
}

This single plugin automatically configures:

WhatiOSAndroid
MapLibre native SDKexpo-osm-sdk/pluginexpo-osm-sdk/plugin
Location permissionNSLocationWhenInUseUsageDescriptionACCESS_FINE_LOCATION
Internet permissionINTERNET

3 · Rebuild your dev client

npx expo run:ios
# or
npx expo run:android

4 · Use — identical import as web

import { CheckoutWidget } from '@quantaroute/checkout';
// No CSS import — styles are React Native StyleSheet objects

export default function CheckoutScreen() {
  return (
    <CheckoutWidget
      apiKey={process.env.EXPO_PUBLIC_QUANTAROUTE_KEY!}
      mapHeight={380}
      onComplete={(address) => {
        console.log(address.digipin);           // "39J-438-TJC7"
        console.log(address.formattedAddress);  // "Flat 4B, ..."
      }}
    />
  );
}

Demo native app

A working Expo demo is in demo-native/:

cd demo-native
npm install
# Set your API key in .env:  EXPO_PUBLIC_QUANTAROUTE_API_KEY=your_key
npx expo run:ios
npx expo run:android

Props

Core props

PropTypeDefaultPlatform
apiKeystringrequiredall
onComplete(addr: CompleteAddress) => voidrequiredall
apiBaseUrlstringhttps://api.quantaroute.comall
onError(err: Error) => voidall
defaultLatnumberIndia centerall
defaultLngnumberIndia centerall
theme`'light''dark'`'light'
mapHeight`stringnumber`'380px' / 380
titlestring'Add Delivery Address'all
classNamestringweb only
style`CSSPropertiesStyleProp`
indiaBoundaryUrlstringweb only
PropTypeDefaultPlatform
enableSearchbooleantrueall

When enableSearch is true, a unified search bar appears above the map. It handles two modes automatically:

  • Address search — calls /v1/digipin/autocomplete (debounced 400 ms, location-biased)
  • DigiPin input — decoded 100 % offline, zero API cost, instant map flyTo

As India Post promotes DigiPin nationally, buyers can paste their 10-character code and land on their exact ±4 m location.

Phone OTP + saved addresses (D2C checkout)

PropTypeDefaultPlatform
enablePhoneAuthbooleanfalseall
merchantIdstringall
supabaseFunctionBaseUrlstringall

When enablePhoneAuth is true the widget starts with a phone-entry step. Returning buyers can pick a previously saved address from any brand in the network instead of dropping the pin again.

On the address details step, buyers choose a Home / Work / Other label before saving. The label is stored in the checkout-addresses API and returned on CompleteAddress.label.

Requires:

<CheckoutWidget
  apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
  merchantId="your-merchant-uuid"
  supabaseFunctionBaseUrl="https://<project>.supabase.co/functions/v1"
  enablePhoneAuth={true}
  enableSearch={true}
  onComplete={(addr) => console.log(addr)}
/>

Email OTP auth (enableEmailAuth)

PropTypeDefaultPlatform
enableEmailAuthbooleanfalseall
merchantIdstringall
supabaseFunctionBaseUrlstringall

When enableEmailAuth is true the widget starts with an email-entry step. The buyer receives a 6-digit OTP via Resend. On success a checkout session is created and saved addresses are shown if available.

If both enablePhoneAuth and enableEmailAuth are set, phone OTP takes precedence.

<CheckoutWidget
  apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
  merchantId="your-merchant-uuid"
  supabaseFunctionBaseUrl="https://<project>.supabase.co/functions/v1"
  enableEmailAuth={true}
  onComplete={(addr) => console.log(addr)}
  onCheckoutEvent={(event) => {
    // email-specific events: email_step_viewed | email_otp_sent | email_verified
    console.log('[checkout]', event.type, event);
  }}
/>

Client-side events emitted during email auth:

Event typeWhen
email_step_viewedEmail input screen rendered
email_otp_sentOTP dispatched via Resend
email_verifiedOTP correct → session created

Merchant courier serviceability

QuantaRoute still validates that a pincode exists in the India Post database, but India Post "Non Delivery" is treated as reference data, not the final merchant delivery decision. Most D2C stores ship through Shiprocket, Delhivery, ClickPost, or direct courier contracts, so the buyer-facing serviceability check should come from the merchant.

PropTypeDefaultPlatform
enableServiceabilityCheckbooleanfalseall
showIndiaPostDeliveryWarningbooleanfalseall
serviceabilityBlockCheckoutbooleanmerchant settingall

When enableServiceabilityCheck is true and the buyer has a checkout session, the widget calls checkout-addresses with action: "serviceability_check". The Edge Function calls the merchant's configured courier endpoint server-side and fails open if that endpoint is unavailable.

<CheckoutWidget
  apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!}
  merchantId={process.env.NEXT_PUBLIC_MERCHANT_ID!}
  supabaseFunctionBaseUrl={process.env.NEXT_PUBLIC_SUPABASE_FUNCTION_URL!}
  enableEmailAuth={true}
  enableServiceabilityCheck={true}
  onComplete={(addr) => console.log(addr)}
/>

Merchant configuration lives on checkout.merchants:

ColumnMeaning
serviceability_enabledEnables the merchant courier check
serviceability_webhook_urlMerchant endpoint called by QuantaRoute
serviceability_webhook_secretShared HMAC secret for outbound calls
serviceability_block_checkoutIf true, serviceable: false blocks address save
show_india_post_delivery_warningOptional soft India Post info banner fallback

Merchant endpoint contract:

POST /checkout/serviceability
Content-Type: application/json
X-QuantaRoute-Timestamp: 1782000000000
X-QuantaRoute-Signature: v1=<hmac_sha256(timestamp + "." + body)>
{
  "merchant_id": "merchant-uuid",
  "pincode": "110011",
  "lat": 28.6139,
  "lng": 77.209,
  "requested_at": "2026-06-21T00:00:00.000Z"
}

Expected response:

{ "serviceable": true, "message": "Delivery available in 3-5 days" }

or:

{ "serviceable": false, "message": "We do not currently ship to this pincode" }

If the merchant endpoint errors, times out, or returns an invalid response, checkout fails open and the buyer can continue. showIndiaPostDeliveryWarning is opt-in and only shows a soft informational banner: "Courier partners may still deliver to this pincode."

Setting up Resend (required for enableEmailAuth)

Hosted merchants (recommended): If you onboarded at developers.quantaroute.com, QuantaRoute already runs Resend on shared infrastructure. Skip steps 1–5 below — just set enableEmailAuth={true} and register your domain in the portal. The steps below apply only when self-hosting the checkout-email-otp Edge Function in your own Supabase project.

  • Create a Resend account at resend.com — free tier covers ~3 000 emails / month.
  • Verify your sending domain (e.g. checkout.yourbrand.com or yourbrand.com):
  • Go to Resend → Domains → Add domain
  • Add the DNS records shown (SPF, DKIM, DMARC) to your DNS provider
  • Wait for "Verified" status (usually under 5 minutes with Cloudflare; up to 48 h with others)
  • Get an API key — Resend → API Keys → Create. Restrict it to Send access only.
  • Set secrets on the checkout-email-otp Edge Function: In Supabase Dashboard → your project → Edge Functions → Secrets:
SecretValue
RESEND_API_KEYre_... from Resend
RESEND_FROM_EMAILA verified sender address, e.g. checkout@yourbrand.com
  • Deploy the Edge Function (if not already done):
 supabase functions deploy checkout-email-otp --project-ref <your-project-ref> --no-verify-jwt

Required env vars (frontend):

NEXT_PUBLIC_QUANTAROUTE_KEY=dp_...
NEXT_PUBLIC_MERCHANT_ID=your-uuid-from-developer-portal
NEXT_PUBLIC_SUPABASE_FUNCTION_URL=https://<project-ref>.supabase.co/functions/v1

Per-merchant email template customisation

Set these columns directly on your checkout.merchants row (SQL or Supabase Table Editor). All are optional — omitting any falls back to the QuantaRoute default.

ColumnTypeDefaultEffect
email_from_nametext"{brand} Checkout""From" display name in the inbox, e.g. "Fabindia"
email_logo_urltext(text brand name)HTTPS URL of your logo (PNG/SVG, 180 × 48 px recommended) shown at the top of every OTP email
email_subject_templatetext"{code} is your {brand} checkout code"Subject line; supports {code} and {brand} placeholders

Example SQL:

UPDATE checkout.merchants
SET
  email_from_name        = 'Fabindia',
  email_logo_url         = 'https://cdn.fabindia.com/logo-email.png',
  email_subject_template = '{code} — verify your Fabindia order'
WHERE id = 'your-merchant-uuid';

Branding

PropTypeDefaultPlatform
brandColorstring (hex)#0ea5e9all
logoUrlstring (URL)all
headerBannerReactNodeweb only
mobilePresentation'auto' | 'sheet' | 'card''auto'web
mobileStepLabels{ auth?: string; address?: string; payment?: string }Mobile/Address/Payweb
<CheckoutWidget
  apiKey="..."
  brandColor="#7c3aed"
  logoUrl="https://yourbrand.com/logo.png"
  headerBanner={<p>🚚 Free shipping on orders over ₹499</p>}
  mobilePresentation="sheet"
  mobileStepLabels={{ auth: 'Mobile', address: 'Address', payment: 'Pay' }}
  onComplete={...}
/>

Mobile-first storefront shells

Use mobilePresentation="sheet" when the widget is mounted inside a mobile drawer or storefront modal. It keeps the package focused on auth, saved addresses, DigiPin verification, and optional payment while your host app owns cart-specific UI.

<CheckoutWidget
  apiKey={geocodingKey}
  merchantId={merchantId}
  supabaseFunctionBaseUrl={functionsUrl}
  enablePhoneAuth
  enablePayment={merchantPaymentMode === 'byo_razorpay'}
  paymentAmountPaise={cartTotalPaiseAfterDiscounts}
  codCartTotalPaise={cartTotalPaiseAfterDiscounts}
  mobilePresentation="sheet"
  mobileStepLabels={{ auth: 'Mobile', address: 'Address', payment: 'Pay' }}
  onCheckoutEvent={(event) => syncHostShellStep(event.step)}
  onComplete={(address) => saveDeliveryAddress(address)}
  onPaymentComplete={(result) => completeOrder(result)}
/>

The host app should keep order summary, product lines, coupons, cart state, platform APIs, and checkout-button interception outside this package. Pass only the final totals and callbacks into the widget.

Analytics (client-side)

PropTypeDefaultPlatform
onCheckoutEvent(event: CheckoutEvent) => voidall

Called on every step transition. Use it to forward events to MoEngage, Clevertap, GTM, or your own backend — no server-side integration required.

<CheckoutWidget
  apiKey="..."
  onCheckoutEvent={(event) => {
    // event.type — e.g. 'phone_verified', 'location_confirmed', 'address_completed'
    // event.step — widget step the user moved INTO
    // event.buyerId — internal buyer UUID (after phone verification)
    // event.digipin — DigiPin of the confirmed location
    // event.timestamp — ISO 8601 timestamp

    // MoEngage example:
    Moengage.track_event(event.type, {
      step: event.step,
      buyerId: event.buyerId,
      digipin: event.digipin,
    });

    // GTM example:
    window.dataLayer?.push({ event: event.type, ...event });
  }}
  onComplete={...}
/>

All event types:

EventWhen firedAuth channel
phone_step_viewedPhone entry screen renderedPhone
otp_sentOTP dispatched via MSG91Phone
phone_verifiedSMS OTP correct; session createdPhone
email_step_viewedEmail input screen renderedEmail
email_otp_sentOTP dispatched via ResendEmail
email_verifiedEmail OTP correct; session createdEmail
auth_skippedBuyer tapped "Skip"Both
saved_addresses_viewedSaved address list rendered
saved_address_selectedBuyer chose a saved address (skips map)
map_step_viewedMap pin step rendered
location_confirmedBuyer tapped "Confirm location"
form_step_viewedAddress detail form rendered
address_completedFull address submitted
widget_resetBuyer tapped "Change address"

CompleteAddress output

interface CompleteAddress {
  digipin: string;          // "39J-438-TJC7"
  lat: number;              // 28.61390
  lng: number;              // 77.20900

  // Auto-filled from QuantaRoute API
  state: string;            // "Delhi"
  district: string;         // "New Delhi"
  division: string;         // "New Delhi Central"
  locality: string;         // "Nirman Bhawan SO"
  pincode: string;          // "110011"
  delivery: string;         // "Delivery" | "Non Delivery"
  country: string;          // "India"

  // Manual entry by user
  flatNumber: string;       // "4B"
  floorNumber: string;      // "3rd"
  buildingName: string;     // "Sunshine Apartments"  (OSM pre-filled)
  streetName: string;       // "MG Road, Action Area" (OSM pre-filled)

  formattedAddress: string; // "4B, 3rd Floor, Sunshine Apartments, MG Road, ..."
  label?: 'home' | 'work' | 'other'; // chosen on address form; defaults to home
}

When enablePhoneAuth or enableEmailAuth is on, the widget persists the address via createAddress() using this label.

type AddressLabel = 'home' | 'work' | 'other';

Advanced usage

Use sub-components individually (web + native)

import { MapPinSelector, AddressForm, getDigiPin, isWithinIndia } from '@quantaroute/checkout';

// Offline DigiPin — no API call, ~0.1 ms
const dp = getDigiPin(28.6139, 77.2090); // "39J-438-TJC7"
const ok = isWithinIndia(28.6139, 77.2090); // true

// Custom two-step flow
function MyCheckout() {
  const [loc, setLoc] = useState<{ lat: number; lng: number; digipin: string } | null>(null);

  return loc == null
    ? <MapPinSelector onLocationConfirm={(lat, lng, digipin) => setLoc({ lat, lng, digipin })} />
    : <AddressForm
        lat={loc.lat}
        lng={loc.lng}
        digipin={loc.digipin}
        apiKey="..."
        onAddressComplete={(addr) => console.log(addr)}
        onBack={() => setLoc(null)}
      />;
}

Dark mode (web)

<CheckoutWidget apiKey="..." theme="dark" onComplete={...} />

Custom web theme via CSS variables

.qr-checkout {
  --qr-primary:      #6366f1;
  --qr-primary-dark: #4f46e5;
  --qr-radius:       8px;
  --qr-font:         'Poppins', sans-serif;
}

India boundary overlay (web only)

<CheckoutWidget
  apiKey="..."
  indiaBoundaryUrl="/geojson/india.geojson"  {/* hosted in your public folder */}
  onComplete={...}
/>

Supabase setup

Only needed when using **enablePhoneAuth**. Skip this section for the basic map-only widget.

1 · Get your merchant credentials

Sign up at developers.quantaroute.com/onboarding/merchant. The wizard auto-provisions:

  • merchant_id — your UUID in checkout.merchants
  • Geocoding API key (for address search and reverse-geocoding)
  • Integration snippet with all values filled in

2 · Deploy Edge Functions

The Edge Functions live in supabase/functions/ inside this repo. Deploy all of them:

# Address CRUD + COD eligibility
supabase functions deploy checkout-addresses      --project-ref <your-project-ref> --no-verify-jwt

# Email OTP auth (Resend — no TRAI dependency, ship now)
supabase functions deploy checkout-email-otp      --project-ref <your-project-ref> --no-verify-jwt

# Phone OTP auth (MSG91 + TRAI DLT — deploy now, enable when DLT is ready)
supabase functions deploy checkout-otp            --project-ref <your-project-ref> --no-verify-jwt

# Session expiry — fires session.expired webhooks; runs every 10 min via pg_cron
supabase functions deploy checkout-session-expiry --project-ref <your-project-ref> --no-verify-jwt

3 · Set Edge Function secrets

In the Supabase Dashboard → your project → Edge Functions → Secrets:

**checkout-email-otp (required for email auth):**

SecretValue
RESEND_API_KEYre_... — from resend.com
RESEND_FROM_EMAILVerified sender address, e.g. checkout@yourbrand.com

**checkout-otp (required for phone/SMS auth — set when TRAI DLT is approved):**

SecretValue
MSG91_AUTHKEYYour MSG91 AuthKey (setup guide) — must match the Edge Function env var name exactly
MSG91_TEMPLATE_IDYour approved DLT OTP template ID

4 · Add your domain to the allowed list

In the developer portal at developers.quantaroute.com/dashboard/checkout, add your storefront's domain (e.g. yourbrand.com). The checkout-email-otp and checkout-otp Edge Functions enforce this list — requests from unlisted origins receive 403 Origin not allowed.

Minimal Next.js integration

Email OTP (available now — no TRAI paperwork)

// app/checkout/page.tsx
'use client';
import dynamic from 'next/dynamic';

const CheckoutWidget = dynamic(
  () => import('@quantaroute/checkout').then((m) => m.CheckoutWidget),
  { ssr: false }
);

export default function CheckoutPage() {
  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={true}        {/* ← email OTP via Resend */}
      enableSearch={true}
      brandColor="#your-brand-hex"
      logoUrl="https://yourbrand.com/logo.png"
      onComplete={(address) => {
        // address.digipin, address.formattedAddress, address.pincode, ...
        // → pass to your backend / payment gateway
      }}
      onCheckoutEvent={(event) => {
        // email_step_viewed | email_otp_sent | email_verified | address_completed | ...
        console.log('[checkout]', event.type, event);
      }}
    />
  );
}

Phone OTP (enable when TRAI DLT is approved — same widget, one prop swap)

<CheckoutWidget
  ...
  enablePhoneAuth={true}   {/* swap enableEmailAuth → enablePhoneAuth when SMS is ready */}
  enableEmailAuth={false}
/>

Required env vars:

NEXT_PUBLIC_QUANTAROUTE_KEY=dp_...
NEXT_PUBLIC_MERCHANT_ID=your-uuid-from-developer-portal
NEXT_PUBLIC_SUPABASE_FUNCTION_URL=https://<project-ref>.supabase.co/functions/v1

Architecture

@quantaroute/checkout/
├── src/
│   ├── components/
│   │   ├── CheckoutWidget.tsx           ← web  (MapLibre GL JS)
│   │   ├── CheckoutWidget.native.tsx    ← native (SafeAreaView)
│   │   ├── MapPinSelector.tsx           ← web  (MapLibre marker)
│   │   ├── MapPinSelector.native.tsx    ← native (expo-osm-sdk OSMView)
│   │   ├── AddressForm.tsx              ← web  (HTML form)
│   │   └── AddressForm.native.tsx       ← native (TextInput / Modal)
│   ├── core/
│   │   ├── digipin.ts     ← offline DigiPin algorithm (shared, no DOM)
│   │   ├── api.ts         ← QuantaRoute API client (shared, fetch)
│   │   └── types.ts       ← TypeScript types (shared)
│   ├── hooks/
│   │   ├── useGeolocation.ts            ← web  (navigator.geolocation)
│   │   ├── useGeolocation.native.ts     ← native (expo-location)
│   │   └── useDigiPin.ts                ← shared (pure math)
│   └── styles/
│       ├── checkout.css                 ← web styles
│       └── checkout.native.ts           ← native StyleSheet.create()
├── expo-plugin.js   ← Expo config plugin
├── babel.config.js  ← Metro/Babel config
└── dist/            ← web build output (Vite)

Platform resolution:

Metro (Expo app)          → "react-native" export → src/index.ts
                            → MapPinSelector.native.tsx (expo-osm-sdk)
                            → useGeolocation.native.ts (expo-location)

Vite / Webpack / Next.js  → "import" export → dist/lib/quantaroute-checkout.es.js
                            → MapPinSelector.tsx (MapLibre GL JS)
                            → useGeolocation.ts (navigator.geolocation)

Map tile license

Uses Carto Positron vector tiles (Carto Voyager on native):

  • Free for commercial use — attribution required
  • Attribution: © OpenStreetMap contributors © CARTO
  • No API key required for Carto basemaps

DigiPin license

The offline DigiPin algorithm is the official India Post implementation:

Documentation

DocDescription
ChangelogVersion history and release notes
Checkout roadmapFeature phases and implementation status
Merchant onboardingPayment modes A/B/C, Razorpay setup (Mode B)
BYO payment gatewayMode A — PayU/Cashfree/Paytm/Shopify integration
Hosted payments roadmapMode C — QuantaRoute Payments (coming soon)
Razorpay E2E setupTest-mode verification checklist (Mode B)
Customer migration playbookMerchant-facing import + lazy DigiPin story
Portal payment wizard specDeveloper portal — /checkout/payments spec
Portal migration wizard specDeveloper portal — /checkout/migrate spec
MSG91 OTP setupIndia SMS / DLT configuration for phone OTP

Security & integration

What ships in the npm package

The published tarball contains no secrets — only client-side widget code:

IncludedExcluded
dist/lib/*.js (Terser-minified ES + UMD)supabase/, Edge Functions, migrations
dist/lib/index.d.tsdemo/, docs/, openapi.yaml, .env*
dist/style.cssvite.config.ts, dev tooling
src/ (required for React Native / Metro)node_modules/
  • Geocoding apiKey (dp_…) is designed for the browser bundle — rate-limited, scoped to your merchant plan.
  • Admin API key (qca_…) must never ship in frontend code. Use it server-side only (x-api-key on checkout-admin).
  • OTP delivery (Resend) and session minting run on QuantaRoute-hosted Edge Functions, not inside the npm bundle.
  • Consistent email OTP: the same 6-digit code is stored per (email, merchant) and reused for up to 10 successful logins (not single-use). Repeat send requests skip Resend when the code is still active. This reduces email cost but is weaker than banking-grade OTP — document for your shoppers if required by policy.

Verify before every publish:

npm run type-check && npm run build:lib
npm pack --dry-run          # confirm 36 files, no .env / supabase / demo
rg -i 're_|sk_|service.role|password' dist/   # should return no matches

Merchant integration checklist (email OTP on web)

Use this when embedding @quantaroute/checkout on a hosted QuantaRoute storefront (onboarded via developers.quantaroute.com):

  • Onboard — complete merchant onboarding; copy merchantId, geocoding apiKey, and supabaseFunctionBaseUrl from the wizard or Checkout dashboard.
  • Installnpm install @quantaroute/checkout maplibre-gl (+ CSS imports).
  • Env vars — expose only public values (NEXT_PUBLIC_QUANTAROUTE_KEY, NEXT_PUBLIC_MERCHANT_ID, NEXT_PUBLIC_SUPABASE_FUNCTION_URL). Never commit .env to git.
  • Allowed domains — in the developer portal, add every origin that will host the widget (e.g. yourbrand.com, www.yourbrand.com, *.yourbrand.com for subdomains). Both checkout-otp and checkout-email-otp reject requests from unlisted origins.
  • Enable auth — set enableEmailAuth={true} with merchantId and supabaseFunctionBaseUrl. Auth is opt-in and web-only today (React Native widget does not include the email OTP steps).
  • Optional brandingbrandColor, logoUrl; per-merchant email template columns (email_from_name, email_logo_url, email_subject_template) in the portal.
  • Handle completion — implement onComplete to pass the CompleteAddress to your cart / payment gateway. Built-in payment (enablePayment) and COD (enableCod) are separate opt-in props.

Resend is platform-managed for hosted merchants. QuantaRouHMACte operates the shared checkout-email-otp Edge Function and Resend sender (RESEND_API_KEY, RESEND_FROM_EMAIL). Merchants do not need their own Resend account for the hosted MVP. See Setting up Resend only if you self-host the Edge Functions.

Platform responsibilities (QuantaRoute)

ItemOwner
Deploy & operate checkout-email-otp, checkout-addresses, checkout-session-expiryQuantaRoute
RESEND_API_KEY, RESEND_FROM_EMAIL, Supabase service roleQuantaRoute (Edge Function secrets)
checkout.merchants provisioning, allowed_domains, email template columnsQuantaRoute portal + DB
Buyer OTP consistent reuse (10 logins per code), fresh-code limit (3 / 24h), verify lockout (5 fails → 15 min)Edge Function
Geocoding API key issuance & plan rate limitsQuantaRoute

Maintainer publish checklist

When publishing a new version to npm (maintainers only):

  • Enable npm 2FA on the @quantaroute org account (publish + modify).
  • Run npm run type-check && npm run build:lib && npm pack --dry-run — confirm tarball contents.
  • Confirm no .env, .pem, or supabase/ paths in the pack list.
  • Publish from a clean git tag with provenance: npm publish --provenance --access public.
  • Do not bump version in the same change unless releasing; current version is 1.4.5.

Development

git clone https://github.com/quantaroute/checkout.git
cd quantaroute-checkout
npm install

# Web dev server
npm run dev              # http://localhost:5173

# Type checks
npm run type-check       # web (Vite tsconfig)
npm run type-check:native # native (RN tsconfig)

# Build
npm run build:lib        # library → dist/lib/
npm run build            # library + demo

Native demo:

cd demo-native
npm install
npx expo run:ios         # requires Xcode
npx expo run:android     # requires Android Studio

Changelog

See CHANGELOG.md for version history. Current release: v1.4.5.

Made with ❤️ in India · Powered by QuantaRoute

Keywords

digipin

FAQs

Package last updated on 05 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts