Transaction Verification API — Python library for verifying Ethiopian payment transactions (CBE, Telebirr, Dashen, Abyssinia, CBE Birr, M-Pesa).
Project description
tx-verify
Async Python library for verifying Ethiopian payment transactions across CBE, Telebirr, Dashen Bank, Bank of Abyssinia, CBE Birr, and M-Pesa.
For every provider the library fetches the official receipt (PDF or HTML API
response), parses it, and returns a strongly-typed result object. No headless
browser is bundled — PDF parsing uses pypdf, HTML parsing uses
BeautifulSoup, and M-Pesa uses the public Safaricom API — so it runs anywhere
Python 3.10+ does.
Supported Providers
| Provider | Function | Input (example) | Result type |
|---|---|---|---|
| CBE | verify_cbe() |
"FT23062669JJ", "12345678" (8-digit suffix) |
TransactionResult |
| Telebirr | verify_telebirr() |
"CE12345678" (10-char alphanumeric) |
TransactionResult |
| Dashen Bank | verify_dashen() |
"1234567890123456" (16 digits) |
TransactionResult |
| Bank of Abyssinia | verify_abyssinia() |
"FT23062669JJ", "90172" (5-digit suffix) |
TransactionResult |
| CBE Birr | verify_cbe_birr() |
"AB1234CD56", "0911234567" (local phone) |
TransactionResult |
| M-Pesa | verify_mpesa() |
"UE20VG1GS8" (10-char alphanumeric) |
TransactionResult |
| Image (Mistral) | verify_image() |
image_bytes (JPEG/PNG) |
ImageVerifyResult |
| Universal | verify_universal() |
Auto-routes by reference format | TransactionResult |
Installation
pip install tx-verify
Or with uv:
uv pip install tx-verify
verify_image() additionally requires the MISTRAL_API_KEY environment
variable (see Image verification).
Quick Start
import asyncio
from tx_verify import verify_telebirr, verify_cbe
async def main():
# --- Telebirr ---
result = await verify_telebirr("CE12345678")
if result.success:
print(result.payer_name, result.amount)
# --- CBE ---
result = await verify_cbe("FT23062669JJ", "12345678")
if result.success:
print(f"Paid {result.amount} ETB to {result.receiver_name}")
asyncio.run(main())
Provider Reference
CBE — Commercial Bank of Ethiopia
CBE transaction references are 12 characters starting with FT. You must
also supply the last 8 digits of the account number as the second
positional argument.
from tx_verify import verify_cbe
result = await verify_cbe("FT23062669JJ", "12345678")
# result.success → bool
# result.payer_name → str | None
# result.payer_account → str | None
# result.receiver_name → str | None
# result.receiver_account → str | None
# result.amount → float | None
# result.transaction_date → datetime | None
# result.transaction_reference → str | None
# result.narrative → str | None
# result.error → str | None
The verifier fetches a PDF receipt from CBE servers, extracts text with
pypdf, and parses payer / receiver / amount / date fields.
Telebirr
Telebirr references are 10-character alphanumeric codes. The verifier
scrapes the public Ethio Telecom receipt page and returns a TransactionResult.
On failure it returns a result with success=False.
from tx_verify import verify_telebirr
result = await verify_telebirr("CE12345678")
if result.success:
print(result.payer_name) # str | None
print(result.payer_account) # str | None
print(result.receiver_name) # str | None
print(result.receiver_account) # str | None
print(result.transaction_status) # str | None
print(result.receipt_number) # str | None
print(result.transaction_date) # datetime | None
print(result.amount) # float | None
print(result.service_charge) # float | None
print(result.vat) # float | None
print(result.total_amount) # float | None
print(result.meta) # dict
TelebirrVerificationError may be raised when a proxy returns an explicit
error message (see Error Handling).
Dashen Bank
Dashen references are 16-digit numbers starting with 3 digits (e.g.
1234567890123456). The verifier fetches a PDF with built-in retry logic
(up to 5 attempts).
from tx_verify import verify_dashen
result = await verify_dashen("1234567890123456")
# result.success → bool
# result.payer_name → str | None
# result.payer_account → str | None
# result.payment_channel → str | None
# result.transaction_type → str | None
# result.narrative → str | None
# result.receiver_name → str | None
# result.receiver_account → str | None
# result.transaction_reference → str | None
# result.transaction_date → datetime | None
# result.amount → float | None
# result.service_charge → float | None
# result.vat → float | None
# result.total_amount → float | None
# result.amount_in_words → str | None
# result.meta → dict[str, Any]
# result.error → str | None
Bank of Abyssinia
Abyssinia references are also 12 characters starting with FT, but the
suffix is the last 5 digits of the account number. The bank returns JSON
rather than a PDF, so parsing is done directly from the API response.
from tx_verify import verify_abyssinia
result = await verify_abyssinia("FT23062669JJ", "90172")
# result.success → bool
# result.transaction_reference → str | None
# result.payer_name → str | None
# result.payer_account → str | None
# result.receiver_name → str | None
# result.receiver_account → str | None
# result.amount → float | None
# result.total_amount → float | None
# result.vat → float | None
# result.service_charge → float | None
# result.currency → str | None
# result.transaction_type → str | None
# result.narrative → str | None
# result.transaction_date → datetime | None
# result.amount_in_words → str | None
# result.meta → dict[str, Any]
# result.error → str | None
CBE Birr
CBE Birr receipts are 10-character alphanumeric codes. You also need the
wallet phone number in local Ethiopian format starting with 09 and 10
digits long (e.g. 0911234567).
from tx_verify import verify_cbe_birr
result = await verify_cbe_birr("AB1234CD56", "0911234567")
# result.success → bool
# result.payer_name → str | None
# result.payer_account → str | None
# result.receiver_account → str | None
# result.receiver_name → str | None
# result.transaction_reference → str | None
# result.transaction_status → str | None
# result.receipt_number → str | None
# result.transaction_date → datetime | None
# result.amount → float | None
# result.service_charge → float | None
# result.vat → float | None
# result.total_amount → float | None
# result.narrative → str | None
# result.payment_channel → str | None
# result.meta → dict
On failure result.success is False and result.error contains the reason:
if not result.success:
print(result.error)
M-Pesa
M-Pesa references are 10-character alphanumeric codes. The verifier calls the Safaricom primary API, decodes a Base64-encoded PDF from the response, and parses it.
from tx_verify import verify_mpesa
result = await verify_mpesa("UE20VG1GS8")
# result.success → bool
# result.transaction_reference → str | None
# result.receipt_number → str | None
# result.transaction_date → datetime | None
# result.amount → float | None
# result.service_charge → float | None
# result.vat → float | None
# result.payer_name → str | None
# result.payer_account → str | None
# result.payment_method → str | None
# result.transaction_type → str | None
# result.payment_channel → str | None
# result.amount_in_words → str | None
# result.meta → dict
# result.error → str | None
Image verification (Mistral Vision)
Upload a receipt image (JPEG/PNG) and Mistral Vision AI will detect whether it is a CBE or Telebirr receipt, extract the reference, and optionally verify it automatically.
from tx_verify import verify_image
with open("receipt.jpg", "rb") as f:
image_bytes = f.read()
# Detect only
info = await verify_image(image_bytes, auto_verify=False)
print(info.type) # "telebirr" | "cbe" | None
print(info.reference) # e.g. "CE12345678"
print(info.forward_to) # "/verify-telebirr" | "/verify-cbe"
# Auto-verify (account_suffix required for CBE)
info = await verify_image(
image_bytes,
auto_verify=True,
account_suffix="12345678",
)
print(info.verified) # bool | None
print(info.details) # TransactionResult | None
Requires the MISTRAL_API_KEY environment variable. The mistralai package
is already installed as a dependency.
Universal — auto-route by reference format
verify_universal() accepts a reference string and routes it to the correct
provider based on length and prefix. Extra arguments are forwarded
automatically.
| Reference length & prefix | How verify_universal decides |
Required extra args |
|---|---|---|
16 digits starting with 3 |
Dashen Bank | None |
12 chars starting with FT + 8-digit suffix |
CBE | suffix="12345678" |
12 chars starting with FT + 5-digit suffix |
Bank of Abyssinia | suffix="90172" |
| 10 chars alphanumeric + phone number | CBE Birr | phone_number="0911234567" |
| 10 chars alphanumeric (no phone) | Telebirr | None |
from tx_verify import verify_universal
# Telebirr — no extra args needed
result = await verify_universal("CE12345678")
# CBE — pass an 8-digit suffix
result = await verify_universal("FT23062669JJ", suffix="12345678")
# CBE Birr — pass a local phone number
result = await verify_universal("AB1234CD56", phone_number="0911234567")
# The wrapper always returns a TransactionResult
print(result.success) # bool
print(result.transaction_reference) # str | None
print(result.error) # str | None
print(result.meta) # dict | None
Proxy Support
All receipt verifiers accept an explicit proxies argument. Environment
variables are never read automatically — you must pass the proxy yourself.
Supported schemes:
| Scheme | Description |
|---|---|
http |
Plain HTTP forward proxy |
https |
HTTPS proxy (CONNECT tunnel) |
socks4 |
SOCKS4 proxy |
socks5 |
SOCKS5 proxy (client resolves DNS) |
socks5h |
SOCKS5 proxy (proxy resolves DNS) |
Authentication is embedded in the URL:
# Single global proxy
proxies = "http://user:pass@proxy.example.com:8080"
# Per-scheme mapping
proxies = {
"http://": "http://proxy.example.com:8080",
"https://": "socks5://localhost:1080",
}
Pass it to any verifier:
from tx_verify import verify_telebirr, verify_cbe, verify_mpesa
# Telebirr through an HTTP proxy
receipt = await verify_telebirr("CE12345678", proxies="http://proxy:8080")
# CBE through SOCKS5
result = await verify_cbe(
"FT23062669JJ", "12345678", proxies="socks5://127.0.0.1:1080"
)
# M-Pesa with per-scheme mapping
result = await verify_mpesa("UE20VG1GS8", proxies={
"http://": "http://proxy:8080",
"https://": "socks5h://proxy:1080",
})
verify_universal and verify_image also forward proxies to the
underlying provider automatically.
SOCKS tip:
socks5h://tells the proxy server to resolve hostnames, which is useful when the client cannot reach DNS directly.
Error Handling
All verifiers return a TransactionResult with success and error fields.
Inspect result.success and result.error for expected failures (network
errors, missing receipts, parsing failures).
TelebirrVerificationErrormay be raised for proxy-level errors. Catch it if you want to show the user a friendly message:
from tx_verify import TelebirrVerificationError, verify_telebirr
try:
result = await verify_telebirr("INVALID_REF")
if not result.success:
print("Receipt not found.")
except TelebirrVerificationError as exc:
print(f"Telebirr error: {exc}")
if exc.details:
print(f"Details: {exc.details}")
The library also provides a generic error handler for wrapping internal errors:
from tx_verify.utils.error_handler import AppError, ErrorType
Environment Variables
| Variable | Purpose | Required by |
|---|---|---|
MISTRAL_API_KEY |
Mistral Vision API key | verify_image() |
LOG_LEVEL |
DEBUG or INFO (default INFO) |
Optional for all verifiers |
Development
# Clone
git clone https://github.com/nahom-network/tx-verify.git
cd tx-verify
# Install with dev dependencies (uv is recommended because uv.lock exists)
uv pip install -e ".[dev]"
# Or pip
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
# Lint & format
ruff check . --fix
ruff format .
# Type-check
mypy tx_verify/
# Run tests
pytest -v
CI enforces lint → typecheck → test in this order. A one-liner that matches the CI gate locally:
ruff check . && ruff format --check . && mypy tx_verify/ && pytest
Examples
See the examples/ directory for a runnable example per
provider:
| File | What it shows |
|---|---|
telebirr.py |
Verify a Telebirr receipt by reference number |
cbe.py |
Fetch and parse a CBE PDF receipt |
cbe_birr.py |
Verify a CBE Birr wallet transaction |
dashen.py |
Verify a Dashen Bank receipt with retry logic |
abyssinia.py |
Verify a Bank of Abyssinia transaction |
mpesa.py |
Verify an Ethiopian M-Pesa transaction |
image.py |
Analyse a receipt image with Mistral Vision AI |
universal.py |
Let the library auto-route to the right provider |
error_handling.py |
Catch provider-specific errors gracefully |
License
ISC © Nahom D
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tx_verify-1.2.0.tar.gz.
File metadata
- Download URL: tx_verify-1.2.0.tar.gz
- Upload date:
- Size: 157.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6d316ef0d6cfeae3262ba2f3df898d236ab6c0e0b4636b57af6e1c25546248fe
|
|
| MD5 |
12fc3a2a44a611d95d34c6a8937e076b
|
|
| BLAKE2b-256 |
5865e911650f082a023ea2f6747672dd452bcb310433182ed71eabd81f2a844e
|
Provenance
The following attestation bundles were made for tx_verify-1.2.0.tar.gz:
Publisher:
release.yml on nahom-network/tx-verify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tx_verify-1.2.0.tar.gz -
Subject digest:
6d316ef0d6cfeae3262ba2f3df898d236ab6c0e0b4636b57af6e1c25546248fe - Sigstore transparency entry: 1472581752
- Sigstore integration time:
-
Permalink:
nahom-network/tx-verify@926ec8eb988ee5a4731244030fb7f618ccc2993c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nahom-network
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@926ec8eb988ee5a4731244030fb7f618ccc2993c -
Trigger Event:
push
-
Statement type:
File details
Details for the file tx_verify-1.2.0-py3-none-any.whl.
File metadata
- Download URL: tx_verify-1.2.0-py3-none-any.whl
- Upload date:
- Size: 35.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b3355f07448ea72b9eca7aeb41e1669716de5fe0e04eb0913321f4a43e3bec8
|
|
| MD5 |
53a643fbf73afea821e0cbe7b60616f7
|
|
| BLAKE2b-256 |
def007bb954d9df530e926f737cb6f02cebf211cb5bdfb8217489a3709d15d1d
|
Provenance
The following attestation bundles were made for tx_verify-1.2.0-py3-none-any.whl:
Publisher:
release.yml on nahom-network/tx-verify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tx_verify-1.2.0-py3-none-any.whl -
Subject digest:
3b3355f07448ea72b9eca7aeb41e1669716de5fe0e04eb0913321f4a43e3bec8 - Sigstore transparency entry: 1472581936
- Sigstore integration time:
-
Permalink:
nahom-network/tx-verify@926ec8eb988ee5a4731244030fb7f618ccc2993c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nahom-network
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@926ec8eb988ee5a4731244030fb7f618ccc2993c -
Trigger Event:
push
-
Statement type: