Skip to main content

Python client for Airtel Money Tanzania — Collection & Disbursement APIs

Project description

pyairtel

image

PyPI version Python versions CI License: MIT

Python client for Airtel Money Tanzania — Collection & Disbursement APIs.

📖 Full documentation & examples → ronaldgosso.github.io/pyairtel


Features

  • 🔐 OAuth2 authentication with automatic token refresh
  • 📲 Collection — USSD push payments (request money from subscribers)
  • Transaction status polling
  • 💸 Disbursement — transfer money to Airtel Money wallets
  • ↩️ Refunds — reverse completed transactions by airtel_money_id
  • 🔒 RSA PIN encryption for disbursement security
  • 📞 Phone number normalisation (handles +255…, 0…, 255… formats)
  • ⚠️ ESB error decoding — all 9 Airtel Tanzania error codes mapped to human-readable messages
  • 🧪 Sandbox & Production environments

Installation

pip install pyairtel

For disbursement with PIN encryption, install the encryption extra:

pip install "pyairtel[encryption]"

Quick Start

1. Get credentials

Create an account at developers.airtel.co.tz, create an application, and add Collection and Disbursement APIs. Copy your client_id and client_secret from Key Management.

2. Collect money from a subscriber

from dotenv import load_dotenv
import os
from pyairtel import AirtelMoney

load_dotenv()

airtel = AirtelMoney(
    client_id=os.environ["AIRTEL_CLIENT_ID"],
    client_secret=os.environ["AIRTEL_CLIENT_SECRET"],
    sandbox=os.getenv("AIRTEL_SANDBOX", "true").lower() == "true",       # set False for production
)

response = airtel.collect(
    phone="+255681219610",
    amount=5000,
    reference="invoice-42",
)

print(response.transaction_id)  # TXN-20240101120000123456-AB12CD34
print(response.is_initiated)    # True

3. Check transaction status

import time

time.sleep(15)  # give the subscriber time to approve

status = airtel.get_collection_status(response.transaction_id)

if status.is_successful:
    print("Payment confirmed!", status.airtel_money_id)
elif status.is_pending:
    print("Still waiting for subscriber to approve...")
elif status.is_failed:
    print("Payment failed:", status.message)

4. Transfer money to a subscriber (Disbursement)

# First validate the payee
check = airtel.validate_payee("+255681219610")
if not check.is_valid:
    print("Payee cannot receive money:", check.message)
else:
    result = airtel.transfer(
        phone="+255681219610",
        amount=2000,
        pin="1234",                              # your merchant PIN
        public_key_pem=open("airtel_pub.pem").read(),  # from Key Management
        payer_first_name="Ronald",
        payer_last_name="Gosso",
        reference="payout-001",
    )
    print(result.is_successful, result.airtel_money_id)

5. Refund a transaction

# Use airtel_money_id from a successful collection status
status = airtel.get_collection_status(transaction_id)

if status.is_successful:
    refund = airtel.refund(status.airtel_money_id)
    print(refund.is_successful, refund.message)

Environment

Parameter Sandbox Production
sandbox True False
Base URL https://openapiuat.airtel.africa https://openapi.airtel.africa

Credentials & Environment Variables

Never hardcode credentials in your code. Copy .env.example to .env and fill in your values:

cp .env.example .env

.env:

AIRTEL_CLIENT_ID=your-client-id-here
AIRTEL_CLIENT_SECRET=your-client-secret-here
AIRTEL_SANDBOX=true
AIRTEL_PUBLIC_KEY_PATH=airtel_pub.pem   # disbursement only

Then load it in your project:

from dotenv import load_dotenv 
import os
from pyairtel import AirtelMoney

load_dotenv()

airtel = AirtelMoney(
    client_id=os.environ["AIRTEL_CLIENT_ID"],
    client_secret=os.environ["AIRTEL_CLIENT_SECRET"],
    sandbox=os.getenv("AIRTEL_SANDBOX", "true").lower() == "true",
)

⚠️ .env and *.pem are in .gitignore — never commit them.

Error Handling

from pyairtel import AirtelMoney, decode_esb_error
from pyairtel.exceptions import AuthenticationError, CollectionError, EncryptionError

try:
    resp = airtel.collect(phone="+255681219610", amount=1000, reference="ref-1")
except AuthenticationError as e:
    print("Bad credentials or token expired:", e)
except CollectionError as e:
    print("Collection failed:", e)
    print("ESB code:", e.esb_code)      # e.g. "ESB000014"
    print("Reason:",   e.esb_message)   # "Insufficient funds..."

