Skip to main content

Python SDK for the LMBTech payment gateway. Supports MTN MoMo, Airtel Money, and card payments in Rwanda.

Project description

lmbtech

Python SDK for the LMBTech payment gateway. Supports MTN MoMo, Airtel Money, and Card payments in Rwanda.

Built and maintained by ATAS — Alliance for Transformative AI Systems.


Why this SDK?

LMBTech's official documentation is minimal and inconsistent. This SDK abstracts all the undocumented behavior discovered through live testing:

  • The collect endpoint regularly times out but the payment is initiated
  • Cancellations take 12-15 minutes to appear as failed — not seconds
  • callback_url is required by the API validator but never called
  • Reference IDs are permanently consumed even on payment failure
  • The minimum payment amount is 100 RWF

The SDK handles all of this transparently so you never have to.


Installation

pip install lmbtech

For Django integration:

pip install lmbtech[django]

Requirements: Python 3.8+, requests>=2.28.0


Quick Start

from lmbtech import LMBTechClient
from lmbtech.momo import generate_reference_id

client = LMBTechClient(
    app_key="your_app_key",
    secret_key="your_secret_key"
)

# Initiate a MoMo payment
result = client.momo.collect(
    phone="0788123456",
    amount=1000,
    reference_id=generate_reference_id("ORD"),
    email="customer@example.com",
    name="Jane Uwimana",
    callback_url="https://yourapp.rw/payments/",
    service_paid="Subscription"
)

if result.success:
    print("USSD prompt sent to customer's phone")
    print("Reference:", result.reference_id)
else:
    print("Failed:", result.error_message)

Fee Handling

LMBTech charges a 5% fee on every transaction. The SDK gives you two options:

from lmbtech.momo import calculate_total_with_fee, calculate_amount_to_send

# Option 1 (default) — customer pays amount + 5% fee
# You request 1000 RWF → customer pays 1050 RWF
result = client.momo.collect(amount=1000, ...)

# Show customer what they will pay before initiating:
info = calculate_total_with_fee(1000)
print(f"Customer pays: {info['total_amount']} RWF")

# Option 2 — customer pays exactly the amount you specify
# You want customer to pay 1000 RWF → SDK sends 952 RWF → customer pays 1000 RWF
result = client.momo.collect(amount=1000, absorb_fee=True, ...)

# Calculate what you will receive:
info = calculate_amount_to_send(1000)
print(f"You receive: {info['amount_to_send']} RWF")

Check Payment Status

status = client.momo.status("ORD-20260315-A1B2C3D4")

if status.is_paid:
    fulfill_order(status.reference_id)
elif status.is_failed:
    notify_customer_failed(status.reference_id)
elif status.is_pending:
    # Still waiting — poll again later
    pass

Card Payments

result = client.card.initiate(
    phone="0788123456",
    amount=5000,
    reference_id=generate_reference_id("ORD"),
    email="customer@example.com",
    name="Jane Uwimana",
    card_redirect_url="https://yourapp.rw/payment/complete",
    service_paid="Subscription"
)

if result.success:
    # Send redirect_url to your frontend
    # React: window.location.href = result.redirect_url
    print("Redirect URL:", result.redirect_url)

Django Integration

1. Add your payment model

# myapp/models.py
from django.db import models
from lmbtech.django.mixins import LMBTechPaymentMixin

class Payment(LMBTechPaymentMixin):
    # All SDK fields inherited — add your own below
    user = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )

2. Configure in AppConfig

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        from django.conf import settings
        from lmbtech.django import LMBTechConfig
        from myapp.handlers import on_success, on_failed, on_expired

        LMBTechConfig.setup(
            app_key=settings.LMBTECH_APP_KEY,
            secret_key=settings.LMBTECH_SECRET_KEY,
            payment_model="myapp.Payment",
            on_payment_success=on_success,
            on_payment_failed=on_failed,
            on_payment_expired=on_expired,
        )

3. Define your handlers

# myapp/handlers.py
from lmbtech.django.results import (
    PaymentSuccessResult,
    PaymentFailedResult,
    PaymentExpiredResult,
)

def on_success(result: PaymentSuccessResult) -> None:
    # Fulfill the order — allocate credits, activate subscription, etc.
    order = Order.objects.get(reference_id=result.reference_id)
    order.fulfill(transaction_id=result.transaction_id)

def on_failed(result: PaymentFailedResult) -> None:
    # Notify the customer
    pass

def on_expired(result: PaymentExpiredResult) -> None:
    # Payment could not be confirmed after 20 minutes
    pass

4. Add Celery tasks

# myapp/tasks.py
from celery import shared_task
from lmbtech.django.tasks import (
    poll_payment_task_logic,
    background_poll_task_logic,
)

