@quantaroute/checkout
v1.3.0
Published
Embeddable DigiPin-powered smart address checkout widget — React (web), React Native & Expo (iOS/Android)
Downloads
149
Maintainers
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.
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 vector basemap — choose from Carto Positron or OpenFreeMap styles (no API key for either)
- 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.tsxfiles on mobile; Vite/Webpack use the web.tsxfiles.
Quick start
0 · Get an API key
- Sign up at developers.quantaroute.com
- 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-glImport 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-sdkuses native modules.
1 · Install
npm install @quantaroute/checkout expo-osm-sdk expo-location2 · 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:android4 · 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:androidProps
| 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 |
| mapStyle | BuiltInMapStyle \| string | 'carto-positron' | all |
| className | string | – | web only |
| style | CSSProperties \| StyleProp<ViewStyle> | – | all |
| indiaBoundaryUrl | string | – | web only |
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, ..."
}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;
}Choose a basemap style
Pass a built-in preset name or any MapLibre-compatible style URL:
// Built-in presets (no API key required for any of them)
<CheckoutWidget apiKey="..." mapStyle="carto-positron" onComplete={...} /> {/* default */}
<CheckoutWidget apiKey="..." mapStyle="openfreemap-liberty" onComplete={...} /> {/* colorful OSM */}
<CheckoutWidget apiKey="..." mapStyle="openfreemap-positron" onComplete={...} /> {/* clean light */}
<CheckoutWidget apiKey="..." mapStyle="openfreemap-bright" onComplete={...} /> {/* vibrant */}
// Custom style URL (your own MapLibre server)
<CheckoutWidget apiKey="..." mapStyle="https://my-tiles.example.com/style.json" onComplete={...} />Attribution is applied automatically for each built-in preset.
OpenFreeMap is a free, open-source tile hosting service. If you use the
openfreemap-*presets in production, consider sponsoring the project to keep the public instance running.
India boundary overlay (web only)
<CheckoutWidget
apiKey="..."
indiaBoundaryUrl="/geojson/india.geojson" {/* hosted in your public folder */}
onComplete={...}
/>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)
│ │ ├── mapStyles.ts ← basemap preset registry + resolver (shared)
│ │ └── 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
| Preset | Provider | Attribution | API key |
|---|---|---|---|
| carto-positron (default) | Carto | © OpenStreetMap contributors © CARTO | None |
| openfreemap-liberty | OpenFreeMap | © OpenStreetMap contributors © OpenMapTiles · OpenFreeMap | None |
| openfreemap-positron | OpenFreeMap | © OpenStreetMap contributors © OpenMapTiles · OpenFreeMap | None |
| openfreemap-bright | OpenFreeMap | © OpenStreetMap contributors © OpenMapTiles · OpenFreeMap | None |
Attribution is injected automatically for all built-in presets. When using a custom style URL you are responsible for correct attribution per your tile provider's terms.
OpenFreeMap is fully open-source (MIT). If you use openfreemap-* presets in production, please consider sponsoring their public instance.
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
# 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 + demoNative demo:
cd demo-native
npm install
npx expo run:ios # requires Xcode
npx expo run:android # requires Android StudioChangelog
v1.3.0
mapStyleprop — choose a basemap preset or pass any MapLibre-compatible style URL'carto-positron'(default, unchanged — fully backward-compatible)'openfreemap-liberty'— colorful OSM-flavored style via OpenFreeMap'openfreemap-positron'— clean light style via OpenFreeMap'openfreemap-bright'— vibrant high-contrast style via OpenFreeMap- Custom URL string — pass any MapLibre style JSON endpoint
- Attribution text is resolved automatically per provider (no manual setup needed)
- New
src/core/mapStyles.ts— shared preset registry used by both web and native components
v1.2.0
- iOS & Android support via
expo-osm-sdk(MapLibre GL Native) MapPinSelector.native.tsx— draggable OSMView pinAddressForm.native.tsx—TextInput/ScrollView/Modallocality pickerCheckoutWidget.native.tsx—SafeAreaViewlayout with identical flow logicuseGeolocation.native.ts— GPS viaexpo-locationcheckout.native.ts— completeStyleSheetdesign system (light + dark)expo-plugin.js— one-lineapp.jsonsetup for iOS/Androidbabel.config.js— Metro transpilation configmapHeightprop now acceptsnumber(native) orstring(web, e.g.'380px')- All native peer dependencies marked optional (zero impact on web bundles)
v1.1.x
- DigiPin badge overlay on map
- Locality alternatives dropdown
- OSM address pre-fill (building name, road, suburb)
- India boundary GeoJSON overlay
v1.0.0
- Initial release: MapLibre GL + Carto Positron
- Offline DigiPin generation
- QuantaRoute API integration
- Mobile-first responsive design
- Dark mode, reduced-motion, keyboard accessible
Made with ❤️ in India · Powered by QuantaRoute
