Skip to main content

A modern, async Python package for integrating Tanzanian Mobile Network Operators and Gateways (M-Pesa, Selcom, Safaricom Daraja).

Project description

lipa-py

The Ultimate Assisant for Tanzanian Payments in Python.

A modern, strictly typed, async-first Python package designed to integrate Tanzanian Mobile Network Operators (Vodacom M-Pesa, Tigo Pesa, Airtel Money) and Gateways (Selcom, TIPS) seamlessly into Python backend applications (especially FastAPI).


Features

  • Unified Orchestrator: Write one simple abstract UnifiedPaymentRequest and let lipa-py route it perfectly based on phone prefixes or your configured fallback gateways (e.g., automatically routing Vodacom numbers to M-Pesa, and Airtel numbers to Selcom).
  • Fully Async: Built from the ground up on modern httpx async clients to keep your API responsive without blocking requests.
  • 100% Type Safe: Powered by Pydantic V2. Absolutely no raw dicts returned. Everything is perfectly schema'd for your IDE intellisense.
  • Batteries-Included Webhooks: Ships with pre-configured, dependency-injectable FastAPI routers specifically crafted to respond to MNO Webhooks with an immediate HTTP 200 while pushing your database logic correctly into Background Tasks. No more timeouts!
  • Strict Security: Implements all vendor-specific cryptography natively. No struggling with RSA Public Keys for M-Pesa or HMAC SHA-256 for Selcom. We handle the math; you handle the business.

📦 Installation

# Using uv (Recommended)
uv add lipa-py

# Or pip
pip install lipa-py

🛠️ Quick Start

1. The Unified Payment Interface (Recommended)

This is the fastest way to accept payments in Tanzania without caring about the underlying MNO.

import asyncio
from lipa_py import UnifiedPaymentClient, UnifiedPaymentRequest

async def accept_payment():
    # 1. Define your active gateways
    client = UnifiedPaymentClient({
        "mpesa": {
            "api_key": "your_vodacom_api_key",
            "public_key": "your_vodacom_public_key_pem",
            "environment": "sandbox"
        },
        "selcom": {
            "vendor_id": "YOUR_SELCOM_VENDOR",
            "api_key": "your_selcom_key",
            "api_secret": "your_selcom_secret",
            "environment": "sandbox"
        },
        "safaricom": {
            "consumer_key": "your_safaricom_key",
            "consumer_secret": "your_safaricom_secret",
            "passkey": "your_passkey",
            "shortcode": "174379",
            "environment": "sandbox"
        },
        "tigo_pesa": {
            "client_id": "your_tigo_client_id",
            "client_secret": "your_tigo_secret",
            "biller_code": "YOUR_BILLER_CODE",
            "environment": "sandbox"
        },
        "airtel_money": {
            "client_id": "your_airtel_id",
            "client_secret": "your_airtel_secret",
            "environment": "sandbox"
        },
        "tips": {
            "api_key": "your_tips_token",
            "institution_id": "YOUR_INSTITUTION",
            "environment": "sandbox"
        }
    })

    # 2. Provide the customer details.
    # lipa-py automatically detects the prefixes!
    # 075x -> M-Pesa 
    # 071x -> Tigo Pesa
    # 078x -> Airtel Money
    # 2547x -> Safaricom Daraja
    # If the number is unrecognized, it falls back to Gateway aggregators (Selcom, TIPS)
    request = UnifiedPaymentRequest(
        phone_number="255716000000",
        amount=1000.0,
        reference="TICKET-123",
        email="customer@example.com" 
    )

    response = await client.request_payment(request)
    print(f"Payment via {response.provider} initialized. Trx ID: {response.transaction_id}")
    
    # Clean up graceful
    await client.close()

if __name__ == "__main__":
    asyncio.run(accept_payment())

2. Using Specific MNO Clients Directly

If you don't need the Orchestrator and just want to talk to M-Pesa directly:

import asyncio
from lipa_py.mpesa import MPesaClient, STKPushRequest