@shared_task(bind=True, max_retries=0, soft_time_limit=55, time_limit=60)
def poll_lmbtech_payment(self, reference_id: str) -> None:
    poll_payment_task_logic(
        reference_id=reference_id,
        schedule_active=lambda ref, cd: (
            poll_lmbtech_payment.apply_async(args=[ref], countdown=cd)
        ),
        schedule_background=lambda ref, cd: (
            background_lmbtech_payment.apply_async(args=[ref], countdown=cd)
        ),
    )

@shared_task(bind=True, max_retries=0, soft_time_limit=55, time_limit=60)
def background_lmbtech_payment(self, reference_id: str) -> None:
    background_poll_task_logic(
        reference_id=reference_id,
        schedule_task=lambda ref, cd: (
            background_lmbtech_payment.apply_async(args=[ref], countdown=cd)
        ),
    )

Payment Lifecycle

The SDK uses a two-phase polling design based on confirmed LMBTech behavior:

Phase 1 — Active (0 to 5 minutes)
    Poll every 15 seconds while user is on payment page.
    Catches approvals (~30 seconds typical).
    After 5 minutes → transition to Phase 2.

Phase 2 — Background (5 to 20 minutes)
    Poll every 2 minutes after user leaves page.
    Catches cancellations (~12-15 minutes typical).
    After 20 minutes → expire permanently.

Payment status flow:

pending → success    Payment approved by customer
pending → failed     Payment rejected (rare in first 5 min)
pending → timeout    Active window ended, background running
timeout → failed     Cancellation confirmed by LMBTech (~12 min)
timeout → success    Rare — approval during background phase
timeout → expired    20 minutes passed, no confirmation

Error Handling

from lmbtech import (
    LMBTechError,          # base — catch all SDK errors
    LMBTechValidationError, # bad input — fix your code
    LMBTechNetworkError,    # could not reach LMBTech — check status before failing
    LMBTechAPIError,        # LMBTech returned an error response
)

try:
    result = client.momo.collect(...)
except LMBTechValidationError as e:
    # Fix your input — do not retry
    print("Input error:", e)
except LMBTechNetworkError as e:
    # IMPORTANT: payment may have been initiated before the error
    # Check status before marking as failed
    status = client.momo.status(reference_id)
except LMBTechAPIError as e:
    print(f"API error {e.status_code}:", e)
    print("Response:", e.raw_response)

Phone Number Formats

The SDK accepts any Rwandan phone format and normalizes automatically:

from lmbtech.momo import normalize_phone, detect_network

normalize_phone("0788123456")    # → "+250788123456"
normalize_phone("+250788123456") # → "+250788123456"
normalize_phone("250788123456")  # → "+250788123456"

detect_network("0788123456")  # → "MTN"
detect_network("0725000000")  # → "Airtel"

Valid prefixes:

  • MTN Rwanda: 078, 079
  • Airtel Rwanda: 072, 073

Reference ID Format

from lmbtech.momo import generate_reference_id

ref = generate_reference_id("ORD")  # → "ORD-20260315-A1B2C3D4"
ref = generate_reference_id("PAY")  # → "PAY-20260315-F2E1D3C4"

Important: Reference IDs are permanently consumed by LMBTech even if the payment fails. Never reuse a reference ID.


Requirements

  • Python 3.8+
  • requests >= 2.28.0
  • Django 3.2+ (only for lmbtech.django)
  • Celery + Redis (only for background polling)

License

MIT License — see LICENSE

Author: IRANKUNDA Elyssa / ATAS — Alliance for Transformative AI Systems

Repository: https://github.com/ielyssa/lmbtech-payment-gateway

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

lmbtech-1.0.0.tar.gz (55.2 kB view details)

Uploaded Source

Built Distribution

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

lmbtech-1.0.0-py3-none-any.whl (50.7 kB view details)

Uploaded Python 3

File details

Details for the file lmbtech-1.0.0.tar.gz.

File metadata

  • Download URL: lmbtech-1.0.0.tar.gz
  • Upload date:
  • Size: 55.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for lmbtech-1.0.0.tar.gz
Algorithm Hash digest
SHA256 f4618a29c7736530fdf9c0b54560a0fadca6c1f72356861ecd57b619911a6582
MD5 54727253bc4897380b5e7c8a735953d4
BLAKE2b-256 03a83027bafe4e52a66245f6a6d52cb78c6a42ee7691a8a3af62ad86ada19f97

See more details on using hashes here.

File details

Details for the file lmbtech-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: lmbtech-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 50.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for lmbtech-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 92eec9c03771ada94f2093ec90313e53f8ec25110fe9d4cd5d86b201d02ef23b
MD5 be475cc21a39a604884baff12fdf530e
BLAKE2b-256 18f6bcc7430f9b031107fb3d2752de4b6c88e735b362a19fbac85b9bf1f6e150

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