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_urlis 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
Release history Release notifications | RSS feed
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f4618a29c7736530fdf9c0b54560a0fadca6c1f72356861ecd57b619911a6582
|
|
| MD5 |
54727253bc4897380b5e7c8a735953d4
|
|
| BLAKE2b-256 |
03a83027bafe4e52a66245f6a6d52cb78c6a42ee7691a8a3af62ad86ada19f97
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92eec9c03771ada94f2093ec90313e53f8ec25110fe9d4cd5d86b201d02ef23b
|
|
| MD5 |
be475cc21a39a604884baff12fdf530e
|
|
| BLAKE2b-256 |
18f6bcc7430f9b031107fb3d2752de4b6c88e735b362a19fbac85b9bf1f6e150
|