ethio-receipt-verify 0.1.0__tar.gz

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.
Files changed (26) hide show
  1. ethio_receipt_verify-0.1.0/.gitignore +41 -0
  2. ethio_receipt_verify-0.1.0/PKG-INFO +409 -0
  3. ethio_receipt_verify-0.1.0/README.md +380 -0
  4. ethio_receipt_verify-0.1.0/ethio_receipt_verify/__init__.py +52 -0
  5. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/__init__.py +28 -0
  6. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/awash.py +48 -0
  7. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/base.py +29 -0
  8. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/boa.py +89 -0
  9. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/cbe.py +103 -0
  10. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/cbebirr.py +46 -0
  11. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/dashen.py +43 -0
  12. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/kaafiebirr.py +21 -0
  13. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/mpesa.py +65 -0
  14. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/siinqee.py +20 -0
  15. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/telebirr.py +88 -0
  16. ethio_receipt_verify-0.1.0/ethio_receipt_verify/banks/zemen.py +48 -0
  17. ethio_receipt_verify-0.1.0/ethio_receipt_verify/cli.py +251 -0
  18. ethio_receipt_verify-0.1.0/ethio_receipt_verify/client.py +416 -0
  19. ethio_receipt_verify-0.1.0/ethio_receipt_verify/client_types.py +415 -0
  20. ethio_receipt_verify-0.1.0/ethio_receipt_verify/errors.py +14 -0
  21. ethio_receipt_verify-0.1.0/ethio_receipt_verify/registry.py +28 -0
  22. ethio_receipt_verify-0.1.0/ethio_receipt_verify/result.py +48 -0
  23. ethio_receipt_verify-0.1.0/pyproject.toml +53 -0
  24. ethio_receipt_verify-0.1.0/tests/__init__.py +0 -0
  25. ethio_receipt_verify-0.1.0/tests/test_banks.py +220 -0
  26. ethio_receipt_verify-0.1.0/tests/test_client.py +485 -0
