Package Exports
- @quantaroute/checkout
- @quantaroute/checkout/dist/style.css
Readme
@quantaroute/checkout
DigiPin-powered smart address checkout widget for Indian e-commerce. Two-step: Map pin → Auto-filled form. Works in any React/Next.js/Nuxt/Vite app.
What it does
Replaces your broken Indian address form with a two-step Uber-style flow:
Step 1 – Map Pin Step 2 – Auto-fill + Details
┌──────────────────────────┐ ┌──────────────────────────┐
│ [DigiPin: 39J-438-TJC7] │ │ 📍 Auto-detected │
│ │ │ State: Delhi │
│ 🗺 Carto Positron 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 (~4m×4m precision)
- No Google Maps. Free Carto Positron vector basemap (MapLibre GL JS)
- 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)
- Dark mode, reduced-motion, keyboard navigation, ARIA labels
- TypeScript, zero runtime deps beyond MapLibre + React
Quick start
0 · Get an API key
- Sign up at developers.quantaroute.com
- Create a project → copy your API key
- Store it as an environment variable in your app:
| Framework | Variable name convention |
|---|---|
| Next.js | NEXT_PUBLIC_QUANTAROUTE_KEY in .env.local |
| Vite | VITE_QR_API_KEY in .env |
| Nuxt | NUXT_PUBLIC_QR_API_KEY in .env |
| Node/Express backend | QUANTAROUTE_KEY (server-side only) |
Never hard-code or commit API keys to git.
1 · Install
npm install @quantaroute/checkout maplibre-gl
# or
yarn add @quantaroute/checkout maplibre-gl2 · Import CSS
// In your app's entry file (main.tsx / _app.tsx / nuxt.config.ts, etc.)
import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/dist/style.css';3 · Drop it in
import { CheckoutWidget } from '@quantaroute/checkout';
export default function CheckoutPage() {
return (
<CheckoutWidget
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!} {/* never hard-code keys */}
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
}}
/>
);
}Get your API key (free tier: 25,000 requests/month) → developers.quantaroute.com
Store it as an environment variable — never commit API keys to source control.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
apiKey |
string |
required | QuantaRoute API key |
onComplete |
(addr: CompleteAddress) => void |
required | Fired after user saves address |
apiBaseUrl |
string |
https://api.quantaroute.com |
Custom API URL (self-hosted) |
onError |
(err: Error) => void |
– | Optional error handler |
defaultLat |
number |
India center | Pre-center the map here |
defaultLng |
number |
India center | Pre-center the map here |
theme |
'light' | 'dark' |
'light' |
Color theme |
mapHeight |
string |
'380px' |
CSS height of the map area |
title |
string |
'Add Delivery Address' |
Widget header text |
className |
string |
– | Extra class on root element |
style |
CSSProperties |
– | Inline styles on root element |
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" (pre-filled from Nominatim OSM)
streetName: string; // "MG Road, Action Area I" (pre-filled from road + suburb)
formattedAddress: string; // "4B, 3rd Floor, Sunshine Apartments, MG Road, ..."
}Framework integration guides
Next.js (App Router)
// app/checkout/page.tsx
'use client'; // ← required (MapLibre is browser-only)
import dynamic from 'next/dynamic';
const CheckoutWidget = dynamic(
() => import('@quantaroute/checkout').then((m) => m.CheckoutWidget),
{ ssr: false } // ← required (no SSR for map)
);
export default function CheckoutPage() {
return (
<main className="max-w-lg mx-auto p-4">
<CheckoutWidget
apiKey={process.env.NEXT_PUBLIC_QUANTAROUTE_KEY!} {/* set in .env.local */}
onComplete={(addr) => console.log(addr)}
/>
</main>
);
}Add CSS imports to app/layout.tsx:
import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/dist/style.css';Next.js (Pages Router)
// pages/checkout.tsx
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!} {/* .env.local */}
onComplete={(a) => console.log(a)}
/>
);
}Nuxt 3
// nuxt.config.ts
export default defineNuxtConfig({
css: [
'maplibre-gl/dist/maplibre-gl.css',
'@quantaroute/checkout/dist/style.css',
],
});<!-- components/AddressWidget.client.vue -->
<!-- The .client suffix ensures this only renders on the client -->
<script setup lang="ts">
import { CheckoutWidget } from '@quantaroute/checkout';
function handleComplete(address) {
console.log(address.digipin, address.formattedAddress);
}
</script>
<template>
<!-- API key via runtimeConfig → useRuntimeConfig().public.qrApiKey -->
<CheckoutWidget
:api-key="runtimeConfig.public.qrApiKey"
map-height="360px"
@complete="handleComplete"
/>
</template>Vite + React
// src/App.tsx
import 'maplibre-gl/dist/maplibre-gl.css';
import '@quantaroute/checkout/dist/style.css';
import { CheckoutWidget } from '@quantaroute/checkout';
function App() {
return (
<div style={{ padding: '20px' }}>
<CheckoutWidget
apiKey={import.meta.env.VITE_QR_API_KEY}
onComplete={(addr) => console.log('Saved:', addr)}
/>
</div>
);
}Vanilla JS (UMD / Script tag)
<!DOCTYPE html>
<html>
<head>
<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" />
</head>
<body>
<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/@quantaroute/checkout/dist/lib/quantaroute-checkout.umd.js"></script>
<script>
const { createRoot } = ReactDOM;
const { CheckoutWidget } = QuantaRouteCheckout;
// Replace window.__QR_API_KEY__ with your key from developers.quantaroute.com
// In production, inject this via server-side template or env variable.
createRoot(document.getElementById('checkout-root')).render(
React.createElement(CheckoutWidget, {
apiKey: window.__QR_API_KEY__ || '',
onComplete: (addr) => console.log('Done:', addr),
})
);
</script>
</body>
</html>React Native (coming soon)
A React Native version is planned that will use react-native-maps + the same offline DigiPin algorithm and QuantaRoute API. Track progress in issues.
Advanced usage
Use sub-components individually
import { MapPinSelector, AddressForm, getDigiPin, isWithinIndia } from '@quantaroute/checkout';
// Offline DigiPin (no API call needed)
const dp = getDigiPin(28.6139, 77.2090); // "39J-438-TJC7"
const valid = isWithinIndia(28.6139, 77.2090); // true
// Custom two-step flow
function MyCheckout() {
const [loc, setLoc] = useState(null);
return loc == null
? <MapPinSelector onLocationConfirm={(lat, lng, dp) => setLoc({ lat, lng, dp })} />
: <AddressForm {...loc} digipin={loc.dp} apiKey="..." onAddressComplete={...} onBack={...} />;
}Custom theme with CSS variables
/* Override the design tokens in your global CSS */
.qr-checkout {
--qr-primary: #6366f1; /* indigo */
--qr-primary-dark: #4f46e5;
--qr-radius: 8px;
--qr-font: 'Poppins', sans-serif;
}Map tile license
This widget uses Carto Positron vector tiles via MapLibre GL JS.
- Free for commercial use (attribution required)
- Attribution:
© OpenStreetMap contributors © CARTO - Does not use Google Maps, Mapbox, or OSM raster tiles
- No API key required for Carto basemaps
DigiPin license
The offline DigiPin algorithm is the official India Post implementation:
- Source: github.com/INDIAPOST-gov/digipin
- Developed by: India Post, IIT Hyderabad, ISRO NRSC
- License: Apache 2.0
Development
git clone https://github.com/quantaroute/checkout.git
cd quantaroute-checkout
npm install
npm run dev # Dev server at http://localhost:5173
npm run type-check # TypeScript check
npm run build # Build demo
npm run build:lib # Build library (dist/lib/)Changelog
v1.0.0
- Initial release
- MapLibre GL + Carto Positron (free, no API key)
- Offline DigiPin generation (real-time on pin drag)
- QuantaRoute location lookup API integration
- Mobile-first responsive design
- Dark mode, reduced-motion, keyboard accessible
- React / Next.js / Nuxt / Vite / Vanilla JS support
Made with ❤️ in India · Powered by QuantaRoute