JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 445
  • Score
    100M100P100Q95489F
  • License MIT

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

Package Exports

  • @quantaroute/checkout
  • @quantaroute/checkout/plugin
  • @quantaroute/checkout/style.css

Readme

@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

Platform Bundler Map engine Install
React / Next.js / Vite / Nuxt Webpack / Vite MapLibre GL JS maplibre-gl
iOS (Expo / React Native) Metro expo-osm-sdk (MapLibre GL Native) expo-osm-sdk expo-location
Android (Expo / React Native) Metro expo-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

  1. Sign up at developers.quantaroute.com
  2. Create a project → copy your 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:

What iOS Android
MapLibre native SDK expo-osm-sdk/plugin expo-osm-sdk/plugin
Location permission NSLocationWhenInUseUsageDescription ACCESS_FINE_LOCATION
Internet permission INTERNET

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

Prop Type Default Platform
apiKey string required all
onComplete (addr: CompleteAddress) => void required all
apiBaseUrl string https://api.quantaroute.com all
onError (err: Error) => void all
defaultLat number India center all
defaultLng number India center all
theme 'light' | 'dark' 'light' all
mapHeight string | number '380px' / 380 all
title string 'Add Delivery Address' all
className string web only
style CSSProperties | StyleProp<ViewStyle> all
indiaBoundaryUrl string web only
Prop Type Default Platform
enableSearch boolean true all

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)

Prop Type Default Platform
enablePhoneAuth boolean false all
merchantId string all
supabaseFunctionBaseUrl string all

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:

  1. A merchant account — sign up at developers.quantaroute.com/onboarding to get your merchantId and supabaseFunctionBaseUrl.
  2. The checkout-otp and checkout-addresses Edge Functions deployed (see Supabase setup).
<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)

Prop Type Default Platform
enableEmailAuth boolean false all
merchantId string all
supabaseFunctionBaseUrl string all

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 type When
email_step_viewed Email input screen rendered
email_otp_sent OTP dispatched via Resend
email_verified OTP correct → session created

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.

  1. Create a Resend account at resend.com — free tier covers ~3 000 emails / month.

  2. 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)
  3. Get an API key — Resend → API Keys → Create. Restrict it to Send access only.

  4. Set secrets on the checkout-email-otp Edge Function:

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

    Secret Value
    RESEND_API_KEY re_... from Resend
    RESEND_FROM_EMAIL A verified sender address, e.g. checkout@yourbrand.com
  5. 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.

Column Type Default Effect
email_from_name text "{brand} Checkout" "From" display name in the inbox, e.g. "Fabindia"
email_logo_url text (text brand name) HTTPS URL of your logo (PNG/SVG, 180 × 48 px recommended) shown at the top of every OTP email
email_subject_template text "{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

Prop Type Default Platform
brandColor string (hex) #0ea5e9 all
logoUrl string (URL) all
headerBanner ReactNode web only
<CheckoutWidget
  apiKey="..."
  brandColor="#7c3aed"
  logoUrl="https://yourbrand.com/logo.png"
  headerBanner={<p>🚚 Free shipping on orders over ₹499</p>}
  onComplete={...}
/>

Analytics (client-side)

Prop Type Default Platform
onCheckoutEvent (event: CheckoutEvent) => void all

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:

Event When fired Auth channel
phone_step_viewed Phone entry screen rendered Phone
otp_sent OTP dispatched via MSG91 Phone
phone_verified SMS OTP correct; session created Phone
email_step_viewed Email input screen rendered Email
email_otp_sent OTP dispatched via Resend Email
email_verified Email OTP correct; session created Email
auth_skipped Buyer tapped "Skip" Both
saved_addresses_viewed Saved address list rendered
saved_address_selected Buyer chose a saved address (skips map)
map_step_viewed Map pin step rendered
location_confirmed Buyer tapped "Confirm location"
form_step_viewed Address detail form rendered
address_completed Full address submitted
widget_reset Buyer 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):

Secret Value
RESEND_API_KEY re_... — from resend.com
RESEND_FROM_EMAIL Verified sender address, e.g. checkout@yourbrand.com

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

Secret Value
MSG91_AUTHKEY Your MSG91 AuthKey (setup guide) — must match the Edge Function env var name exactly
MSG91_TEMPLATE_ID Your 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

Doc Description
Changelog Version history and release notes
Checkout roadmap Feature phases and implementation status
MSG91 OTP setup India SMS / DLT configuration for phone OTP
Brand teardown Competitive analysis (Shopflo)

Security & integration

What ships in the npm package

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

Included Excluded
dist/lib/*.js (Terser-minified ES + UMD) supabase/, Edge Functions, migrations
dist/lib/index.d.ts demo/, docs/, openapi.yaml, .env*
dist/style.css vite.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):

  1. Onboard — complete merchant onboarding; copy merchantId, geocoding apiKey, and supabaseFunctionBaseUrl from the wizard or Checkout dashboard.
  2. Installnpm install @quantaroute/checkout maplibre-gl (+ CSS imports).
  3. Env vars — expose only public values (NEXT_PUBLIC_QUANTAROUTE_KEY, NEXT_PUBLIC_MERCHANT_ID, NEXT_PUBLIC_SUPABASE_FUNCTION_URL). Never commit .env to git.
  4. 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.
  5. 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).
  6. Optional brandingbrandColor, logoUrl; per-merchant email template columns (email_from_name, email_logo_url, email_subject_template) in the portal.
  7. 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. QuantaRoute 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)

Item Owner
Deploy & operate checkout-email-otp, checkout-addresses, checkout-session-expiry QuantaRoute
RESEND_API_KEY, RESEND_FROM_EMAIL, Supabase service role QuantaRoute (Edge Function secrets)
checkout.merchants provisioning, allowed_domains, email template columns QuantaRoute 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 limits QuantaRoute

Maintainer publish checklist

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

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

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.1.


Made with ❤️ in India · Powered by QuantaRoute