
Research
/Security News
Popular Rust Crates Compromised in Build-Time Supply Chain Attack
Three compromised Rust crates pulled in a malicious dependency that downloaded and executed cross-platform malware during Cargo builds.
@quantaroute/checkout
Advanced tools
Embeddable DigiPin-powered smart address checkout widget — React (web), React Native & Expo (iOS/Android)
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
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:
| 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.tsxfiles on mobile; Vite/Webpack use the web.tsxfiles.
Never hard-code or commit API keys to git.
npm install @quantaroute/checkout maplibre-gl
// 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';
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
}}
/>
);
}
// 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';
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.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>
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)}
/>
);
}
<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>
Requires a development build. This does NOT work in Expo Go —
expo-osm-sdkuses native modules.
npm install @quantaroute/checkout expo-osm-sdk expo-location
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 |
npx expo run:ios
# or
npx expo run:android
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, ..."
}}
/>
);
}
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
| 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' |
mapHeight | `string | number` | '380px' / 380 |
title | string | 'Add Delivery Address' | all |
className | string | – | web only |
style | `CSSProperties | StyleProp` | – |
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:
/v1/digipin/autocomplete (debounced 400 ms, location-biased)As India Post promotes DigiPin nationally, buyers can paste their 10-character code and land on their exact ±4 m location.
| 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:
merchantId and supabaseFunctionBaseUrl.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)}
/>
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
enablePhoneAuthandenableEmailAuthare 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 |
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.
| Prop | Type | Default | Platform |
|---|---|---|---|
enableServiceabilityCheck | boolean | false | all |
showIndiaPostDeliveryWarning | boolean | false | all |
serviceabilityBlockCheckout | boolean | merchant setting | all |
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:
| Column | Meaning |
|---|---|
serviceability_enabled | Enables the merchant courier check |
serviceability_webhook_url | Merchant endpoint called by QuantaRoute |
serviceability_webhook_secret | Shared HMAC secret for outbound calls |
serviceability_block_checkout | If true, serviceable: false blocks address save |
show_india_post_delivery_warning | Optional 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."
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 thecheckout-email-otpEdge Function in your own Supabase project.
checkout.yourbrand.com or yourbrand.com):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 |
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
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';
| 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={...}
/>
| 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_otp_sent | OTP dispatched via Resend | |
email_verified | Email OTP correct; session created | |
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 outputinterface 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';
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)}
/>;
}
<CheckoutWidget apiKey="..." theme="dark" onComplete={...} />
.qr-checkout {
--qr-primary: #6366f1;
--qr-primary-dark: #4f46e5;
--qr-radius: 8px;
--qr-font: 'Poppins', sans-serif;
}
<CheckoutWidget
apiKey="..."
indiaBoundaryUrl="/geojson/india.geojson" {/* hosted in your public folder */}
onComplete={...}
/>
Only needed when using
**enablePhoneAuth**. Skip this section for the basic map-only widget.
Sign up at developers.quantaroute.com/onboarding/merchant. The wizard auto-provisions:
merchant_id — your UUID in checkout.merchantsThe 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
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 |
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.
// 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);
}}
/>
);
}
<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
@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)
Uses Carto Positron vector tiles (Carto Voyager on native):
© OpenStreetMap contributors © CARTOThe offline DigiPin algorithm is the official India Post implementation:
| Doc | Description |
|---|---|
| Changelog | Version history and release notes |
| Checkout roadmap | Feature phases and implementation status |
| Merchant onboarding | Payment modes A/B/C, Razorpay setup (Mode B) |
| BYO payment gateway | Mode A — PayU/Cashfree/Paytm/Shopify integration |
| Hosted payments roadmap | Mode C — QuantaRoute Payments (coming soon) |
| Razorpay E2E setup | Test-mode verification checklist (Mode B) |
| Customer migration playbook | Merchant-facing import + lazy DigiPin story |
| Portal payment wizard spec | Developer portal — /checkout/payments spec |
| Portal migration wizard spec | Developer portal — /checkout/migrate spec |
| MSG91 OTP setup | India SMS / DLT configuration for phone OTP |
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/ |
apiKey (dp_…) is designed for the browser bundle — rate-limited, scoped to your merchant plan.qca_…) must never ship in frontend code. Use it server-side only (x-api-key on checkout-admin).(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
Use this when embedding @quantaroute/checkout on a hosted QuantaRoute storefront (onboarded via developers.quantaroute.com):
merchantId, geocoding apiKey, and supabaseFunctionBaseUrl from the wizard or Checkout dashboard.npm install @quantaroute/checkout maplibre-gl (+ CSS imports).NEXT_PUBLIC_QUANTAROUTE_KEY, NEXT_PUBLIC_MERCHANT_ID, NEXT_PUBLIC_SUPABASE_FUNCTION_URL). Never commit .env to git.yourbrand.com, www.yourbrand.com, *.yourbrand.com for subdomains). Both checkout-otp and checkout-email-otp reject requests from unlisted origins.enableEmailAuth={true} with merchantId and supabaseFunctionBaseUrl. Auth is opt-in and web-only today (React Native widget does not include the email OTP steps).brandColor, logoUrl; per-merchant email template columns (email_from_name, email_logo_url, email_subject_template) in the portal.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-otpEdge 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.
| 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 |
When publishing a new version to npm (maintainers only):
@quantaroute org account (publish + modify).npm run type-check && npm run build:lib && npm pack --dry-run — confirm tarball contents..env, .pem, or supabase/ paths in the pack list.npm publish --provenance --access public.1.4.5.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
See CHANGELOG.md for version history. Current release: v1.4.5.
Made with ❤️ in India · Powered by QuantaRoute
FAQs
Embeddable DigiPin-powered smart address checkout widget — React (web), React Native & Expo (iOS/Android)
The npm package @quantaroute/checkout receives a total of 35 weekly downloads. As such, @quantaroute/checkout popularity was classified as not popular.
We found that @quantaroute/checkout demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Research
/Security News
Three compromised Rust crates pulled in a malicious dependency that downloaded and executed cross-platform malware during Cargo builds.

Research
/Security News
Socket uncovered 77 linked Firefox extensions, including 40 that steal wallet secrets or credentials and 37 deceptive sports-score shells.

Security News
NIST disclosed an unreleased AI tool called V-etalon and opened a broad inquiry into NVD modernization after years of automation plans produced no public enrichment system.