# Decode any ESB code manually
print(decode_esb_error("ESB000039"))
# → "Transaction timed out. The subscriber did not respond to the USSD prompt in time."
Exception When raised
AuthenticationError Token acquisition fails (bad credentials, network)
CollectionError USSD push, status check, or refund fails — includes .esb_code and .esb_message
DisbursementError Transfer or payee validation fails
EncryptionError RSA PIN encryption fails (e.g. missing pycryptodome)
ValidationError Invalid phone number format

Airtel Tanzania ESB Error Codes

Code Meaning
ESB000001 General error — check credentials and try again
ESB000004 Service unavailable — Airtel Money is temporarily down
ESB000008 Invalid transaction — request parameters are incorrect
ESB000011 Subscriber not found — number not registered on Airtel Money
ESB000014 Insufficient funds — subscriber balance too low
ESB000033 Transaction limit exceeded — amount above subscriber's limit
ESB000036 Daily limit exceeded — subscriber has hit their daily cap
ESB000039 Transaction timed out — subscriber didn't respond to USSD prompt
ESB000041 PIN locked — too many incorrect PIN attempts
ESB000045 Duplicate transaction — this ID was already processed

Local Development

Follow these steps to run pyairtel locally, run tests, and contribute.

1. Clone the repository

git clone https://github.com/ronaldgosso/pyairtel.git
cd pyairtel

2. Create and activate a virtual environment

# Create venv
python -m venv venv

# Activate — Linux / Mac
source venv/bin/activate

# Activate — Windows
venv\Scripts\activate

# Confirm — should show the venv path
which python

3. Install the package and dev dependencies

pip install -e ".[dev]"

# Also install encryption support for disbursement tests
pip install -e ".[dev,encryption]"

# install from requirements.txt
pip install -r requirements.txt

4. Fix lint errors automatically

# Auto-fix ruff issues (import order, trailing whitespace, deprecated types)
ruff check pyairtel tests --fix --unsafe-fixes

# Format with black
black pyairtel tests

5. Verify the full quality gate

# Linting — should print: All checks passed!
ruff check pyairtel tests

# Formatting — should print: X files would be left unchanged.
black --check pyairtel tests

# Type checking — should print: Success: no issues found in 7 source files
mypy pyairtel

6. Run the test suite

# Run all 25 tests with verbose output
pytest tests/ -v

# Run a specific test class
pytest tests/ -v -k "TestCollection"

# Run a single test by name
pytest tests/ -v -k "test_collect_success"

7. Deactivate the venv when done

deactivate

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository on GitHub
  2. Create a feature branch: git checkout -b feat/your-feature-name
  3. Make your changes — all new code must include tests
  4. Run the full quality gate (steps 4–6 above) and ensure everything passes
  5. Commit with a descriptive message: git commit -m "feat: add your feature"
  6. Push and open a Pull Request against main

Please keep PRs focused — one feature or fix per PR. If you're unsure whether something is in scope, open an issue first.


License

MIT © Ronald Isack Gosso

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

pyairtel-0.1.0.tar.gz (263.5 kB view details)

Uploaded Source

Built Distribution

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

pyairtel-0.1.0-py3-none-any.whl (16.1 kB view details)

Uploaded Python 3

File details

Details for the file pyairtel-0.1.0.tar.gz.

File metadata

  • Download URL: pyairtel-0.1.0.tar.gz
  • Upload date:
  • Size: 263.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyairtel-0.1.0.tar.gz
Algorithm Hash digest
SHA256 49d9a9c157db4c8fc314264ce0fce32400f46e74aefd7ce5076cd774e9e4639a
MD5 eedb5849f7f101700546ff2ee336e0a1
BLAKE2b-256 e2f0e894e0e00e43ed37f138df00951c7067841100ac87ccf6025e5e1bf90c5f

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyairtel-0.1.0.tar.gz:

Publisher: ci.yml on ronaldgosso/pyairtel

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

File details

Details for the file pyairtel-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyairtel-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyairtel-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 38c62721e5991fe804d7acf5174e543fb1375f9b63e1b136456c0bf937f88de6
MD5 8455723cf95d1671b7187090efd48ebd
BLAKE2b-256 baff4520dce3474c24b051dc01783b10260a42f843958eac29348b6ad999a046

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyairtel-0.1.0-py3-none-any.whl:

Publisher: ci.yml on ronaldgosso/pyairtel

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