@@ -0,0 +1,41 @@
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
@@ -0,0 +1,409 @@
1
+ Metadata-Version: 2.4
2
+ Name: ethio-receipt-verify
3
+ Version: 0.1.0
4
+ Summary: Free, open-source Ethiopian bank/wallet receipt verification. Reverse-engineered public endpoints.
5
+ Project-URL: Homepage, https://github.com/1RB/cheki
6
+ Project-URL: Repository, https://github.com/1RB/cheki
7
+ Project-URL: Issues, https://github.com/1RB/cheki/issues
8
+ Author: 1RB
9
+ License: MIT
10
+ Keywords: bank,cbe,ethiopia,receipt,telebirr,verification
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Requires-Python: >=3.9
20
+ Requires-Dist: beautifulsoup4>=4.11.0
21
+ Requires-Dist: pdfplumber>=0.9.0
22
+ Requires-Dist: requests>=2.28.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
25
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
26
+ Requires-Dist: reportlab>=4.0.0; extra == 'dev'
27
+ Requires-Dist: responses>=0.23.0; extra == 'dev'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # ethio-receipt-verify (cheki Python SDK)
31
+
32
+ Free, open-source **Ethiopian bank/wallet receipt verification** for Python.
33
+
34
+ cheki verifies that a payment receipt is real by fetching it directly from the
35
+ bank's own public receipt endpoint — no API keys, no paywalls, no scraping with
36
+ Selenium. This SDK mirrors the [cheki](https://github.com/1RB/cheki) project's
37
+ other official SDKs (TypeScript, Go, PHP, Dart).
38
+
39
+ ## Two modes
40
+
41
+ The SDK offers two complementary ways to verify receipts:
42
+
43
+ | Mode | When to use | How it works |
44
+ | --- | --- | --- |
45
+ | **API client** (recommended) | Production apps, servers, anything that wants reliability | Calls the hosted cheki REST API, which handles geo-blocking, QR decryption, PDF parsing, and bank-endpoint rotation for you. |
46
+ | **Direct verification** (advanced) | Self-hosting, no external dependency, research | Fetches bank endpoints *directly* from your machine. No round-trip to cheki, but geo-blocked banks (telebirr, M-Pesa) will fail outside Ethiopia. |
47
+
48
+ Both are importable from the top-level package:
49
+
50
+ ```python
51
+ from ethio_receipt_verify import ChekiClient, verify, supported_banks
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Quick start — API client
57
+
58
+ ```python
59
+ from ethio_receipt_verify import ChekiClient
60
+
61
+ cheki = ChekiClient() # uses https://cheki-pi.vercel.app by default
62
+
63
+ # CBE requires the receiving account number
64
+ result = cheki.verify("cbe", "FT26140P01YB", account_number="1000560536171")
65
+
66
+ if result.is_verified:
67
+ print(f"{result.sender_name} sent {result.amount} {result.currency}")
68
+ print(f"to {result.receiver_name} on {result.date}")
69
+ else:
70
+ print(f"Not verified: {result.error}")
71
+ ```
72
+
73
+ ### Batch verification
74
+
75
+ Verify up to 50 receipts in a single request:
76
+
77
+ ```python
78
+ results = cheki.verify_batch([
79
+ {"bank": "cbe", "reference": "FT26140P01YB", "accountNumber": "1000560536171"},
80
+ {"bank": "telebirr", "reference": "DET8FJGUJ4"},
81
+ {"bank": "dashen", "reference": "B22WDTI261620001"},
82
+ ])
83
+
84
+ print(f"{results.verified}/{results.total} verified")
85
+ for r in results.results:
86
+ print(r.reference, r.is_verified, r.error)
87
+ ```
88
+
89
+ ### Discover supported banks
90
+
91
+ ```python
92
+ for bank in cheki.get_banks():
93
+ print(f"{bank.code:<12} {bank.name} [{bank.status}]")
94
+ ```
95
+
96
+ ### Health check
97
+
98
+ ```python
99
+ health = cheki.get_health()
100
+ print(health.status) # "ok"
101
+ for check in health.checks:
102
+ print(f" {check.name}: {check.status} ({check.latency_ms}ms)")
103
+ ```
104
+
105
+ ### Context manager
106
+
107
+ `ChekiClient` reuses a connection pool and can be used as a context manager:
108
+
109
+ ```python
110
+ with ChekiClient(timeout=10, max_retries=5) as cheki:
111
+ result = cheki.verify("cbe", "FT26140P01YB", account_number="1000560536171")
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Quick start — Direct verification (advanced)
117
+
118
+ Direct verification fetches the bank endpoint from *your* machine. It needs no
119
+ cheki server but is subject to geo-blocking and bank-side changes.
120
+
121
+ ```python
122
+ from ethio_receipt_verify import verify, supported_banks
123
+
124
+ # CBE needs the full receiving account number
125
+ result = verify("cbe", "FT26140P01YB", account_number="1000560536171")
126
+
127
+ print(result.status) # VerificationStatus.VERIFIED
128
+ print(result.exists) # True
129
+ print(result.amount) # 20000.0
130
+ print(result.sender_name)
131
+
132
+ # telebirr / M-Pesa only work from Ethiopian IP addresses
133
+ print(supported_banks())
134
+ ```
135
+
136
+ Direct verification returns a `VerificationResult` (different from the API
137
+ client's `ClientVerifyResult`). See the [API reference](#api-reference) below.
138
+
139
+ ---
140
+
141
+ ## Installation
142
+
143
+ ```bash
144
+ pip install ethio-receipt-verify
145
+ ```
146
+
147
+ From source (development):
148
+
149
+ ```bash
150
+ git clone https://github.com/1RB/cheki.git
151
+ cd cheki/python
152
+ pip install -e ".[dev]"
153
+ ```
154
+
155
+ Dependencies: `requests`, `beautifulsoup4`, `pdfplumber` (the latter two are only
156
+ needed for direct verification of PDF/HTML receipts).
157
+
158
+ ---
159
+
160
+ ## API reference
161
+
162
+ ### `ChekiClient`
163
+
164
+ ```python
165
+ ChekiClient(
166
+ base_url="https://cheki-pi.vercel.app",
167
+ api_key=None,
168
+ timeout=30,
169
+ max_retries=3,
170
+ session=None,
171
+ user_agent=None,
172
+ )
173
+ ```
174
+
175
+ | Parameter | Type | Default | Description |
176
+ | --- | --- | --- | --- |
177
+ | `base_url` | `str` | `https://cheki-pi.vercel.app` | cheki API root URL. |
178
+ | `api_key` | `str \| None` | `None` | Optional bearer token. The public API does not require one. |
179
+ | `timeout` | `float` | `30` | Per-request timeout in seconds. |
180
+ | `max_retries` | `int` | `3` | Retries on HTTP 408/429/5xx (in addition to the first attempt). |
181
+ | `session` | `requests.Session \| None` | `None` | Reuse a custom session (connection pool, proxies, etc.). |
182
+ | `user_agent` | `str \| None` | auto | `User-Agent` header value. |
183
+
184
+ #### Methods
185
+
186
+ ##### `verify(bank, reference, account_number=None, phone_number=None, qr_data=None) -> ClientVerifyResult`
187
+
188
+ Verify a single receipt.
189
+
190
+ ##### `verify_batch(receipts) -> ClientBatchResult`
191
+
192
+ Verify up to 50 receipts. Each receipt is a dict with `bank`, `reference`, and
193
+ optional `accountNumber` / `phoneNumber` / `qrData`. Results are returned in
194
+ input order with computed `total` / `verified` / `failed` counts.
195
+
196
+ ##### `get_banks() -> list[ClientBankInfo]`
197
+
198
+ List supported banks/wallets.
199
+
200
+ ##### `get_health() -> ClientHealthStatus`
201
+
202
+ Check service health (per-bank reachability).
203
+
204
+ ##### `get_receipt_url(bank, reference, account_number=None) -> str`
205
+
206
+ Build a direct receipt-viewer URL (no network request).
207
+
208
+ ##### `close()`
209
+
210
+ Close the underlying HTTP session.
211
+
212
+ ### Response types
213
+
214
+ #### `ClientVerifyResult`
215
+
216
+ | Field | Type | Notes |
217
+ | --- | --- | --- |
218
+ | `success` | `bool` | HTTP-level success. |
219
+ | `verified` | `bool \| None` | Whether the receipt is legitimate. |
220
+ | `bank`, `bank_code`, `reference` | `str \| None` | Identifiers. |
221
+ | `source_url` | `str \| None` | Where the receipt was fetched from. |
222
+ | `sender_name`, `sender_account` | `str \| None` | Sender details. |
223
+ | `receiver_name`, `receiver_account` | `str \| None` | Receiver details. |
224
+ | `amount`, `currency` | `float \| None`, `str \| None` | Payment amount. |
225
+ | `date` | `str \| None` | Transaction date (as reported by the bank). |
226
+ | `branch`, `reason` | `str \| None` | Extra metadata. |
227
+ | `duration_ms` | `int \| None` | Server-side processing time. |
228
+ | `invoice_number`, `transaction_status` | `str \| None` | Wallet-specific. |
229
+ | `settled_amount`, `stamp_duty`, `discount_amount` | `float \| None` | Wallet fees. |
230
+ | `service_fee`, `service_fee_vat`, `total_paid` | `float \| None` | Wallet fees. |
231
+ | `amount_in_words`, `payment_mode`, `payment_channel` | `str \| None` | Wallet metadata. |
232
+ | `bank_account_number`, `bank_account_name` | `str \| None` | Wallet metadata. |
233
+ | `error` | `str \| None` | Error message on failure. |
234
+ | `fallback_url` | `str \| None` | Direct URL for geo-blocked banks. |
235
+ | `index` | `int \| None` | Position within a batch. |
236
+ | `raw` | `dict` | The unparsed API payload. |
237
+
238
+ Helper: `result.is_verified` → `True` when `success` and `verified` are both true.
239
+
240
+ #### `ClientBatchResult`
241
+
242
+ `success`, `total`, `verified`, `failed`, `results` (list of
243
+ `ClientVerifyResult`), `error`, `raw`.
244
+
245
+ #### `ClientBankInfo`
246
+
247
+ `code`, `name`, `swift`, `type`, `status`, `requires_account`,
248
+ `account_digits`, `requires_phone`, `endpoint`, `color`, `initials`, `raw`.
249
+ Helper: `bank.is_live`.
250
+
251
+ #### `ClientHealthStatus`
252
+
253
+ `success`, `status`, `version`, `timestamp`, `checks` (list of
254
+ `ClientHealthCheck`), `raw`. Helper: `health.is_ok`.
255
+
256
+ #### `ClientHealthCheck`
257
+
258
+ `name`, `status`, `latency_ms`, `raw`.
259
+
260
+ ### Direct verification
261
+
262
+ #### `verify(bank, reference, **kwargs) -> VerificationResult`
263
+
264
+ Fetch and parse a receipt directly from the bank endpoint.
265
+
266
+ - `cbe`, `boa`: pass `account_number=` (full receiving account).
267
+ - `cbebirr`: pass `phone_number=` (payer phone, `2519XXXXXXXXX`).
268
+
269
+ Returns a `VerificationResult` with `status` (`VerificationStatus`), `exists`,
270
+ `amount`, `sender_name`, `receiver_name`, `transaction_date`, `source_url`, etc.
271
+
272
+ #### `supported_banks() -> dict[str, str]`
273
+
274
+ Map of bank code → human name for the banks with direct verifiers.
275
+
276
+ ---
277
+
278
+ ## Error handling
279
+
280
+ ### API client errors
281
+
282
+ All API client errors derive from `ChekiClientError`:
283
+
284
+ ```python
285
+ from ethio_receipt_verify import (
286
+ ChekiClient, ChekiClientError, ChekiAPIError, ChekiNetworkError, ChekiTimeoutError,
287
+ )
288
+
289
+ cheki = ChekiClient()
290
+ try:
291
+ result = cheki.verify("cbe", "FT26140P01YB", account_number="1000560536171")
292
+ except ChekiTimeoutError as exc:
293
+ print("Request timed out:", exc)
294
+ except ChekiNetworkError as exc:
295
+ print("Network error:", exc)
296
+ except ChekiAPIError as exc:
297
+ print(f"API error (HTTP {exc.status_code}):", exc.message)
298
+ except ChekiClientError as exc:
299
+ print("Client error:", exc)
300
+ ```
301
+
302
+ | Error | Meaning |
303
+ | --- | --- |
304
+ | `ChekiAPIError` | Non-2xx API response. Carries `status_code`, `message`, `body`. |
305
+ | `ChekiNetworkError` | Connection failure. |
306
+ | `ChekiTimeoutError` | Request timed out (subclass of `ChekiNetworkError`). |
307
+
308
+ > **Note:** a verification that *finds no receipt* is **not** an error — the API
309
+ > returns `success: false` with an `error` message in the `ClientVerifyResult`.
310
+ > Exceptions are reserved for transport/server failures.
311
+
312
+ ### Direct verification errors
313
+
314
+ ```python
315
+ from ethio_receipt_verify import verify
316
+ from ethio_receipt_verify.errors import (
317
+ VerificationError, ReceiptNotFoundError, UpstreamError, UnsupportedBankError,
318
+ )
319
+ ```
320
+
321
+ | Error | Meaning |
322
+ | --- | --- |
323
+ | `UnsupportedBankError` | The bank code is not supported. |
324
+ | `ReceiptNotFoundError` | The bank returned a "not found" response. |
325
+ | `UpstreamError` | The bank endpoint is unreachable or returned an error. |
326
+
327
+ ### Retries
328
+
329
+ `ChekiClient` automatically retries on HTTP `408`, `429`, and `5xx` using
330
+ exponential backoff with full jitter (up to `max_retries` extra attempts). A
331
+ `Retry-After` header, when present, is honored. Network and timeout errors are
332
+ also retried.
333
+
334
+ ---
335
+
336
+ ## CLI
337
+
338
+ Install the package to get the `ethio-verify` command:
339
+
340
+ ```bash
341
+ # API client (recommended)
342
+ ethio-verify cbe FT26140P01YB --account 1000560536171 --api
343
+ ethio-verify telebirr DET8FJGUJ4 --api --json
344
+
345
+ # Direct verification (advanced)
346
+ ethio-verify cbe FT26140P01YB --account 1000560536171
347
+
348
+ # Service health & supported banks (via API)
349
+ ethio-verify --health
350
+ ethio-verify --list-banks --api
351
+ ```
352
+
353
+ ### Flags
354
+
355
+ | Flag | Description |
356
+ | --- | --- |
357
+ | `bank` | Bank/wallet code (e.g. `cbe`, `telebirr`, `boa`, `mpesa`). |
358
+ | `reference` | Transaction reference number. |
359
+ | `--account` | Receiving account number (required for cbe, boa). |
360
+ | `--phone` | Payer phone number (required for cbebirr). |
361
+ | `--qr` | Raw QR payload (Bank of Abyssinia inter-bank receipts). |
362
+ | `--api` | Use the hosted cheki REST API instead of direct verification. |
363
+ | `--base-url` | cheki API base URL (default: `https://cheki-pi.vercel.app`). |
364
+ | `--api-key` | Optional bearer token. |
365
+ | `--timeout` | Per-request timeout in seconds (API mode, default: 30). |
366
+ | `--json` | Output raw JSON. |
367
+ | `--list-banks` | List supported banks and exit. |
368
+ | `--health` | Check cheki API health and exit (implies `--api`). |
369
+
370
+ ---
371
+
372
+ ## Configuration
373
+
374
+ ### Custom base URL / self-hosting
375
+
376
+ ```python
377
+ cheki = ChekiClient(base_url="https://cheki.my-server.com")
378
+ ```
379
+
380
+ ### Proxies & custom session
381
+
382
+ ```python
383
+ import requests
384
+
385
+ session = requests.Session()
386
+ session.proxies = {"https": "http://proxy.local:8080"}
387
+ cheki = ChekiClient(session=session)
388
+ ```
389
+
390
+ ### Timeouts & retries
391
+
392
+ ```python
393
+ cheki = ChekiClient(timeout=10, max_retries=5)
394
+ ```
395
+
396
+ ---
397
+
398
+ ## Supported banks
399
+
400
+ cbe, telebirr, boa, mpesa, dashen, zemen, cbebirr, siinqee, kaafiebirr (and
401
+ more — run `cheki.get_banks()` or `ethio-verify --list-banks --api` for the
402
+ current list). Availability depends on the bank endpoint's status and
403
+ geo-restrictions.
404
+
405
+ ---
406
+
407
+ ## License
408
+
409
+ MIT © [1RB](https://github.com/1RB)