@vouch.finance/sdk
v0.3.0
Published
TypeScript SDK for the Vouch Purpose-Bound Voucher platform — UPI QR redemption, payments, open-network merchant support
Maintainers
Readme
@vouch.finance/sdk
TypeScript SDK for the Vouch Purpose-Bound Voucher platform. Supports UPI QR voucher redemption, payment intents, voucher balance checks, and verification.
Two entry points:
@vouch.finance/sdk— core client, UPI parsing, framework-agnostic scanner@vouch.finance/sdk/react— React QR scanner component
Install
npm install @vouch.finance/sdkIf using the QR scanner, also install the optional peer dependencies:
# React apps using <QRScanner />
npm install react react-dom html5-qrcode
# Non-React apps using UPIScanner
npm install html5-qrcodeCDN (no bundler):
<script src="https://cdn.jsdelivr.net/npm/@vouch.finance/sdk/dist/index.global.js"></script>
<script>
const vouch = new Vouch.Vouch({
apiKey: "your-key",
baseUrl: "https://api.your-domain.com/api/v1",
});
</script>Configuration
Always set baseUrl explicitly in production. The SDK only falls back to a local dev default when omitted.
| Source | Used by | Example |
| --- | --- | --- |
| VouchConfig.baseUrl | All runtimes | https://sandbox.lk.vouch.finance/api/v1 |
| VOUCH_BASE_URL env | Node.js / tests | http://localhost:9393/api/v1 |
| DEFAULT_DEV_BASE_URL | Dev fallback only | http://localhost:9393/api/v1 |
import { Vouch, DEFAULT_DEV_BASE_URL } from "@vouch.finance/sdk";
const vouch = new Vouch({
apiKey: process.env.VOUCH_API_KEY!,
baseUrl: process.env.VOUCH_BASE_URL ?? DEFAULT_DEV_BASE_URL,
showVoucherOnlyWithBalance: true, // optional: gate UI on spendable balance
});Browser apps (Vite / React)
Avoid CORS and hardcoded hosts by proxying /api to your backend and using a relative SDK base URL:
# .env.local
VITE_BACKEND_URL=http://localhost:9393
VITE_VOUCH_BASE_URL=/api/v1
VITE_VOUCH_API_KEY=sk_test_...See examples/partner-test-ui/ for a full working setup.
E2E tests
cd sdk
VOUCH_API_KEY=sk_test_... \
VOUCH_BASE_URL=http://localhost:9393/api/v1 \
VOUCH_PROGRAM_ID=cmpo31oud0000vzmopcrfb9kx \
VOUCH_BENEFICIARY_PHONE=9953531809 \
npm run test:e2eQuick Start
import { Vouch } from "@vouch.finance/sdk";
const vouch = new Vouch({
apiKey: "sk_test_...",
baseUrl: "https://api.your-domain.com/api/v1",
});Voucher balance gate (partner apps)
Before showing voucher UI, check whether the beneficiary has spendable balance:
const balance = await vouch.vouchers.getBalance({
beneficiaryPhone: "9953531809",
programId: "cmpo31oud0000vzmopcrfb9kx",
});
// balance.hasVoucher, balance.hasBalance, balance.remainingBalanceMinor, balance.tokenId
const showUi = await vouch.vouchers.shouldShowUI({
beneficiaryPhone: "9953531809",
programId: "cmpo31oud0000vzmopcrfb9kx",
});
// true only when showVoucherOnlyWithBalance is set and hasBalance is trueUPI Voucher Payment Flow
The primary integration for partners building beneficiary-facing apps. The partner identifies the beneficiary by phone number, scans a merchant UPI QR, and completes payment using the voucher.
Step 1 — Scan merchant UPI QR
import { parseUPIScheme } from "@vouch.finance/sdk";
const upi = parseUPIScheme("upi://pay?pa=pharmacy@upi&pn=City+Pharmacy&am=250.00");
if (!upi) throw new Error("Not a valid UPI QR");
// upi = {
// type: "upi",
// upiId: "pharmacy@upi",
// merchantName: "City Pharmacy",
// amount: "250.00",
// mcc: "5912",
// raw: "upi://pay?..."
// }Or use the scanner components (see QR Scanner section below).
Step 2 — Create intent and get quote
const intent = await vouch.payments.initiateUPI({
upiData: upi,
beneficiaryPhone: "+919876543210",
programId: "cmpo31oud0000vzmopcrfb9kx",
});Step 3 — Show payment review
Display the quote to the beneficiary. Proceed only if status === "QUOTED" and eligibleAmount > 0.
Step 4 — Authorize payment
const auth = await vouch.payments.authorize({ intentId: intent.intentId });Step 5 — Poll for settlement
const status = await vouch.payments.getIntent(intent.intentId);
// status.status — "AUTHORIZED" | "SETTLED" | "FAILED"Open-Network UPI (Unregistered Merchants)
Payments work to any UPI merchant — registered or not. Unregistered merchants return merchantResolution: "open_network" and settle via fiat UPI (settlementRail: "fiat").
const intent = await vouch.payments.initiateUPI({
upiData: upi,
beneficiaryPhone: "+919876543210",
programId: "cmpo31oud0000vzmopcrfb9kx",
});
if (intent.merchantResolution === "open_network") {
console.log("Paying via UPI to:", intent.upiId);
}External Settlement (partner-managed money movement)
Use this when your platform moves the money itself (e.g. you run your own UPI payout
rail) and you only want Vouch to track the voucher ledger. Pass externalSettlement: true
on initiateUPI. For the open-network UPI (fiat) settlement path, Vouch will then:
- record the voucher ledger entry and mark the payment
SETTLED, but - not call the UPI rail engine to perform or confirm the payout.
Optionally pass your own payoutReference to store your transfer ID against the payment.
Scope: this only affects the fiat (open-network) settlement path. On-chain settlement for registered merchants is unaffected.
const intent = await vouch.payments.initiateUPI({
upiData: upi,
beneficiaryPhone: "+919876543210",
programId: "cmpo31oud0000vzmopcrfb9kx",
externalSettlement: true, // skip Vouch's UPI payout; you settle the money
payoutReference: "PARTNER-TXN-001", // optional: your own transfer reference
});
// intent.externalSettlement === true
// intent.settlementRail === "fiat" for open-network merchants
await vouch.payments.authorize({ intentId: intent.intentId });
// Poll until settled — completes without invoking the UPI rail engine.
const status = await vouch.payments.getIntent(intent.intentId);
// status.status === "SETTLED"QR Scanner
React
import { QRScanner } from "@vouch.finance/sdk/react";
<QRScanner
onScan={(upi) => console.log(upi.upiId, upi.amount)}
onError={(err) => console.error(err)}
onClose={() => setOpen(false)}
width={320}
height={320}
/>Vanilla JS (any framework)
import { UPIScanner } from "@vouch.finance/sdk";
const scanner = new UPIScanner({
container: "#scanner",
onScan: (upi) => console.log(upi.upiId),
onError: (err) => console.error(err),
});
await scanner.start();API Reference
Client
new Vouch({
apiKey,
baseUrl?, // required in production
version?, // default "2026-04-06"
showVoucherOnlyWithBalance?, // default false
})All requests include x-api-key and Vouch-Version headers.
Programs
| Method | Return |
|--------|--------|
| vouch.programs.list() | { programs: Program[] } |
| vouch.programs.get(slug) | { program: Program } |
| vouch.programs.create(params) | { data: { program }, meta } |
| vouch.programs.stats(slug) | { data: { stats }, meta } |
| vouch.programs.enrol(slug, { phone, fields? }) | { data, meta } |
Payments
| Method | Return |
|--------|--------|
| vouch.payments.initiateUPI({ upiData, programId, beneficiaryPhone?, beneficiaryId?, tokenId?, externalSettlement?, payoutReference? }) | UPIIntentResponse |
| vouch.payments.createIntent({ merchantId, items, programId? }) | { data: PaymentIntent, meta } |
| vouch.payments.quote({ intentId, tokenId }) | { data, meta } |
| vouch.payments.authorize({ intentId }) | AuthorizeResponse |
| vouch.payments.getIntent(intentId) | IntentStatusResponse |
Vouchers & Merchants
| Method | Return |
|--------|--------|
| vouch.vouchers.verify(token, programId?) | { data: Voucher, meta } |
| vouch.vouchers.getBalance({ beneficiaryPhone, programId }) | VoucherBalanceStatus |
| vouch.vouchers.shouldShowUI({ beneficiaryPhone, programId }) | boolean |
| vouch.merchants.list(programId?) | { data: Merchant[], meta, pagination } |
Constants
| Export | Description |
|--------|-------------|
| DEFAULT_DEV_BASE_URL | http://localhost:9393/api/v1 — local dev fallback |
| DEFAULT_DEV_BACKEND_ORIGIN | http://localhost:9393 — for Vite proxy config |
| resolveBaseUrl(explicit?) | Config → VOUCH_BASE_URL → dev default |
Types
| Type | Description |
|------|-------------|
| VouchConfig | apiKey, baseUrl?, version?, showVoucherOnlyWithBalance? |
| VoucherBalanceStatus | Response from getBalance — hasVoucher, hasBalance, remainingBalanceMinor, tokenId, currency, etc. |
| UPIData | Parsed UPI QR (upiId, merchantName?, amount?, mcc?) |
| UPIIntentResponse | Response from initiateUPI — includes merchantResolution, settlementRail, externalSettlement |
| AuthorizeResponse | Response from authorize |
| IntentStatusResponse | Response from getIntent |
| VouchError | code, statusCode, requestId, message |
Error Handling
import { VouchError } from "@vouch.finance/sdk";
try {
await vouch.payments.authorize({ intentId: "bad" });
} catch (err) {
if (err instanceof VouchError) {
console.log(err.code, err.statusCode, err.requestId, err.message);
}
}Examples
| App | Port | Description |
| --- | --- | --- |
| examples/partner-test-ui/ | 5182 | Dvara flow: balance gate + UPI scan (recommended) |
| examples/react-app/ | 5180 | Minimal React + <QRScanner /> |
| examples/vanilla/ | — | CDN HTML, no build step |
cd sdk && npm run build
# Partner test UI
cd examples/partner-test-ui
cp .env.local.example .env.local
npm install && npm run dev
# React example
cd examples/react-app
cp .env.local.example .env.local
npm install && npm run devPublishing
cd sdk
npm install
npm run build
NPM_TOKEN=... ./scripts/publish.shBuild outputs: dist/index.js (CJS), dist/index.mjs (ESM), dist/index.global.js (IIFE/CDN), dist/react/ (React entry).
