Documentation

Everything you need to integrate @localpay/verification-engine into your project.

Installation

Install via npm, yarn, or pnpm:

npm install @localpay/verification-engine

Requires Node.js ≥ 20. The package ships its own TypeScript types — no @types package needed.

Quick Start

Create an engine instance and call verify() with a bank key, expected amount, method, and raw proof.

verify.ts
import { VerificationEngine } from "@localpay/verification-engine"; const engine = new VerificationEngine(); const result = await engine.verify({ bank: "CBE", amount: 500, verMethod: "LINK", rawProof: "https://apps.cbe.com.et:100/?id=FT26093JCD3218872366", }); if (result.status === "SUCCESS") { console.log(result.receipt.bank); // "CBE" console.log(result.receipt.receipt.transactionNumber); // "FT26093JCD32..." console.log(result.receipt.receipt.amount); // "500" console.log(result.receipt.receipt.receiverAccount); // account number console.log(result.receipt.receipt.receiverName); // account holder name console.log(result.receipt.receipt.date); // raw date string } else { console.log(result.reason); // human-readable failure reason }

Supported Banks

All parsers support all five verification methods.

Parser KeyBankMethods
CBECommercial Bank of EthiopiaLINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT
TELEBIRRTelebirrLINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT
ABYSSINIABank of AbyssiniaLINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT
EBIRRE-BirrLINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT

Verification Methods

LINK

Pass the receipt URL directly. The parser fetches and parses the receipt page.

SMS

Pass raw SMS text. The parser extracts the receipt link from the message and fetches it.

TRANSACTION_REF

Pass only the transaction or reference number. The parser builds the URL and fetches the receipt.

OCR

Pass an image path or Buffer. tesseract.js runs OCR internally and the parser extracts fields from the text output.

SCREENSHOT

Alias of OCR. Use either key — they behave identically.

Proxy Support

Some bank receipt endpoints are geo-restricted (e.g. Telebirr blocks foreign IPs). Pass a proxyResolver when constructing the engine:

proxy.ts
import { VerificationEngine, ProxyType } from "@localpay/verification-engine"; const engine = new VerificationEngine({ proxyResolver: { resolve: async (countryCode) => ({ enabled: true, url: "http://user:pass@proxy.example.com:8080", type: ProxyType.HTTP_CONNECT, // or ProxyType.SOCKS5 }), }, }); const result = await engine.verify({ bank: "CBE", amount: 500, verMethod: "LINK", rawProof: "https://apps.cbe.com.et:100/?id=FT26093JCD3218872366", });

ProxyType supports HTTP_CONNECT and SOCKS5. There is no HTTP or HTTPS type.

URL Validation

Every receipt URL is validated before the parser makes any network request. Three checks run automatically:

01Protocol must be HTTPS
02Domain must be on the bank's allowed list
03URL structure must pass the bank-specific validate() function

Allowed domains per bank

BankAllowed domains
CBEapps.cbe.com.et, mbreciept.cbe.com.et
TELEBIRRtransactioninfo.ethiotelecom.et
EBIRRmy.ebirr.com
ABYSSINIAcs.bankofabyssinia.com

No config needed for built-in banks — validation is automatic:

validation-builtin.ts
// URL validation is automatic — no config needed for built-in banks. // The engine validates every URL before fetching: // 1. Protocol must be HTTPS // 2. Domain must be on the bank's allowed list // 3. URL structure must pass the bank-specific validate() function // Example: this URL would be rejected before any network call await engine.verify({ bank: "CBE", rawProof: "https://evil.com/fake-receipt?id=FT123", // ← FAIL: domain not allowed ... });

Extend or override validators for custom banks via the urlValidators option:

custom-validator.ts
import { VerificationEngine, URL_VALIDATION_REGISTRY, UrlValidationConfig, } from "@localpay/verification-engine"; // Define a config for your custom bank const MY_BANK_CONFIG: UrlValidationConfig = { domains: ["receipts.my-bank.et"], validate(parsed: URL) { // Enforce that the URL has a ref query param if (!parsed.searchParams.get("ref")) { throw new Error("Missing receipt reference."); } }, }; const engine = new VerificationEngine({ urlValidators: { ...URL_VALIDATION_REGISTRY, // keep all built-in bank validators MY_BANK: MY_BANK_CONFIG, }, });

Amount Tolerance

The engine compares payload.amount with the parsed receipt amount. Currency symbols and commas are stripped automatically before comparison. Use amountTolerance to allow small rounding differences — the default is 0.01.

tolerance.ts
await engine.verify({ bank: "CBE", amount: 500, amountTolerance: 0.05, // allow up to 0.05 difference — default is 0.01 verMethod: "LINK", rawProof: "https://apps.cbe.com.et:100/?id=FT26093JCD3218872366", }); // Currency symbols and commas are stripped automatically: // "ETB 9,540.00" → compared as 9540.00 // "500.00 Br" → compared as 500.00

OCR Support

Pass verMethod: "OCR" (or its alias SCREENSHOT) with a file path or Buffer as rawProof. tesseract.js runs entirely in-process — no external OCR service required. Supply a custom ocrReader for higher accuracy.

ocr.ts
import { VerificationEngine } from "@localpay/verification-engine"; import * as fs from "fs"; const engine = new VerificationEngine(); // From file path const result = await engine.verify({ bank: "TELEBIRR", amount: 250, verMethod: "OCR", rawProof: "/tmp/receipt-screenshot.png", }); // From Buffer const imageBuffer = fs.readFileSync("/tmp/receipt.png"); const result2 = await engine.verify({ bank: "TELEBIRR", amount: 250, verMethod: "OCR", rawProof: imageBuffer, }); // Custom OCR reader (e.g. Google Cloud Vision for higher accuracy) const engineWithCustomOcr = new VerificationEngine({ ocrReader: async (input) => { const text = await myVisionApi.recognize(input); return text; }, });

Adding a New Bank Parser

Implement the ParserAndExtractor interface, then pass it to VerificationEngine through the parsers option.

Every new parser must include fixture tests for both extract() and receiptParser(). Anonymize all sample receipt data before committing.

mybank-parser.ts
import { PARSER_REGISTRY, ParserAndExtractor, ParserFetchContext, RawReceipt, VerificationEngine, } from "@localpay/verification-engine"; export class MyBankParser implements ParserAndExtractor { extract(text: string, accountNumber?: string): { link: string } { return { link: text }; } transactionRef(ref: string, accountNumber?: string): { link: string } { return { link: `https://mybank.et/receipt?ref=${ref}` }; } async fetch( link: string, context?: ParserFetchContext, ): Promise<{ page: any }> { const response = await context?.fetcher.fetch(link, context.countryCode); return { page: response?.data }; } async receiptParser( page: any, ): Promise<{ bank: string; receipt: RawReceipt }> { return { bank: "MY_BANK", receipt: { transactionNumber: "...", date: "...", amount: "...", receiverAccount: "...", receiverName: "...", }, }; } } const engine = new VerificationEngine({ parsers: { ...PARSER_REGISTRY, MY_BANK: new MyBankParser(), }, });

NestJS Integration

The engine is framework-independent. Wrap it in a NestJS provider manually — do not add NestJS as a peer dependency to the core package.

verification.module.ts
import { Module } from "@nestjs/common"; import { VerificationEngine } from "@localpay/verification-engine"; @Module({ providers: [ { provide: "VERIFICATION_ENGINE", useFactory: () => new VerificationEngine(), }, ], exports: ["VERIFICATION_ENGINE"], }) export class VerificationModule {} // In a service import { Inject, Injectable } from "@nestjs/common"; @Injectable() export class PaymentService { constructor( @Inject("VERIFICATION_ENGINE") private readonly engine: VerificationEngine, ) {} async verifyReceipt(bank: string, amount: number, rawProof: string) { return this.engine.verify({ bank, amount, verMethod: "LINK", rawProof }); } }

Publishing & Versioning

01All PRs must target the cont branch, not prod. This is the active contribution branch.
02Only the package owner merges cont → prod.
03Breaking changes to the public API must be discussed before implementation.
04Bump semver appropriately: patch for bug fixes, minor for new parsers or methods, major for breaking API changes.
05Do not commit secrets, real customer receipts, or private account numbers.
06Run npm run lint && npm test before opening a PR.