async def mpesa_only():
    async with MPesaClient(api_key="...", public_key="...") as mpesa:
        req = STKPushRequest(
            phone_number="255754000000",
            amount=5000,
            reference="TICKET-456",
            third_party_conversation_id="ABC-123"
        )
        res = await mpesa.stk_push(req)
        print(f"STK Push Sent: {res.output_ConversationID}")

asyncio.run(mpesa_only())

3. FastAPI Webhook Integration

Handling asynchronous webhooks correctly in Africa is notoriously difficult due to MNO timeouts. We provide native FastAPI routers that handle the required response codes instantly while delegating your logic to the background.

from fastapi import FastAPI
from lipa_py.mpesa import mpesa_router, set_webhook_handler, MpesaWebhookData

app = FastAPI()

# 1. Write the logic you want to execute when a payment completes
async def handle_successful_payment(data: MpesaWebhookData):
    # This runs safely in a BackgroundTask!
    if data.input_ResultCode == "0": # 0 typically means success in M-Pesa 
        print(f"Payment of {data.input_TransactionID} succeeded!")
        # -> UPDATE YOUR DATABASE HERE <-

# 2. Register your handler
set_webhook_handler(handle_successful_payment)

# 3. Mount the pre-built router
app.include_router(mpesa_router, prefix="/payments/mpesa")

Your webhook is now live at POST /payments/mpesa/webhook.

🧪 Testing MVP & Real-World Requirements

Because lipa-py interacts with strict African MNOs and Gateways, there are some hard requirements before you can successfully make a test API call:

1. Vodacom M-Pesa Requirements

To use the M-Pesa client, you cannot just use dummy strings. You must have:

  • API Key: Available from the Vodacom M-Pesa Developer Portal.
  • Public Key (PEM Format): Vodacom requires your requests to be RSA encrypted using their specific Certificate. You must download this .pem file from the Developer Portal, open it, and pass its contents into the public_key field. Without this, the crypto.py module will throw an error.

2. Safaricom (Kenya) Requirements

To use the Safaricom Daraja STK Push integration:

  • Consumer Key & Secret: Generated by creating an App on the Safaricom Daraja Portal. (Ensure your Sandbox App has the M-Pesa Express scope checked!).
  • Passkey & Shortcode: The Sandbox provides default values for these. For production, these are provided to you by Safaricom when you Go Live.

3. Selcom Gateway Requirements

To use the Selcom integration:

  • Vendor ID: Your registered Selcom Vendor/Till number.
  • API Key & API Secret: Provided by Selcom upon account registration. Selcom uses this to validate the HMAC-SHA256 signature generated by lipa-py on every request.

If you try to run the quickstart using fake keys, the initial Pydantic schema validation will succeed, but the subsequent asynchronous httpx call to the Sandbox/Live URL will be rejected by the Gateway with an Authentication Error HTTP Status Code.

🤝 Contributing

We want to make lipa-py the definitive standard for Tanzanian and East African payments in Python. Pull Requests are always welcome for:

  • Improving test suites and coverage
  • Incorporating missing Gateways / Aggregators (e.g. DPO, Pesapal)
  • Real-world production edge case fixes

Sponsors

Support lipa-py and get your logo here!

📄 License

MIT License

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

lipa_py-0.1.0.tar.gz (14.4 kB view details)

Uploaded Source

Built Distribution

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

lipa_py-0.1.0-py3-none-any.whl (28.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: lipa_py-0.1.0.tar.gz
  • Upload date:
  • Size: 14.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for lipa_py-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e864385fbdec7c1d89442715eb90e661c8daf3df13df22ec501ba95790304355
MD5 73fe2159198c298d46793c35b9c1eee7
BLAKE2b-256 d65f2a9f62a358c1ef4a9edb28c7f4d8eaa08c5049c00a0157650b965000e363

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lipa_py-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 28.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.6 {"installer":{"name":"uv","version":"0.10.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for lipa_py-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f759a0bfe7bfa6ca655575516ed11d26af1c28ef91fd009f7de6dc312df7f9a5
MD5 e1b21f177d7b91ecb584fb7852015fad
BLAKE2b-256 9de0cbf2df6cefdf0873b907dbcb353813858cff87670b247636130c166fb844

See more details on using hashes here.

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