Skip to main content

Transaction Verification API — Python library for verifying Ethiopian payment transactions (CBE, Telebirr, Dashen, Abyssinia, CBE Birr, M-Pesa).

Project description

tx-verify

PyPI Python License: ISC

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).

  • TelebirrVerificationError may 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

tx_verify-1.2.0.tar.gz (157.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tx_verify-1.2.0-py3-none-any.whl (35.8 kB view details)

Uploaded Python 3

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

Hashes for tx_verify-1.2.0.tar.gz
Algorithm Hash digest
SHA256 6d316ef0d6cfeae3262ba2f3df898d236ab6c0e0b4636b57af6e1c25546248fe
MD5 12fc3a2a44a611d95d34c6a8937e076b
BLAKE2b-256 5865e911650f082a023ea2f6747672dd452bcb310433182ed71eabd81f2a844e

See more details on using hashes here.

Provenance

The following attestation bundles were made for tx_verify-1.2.0.tar.gz:

Publisher: release.yml on nahom-network/tx-verify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for tx_verify-1.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3b3355f07448ea72b9eca7aeb41e1669716de5fe0e04eb0913321f4a43e3bec8
MD5 53a643fbf73afea821e0cbe7b60616f7
BLAKE2b-256 def007bb954d9df530e926f737cb6f02cebf211cb5bdfb8217489a3709d15d1d

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page