checkbirr 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +10 -0
- package/README.md +85 -3
- package/dist/banks/cbe.d.ts +3 -0
- package/dist/banks/cbe.js +43 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/types/receipt.d.ts +9 -0
- package/dist/types/receipt.js +1 -0
- package/dist/types/transaction.d.ts +12 -0
- package/dist/types/transaction.js +1 -0
- package/dist/utils/banks.d.ts +5 -0
- package/dist/utils/banks.js +7 -0
- package/dist/utils/hasher.d.ts +1 -0
- package/dist/utils/hasher.js +7 -0
- package/dist/utils/identifyBank.d.ts +1 -0
- package/dist/utils/identifyBank.js +8 -0
- package/dist/utils/scan.d.ts +1 -0
- package/dist/utils/scan.js +20 -0
- package/dist/utils/validator.d.ts +21 -0
- package/dist/utils/validator.js +15 -0
- package/package.json +39 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2011-2026 The Bootstrap Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
package/README.md
CHANGED
|
@@ -1,3 +1,85 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# CheckBirr 💵
|
|
2
|
+
|
|
3
|
+
Verify payment screenshots instantly ⚡
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install checkbirr
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import checkbirr from "checkbirr";
|
|
17
|
+
|
|
18
|
+
async function main() {
|
|
19
|
+
try {
|
|
20
|
+
const output = await checkbirr({
|
|
21
|
+
src: "./screenshot.jpg",
|
|
22
|
+
amount: 5000,
|
|
23
|
+
receiverAccounts: [
|
|
24
|
+
{
|
|
25
|
+
bank: "CBE",
|
|
26
|
+
receiverName: "John Doe Smith",
|
|
27
|
+
receiverAccountNumber: "1000*****",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(output);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.log(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
main();
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Output
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
{
|
|
45
|
+
verified:true,
|
|
46
|
+
bank:"CBE",
|
|
47
|
+
senderName:"Rip Van Winkle",
|
|
48
|
+
senderAccountNumber:"1000*****",
|
|
49
|
+
receiverName:"John Doe Smith",
|
|
50
|
+
receiverAccountNumber:"1000*****",
|
|
51
|
+
date:"Nov 02, 2026, 4:36 PM",
|
|
52
|
+
referenceNumber:"FM43812X",
|
|
53
|
+
amount:5000,
|
|
54
|
+
checkBirrID:"asd8f87a0d9f8a0as89f8"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## How it works
|
|
59
|
+
|
|
60
|
+
CheckBirr scans the QrCode present on the screenshot and navigates to the official website of the bank to extract transaction details.
|
|
61
|
+
After the details are extracted it simply compares the values provided by the developer with the ones retrieved from the website.
|
|
62
|
+
Finally, deming the transaction as verified or not depending on the similarity of the two values.
|
|
63
|
+
|
|
64
|
+
## Why use CheckBirr
|
|
65
|
+
|
|
66
|
+
CheckBirr does **NOT** simply extract the transaction details from the image using OCR scanning, But rather
|
|
67
|
+
it retrieves them directly from the official site of the bank using web scrapping.
|
|
68
|
+
This makes it very reliable and impossible to trick with fake or edited screenshots.
|
|
69
|
+
|
|
70
|
+
## Duplicate Receipts
|
|
71
|
+
|
|
72
|
+
This library does **NOT** detect duplicate transactions nor does it keep a record of previous transactions.
|
|
73
|
+
It is up to the developer to store a history of **checkBirrID**s (which are unique IDs given to each transaction by the library) and compare new receipts in order to detect duplicates.
|
|
74
|
+
|
|
75
|
+
## Supported Banks
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
## Disclaimer
|
|
80
|
+
|
|
81
|
+
This is **NOT** an official library published by any of the banks supported.
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
This project is licensed under the **MIT License**.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import puppeteer from "puppeteer";
|
|
2
|
+
import hash from "../utils/hasher.js";
|
|
3
|
+
export default async function checkCBE(url, receipt) {
|
|
4
|
+
const browser = await puppeteer.launch({ headless: true });
|
|
5
|
+
const page = await browser.newPage();
|
|
6
|
+
await page.goto(url, { waitUntil: "networkidle2" });
|
|
7
|
+
await page.waitForSelector(".md\\:text-lg.text-sm.font-semibold.py-1.md\\:py-2.px-4.text-center.border-b.m-0", { visible: true, timeout: 10000 });
|
|
8
|
+
const selector = await page.$$eval(`.text-black.text-right.md\\:text-sm.md\\:col-span-1`, (elements) => elements.map((el) => el.textContent?.trim() ?? ""));
|
|
9
|
+
const bank = "CBE";
|
|
10
|
+
const senderName = selector[0];
|
|
11
|
+
const senderAccountNumber = selector[1];
|
|
12
|
+
const receiverName = selector[2];
|
|
13
|
+
const receiverAccountNumber = selector[3];
|
|
14
|
+
const date = selector[5];
|
|
15
|
+
const referenceNumber = selector[6];
|
|
16
|
+
const amount = parseFloat(selector[8]);
|
|
17
|
+
await browser.close();
|
|
18
|
+
const checkBirrID = hash(bank, referenceNumber);
|
|
19
|
+
let verified = false;
|
|
20
|
+
for (let i = 0; i < receipt.receiverAccounts.length; i++) {
|
|
21
|
+
if (receipt.receiverAccounts[i].bank === "CBE") {
|
|
22
|
+
if (receipt.amount === amount &&
|
|
23
|
+
receipt.receiverAccounts[i].receiverAccountNumber.slice(-4) ===
|
|
24
|
+
receiverAccountNumber.slice(-4) &&
|
|
25
|
+
receipt.receiverAccounts[i].receiverName === receiverName) {
|
|
26
|
+
verified = true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const transaction = {
|
|
31
|
+
verified,
|
|
32
|
+
bank,
|
|
33
|
+
senderName,
|
|
34
|
+
senderAccountNumber,
|
|
35
|
+
receiverName,
|
|
36
|
+
receiverAccountNumber,
|
|
37
|
+
date,
|
|
38
|
+
referenceNumber,
|
|
39
|
+
amount,
|
|
40
|
+
checkBirrID,
|
|
41
|
+
};
|
|
42
|
+
return transaction;
|
|
43
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import scan from "./utils/scan.js";
|
|
2
|
+
import identifyBank from "./utils/identifyBank.js";
|
|
3
|
+
import validator from "./utils/validator.js";
|
|
4
|
+
import checkCBE from "./banks/cbe.js";
|
|
5
|
+
export default async function checkbirr(receipt) {
|
|
6
|
+
const validationResult = validator(receipt);
|
|
7
|
+
if (!validationResult.success) {
|
|
8
|
+
const messages = validationResult.error.issues.map((issue) => {
|
|
9
|
+
const path = issue.path.join(".");
|
|
10
|
+
return path ? `"${path}": ${issue.message}` : issue.message;
|
|
11
|
+
});
|
|
12
|
+
throw new Error(`checkbirr: invalid receipt:\n${messages.join("\n")}`);
|
|
13
|
+
}
|
|
14
|
+
const validReceipt = validationResult.data;
|
|
15
|
+
const url = await scan(validReceipt.src);
|
|
16
|
+
const bank = identifyBank(url);
|
|
17
|
+
switch (bank) {
|
|
18
|
+
case "CBE": {
|
|
19
|
+
const output = await checkCBE(url, validReceipt);
|
|
20
|
+
return output;
|
|
21
|
+
}
|
|
22
|
+
default:
|
|
23
|
+
throw new Error(`Unsupported bank: ${bank}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default interface Transaction {
|
|
2
|
+
verified: boolean;
|
|
3
|
+
referenceNumber: string;
|
|
4
|
+
bank: string;
|
|
5
|
+
senderAccountNumber: string;
|
|
6
|
+
senderName: string;
|
|
7
|
+
receiverAccountNumber: string;
|
|
8
|
+
receiverName: string;
|
|
9
|
+
amount: number;
|
|
10
|
+
date: string;
|
|
11
|
+
checkBirrID: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function hash(bank: string, referenceNumber: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function identifyBank(url: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function scan(imagePath: string): Promise<string>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import jsQR from "jsqr";
|
|
2
|
+
import { Jimp } from "jimp";
|
|
3
|
+
export default async function scan(imagePath) {
|
|
4
|
+
try {
|
|
5
|
+
const image = await Jimp.read(imagePath);
|
|
6
|
+
const { data, width, height } = image.bitmap;
|
|
7
|
+
const code = jsQR.default(new Uint8ClampedArray(data), width, height);
|
|
8
|
+
if (code) {
|
|
9
|
+
return code.data;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
console.log("No QR code found");
|
|
13
|
+
return `Error: QrCode Not Found`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.log(error);
|
|
18
|
+
return `Error: ${error}`;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
declare const receiptSchema: z.ZodObject<{
|
|
3
|
+
src: z.ZodString;
|
|
4
|
+
receiverAccounts: z.ZodArray<z.ZodObject<{
|
|
5
|
+
bank: z.ZodString;
|
|
6
|
+
receiverName: z.ZodString;
|
|
7
|
+
receiverAccountNumber: z.ZodString;
|
|
8
|
+
}, z.core.$strip>>;
|
|
9
|
+
amount: z.ZodNumber;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type Receipt = z.infer<typeof receiptSchema>;
|
|
12
|
+
export default function validateReceipt(receipt: unknown): z.ZodSafeParseResult<{
|
|
13
|
+
src: string;
|
|
14
|
+
receiverAccounts: {
|
|
15
|
+
bank: string;
|
|
16
|
+
receiverName: string;
|
|
17
|
+
receiverAccountNumber: string;
|
|
18
|
+
}[];
|
|
19
|
+
amount: number;
|
|
20
|
+
}>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
const receiptSchema = z.object({
|
|
3
|
+
src: z.string(),
|
|
4
|
+
receiverAccounts: z
|
|
5
|
+
.array(z.object({
|
|
6
|
+
bank: z.string(),
|
|
7
|
+
receiverName: z.string(),
|
|
8
|
+
receiverAccountNumber: z.string(),
|
|
9
|
+
}))
|
|
10
|
+
.min(1, "receiverAccounts must have at least one entry"),
|
|
11
|
+
amount: z.number(),
|
|
12
|
+
});
|
|
13
|
+
export default function validateReceipt(receipt) {
|
|
14
|
+
return receiptSchema.safeParse(receipt);
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "checkbirr",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Automatic payment verification for Ethiopian bank receipts",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"birr",
|
|
16
|
+
"checkbirr",
|
|
17
|
+
"verification",
|
|
18
|
+
"payment",
|
|
19
|
+
"payment-verification",
|
|
20
|
+
"screenshot",
|
|
21
|
+
"cbe",
|
|
22
|
+
"Ethiopia"
|
|
23
|
+
],
|
|
24
|
+
"author": "Amieldev",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/Amieldev/checkbirr"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"jimp": "^1.6.1",
|
|
32
|
+
"jsqr": "^1.4.0",
|
|
33
|
+
"puppeteer": "^25.10.0",
|
|
34
|
+
"zod": "^4.6.7"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^26.4.0",
|
|
38
|
+
"tsx": "^4.23.15",
|
|
39
|
+
"typescript": "^7.0.2"
|
|
40
|
+
}
|
|
41
|
+
}
|