Documentation
Everything you need to integrate @localpay/verification-engine into your project.
Installation
Install via npm, yarn, or pnpm:
npm install @localpay/verification-engineRequires 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.
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 Key | Bank | Methods |
|---|---|---|
| CBE | Commercial Bank of Ethiopia | LINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT |
| TELEBIRR | Telebirr | LINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT |
| ABYSSINIA | Bank of Abyssinia | LINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT |
| EBIRR | E-Birr | LINK, SMS, TRANSACTION_REF, OCR, SCREENSHOT |
Verification Methods
Pass the receipt URL directly. The parser fetches and parses the receipt page.
Pass raw SMS text. The parser extracts the receipt link from the message and fetches it.
Pass only the transaction or reference number. The parser builds the URL and fetches the receipt.
Pass an image path or Buffer. tesseract.js runs OCR internally and the parser extracts fields from the text output.
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:
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:
Allowed domains per bank
| Bank | Allowed domains |
|---|---|
| CBE | apps.cbe.com.et, mbreciept.cbe.com.et |
| TELEBIRR | transactioninfo.ethiotelecom.et |
| EBIRR | my.ebirr.com |
| ABYSSINIA | cs.bankofabyssinia.com |
No config needed for built-in banks — validation is automatic:
// 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:
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.
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.00OCR 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.
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.
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.
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 });
}
}