A simple Python library for integrating M-Pesa STK Push (Lipa Na M-Pesa Online) payments into your applications.
- Easy STK Push integration
- Automatic phone number validation and formatting
- Secure token management with auto-refresh
- Amount validation with M-Pesa limits
- Comprehensive error handling
- Kenyan phone number support
pip install python-mpesa-daraja- Python 3.8+
- requests
- phonenumbers
from python_mpesa import MpesaGateway
# Initialize the gateway
mpesa = MpesaGateway(
business_shortcode="your_business_shortcode",
consumer_key="your_consumer_key",
consumer_secret="your_consumer_secret",
pass_key="your_pass_key",
access_token_url="https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials", # Use production URL in live environment
stk_push_url="https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest" # Use production URL in live environment
)
# Initiate STK Push
response = mpesa.stk_push(
phone_number="0712345678",
amount=100,
account_reference="account_reference", # e.g., invoice number
transaction_type="CustomerPayBillOnline", # or "CustomerBuyGoodsOnline" for Till
transaction_desc="Payment description", # e.g., "Payment for Order #12345"
callback_url="your_callback_url" # e.g., "https://yourdomain.com/mpesa/callback"
)
print(response)ACCESS_TOKEN_URL = "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"
STK_PUSH_URL = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"ACCESS_TOKEN_URL = "https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials"
STK_PUSH_URL = "https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest"The main class for interacting with the M-Pesa API.
MpesaGateway(
business_shortcode: str,
consumer_key: str,
consumer_secret: str,
pass_key: str,
access_token_url: str,
stk_push_url: str
)Parameters:
business_shortcode(str): Your M-Pesa business shortcode (Paybill or Till number)consumer_key(str): Consumer key from Daraja APIconsumer_secret(str): Consumer secret from Daraja APIpass_key(str): Lipa Na M-Pesa Online passkeyaccess_token_url(str): OAuth token endpoint URLstk_push_url(str): STK Push API endpoint URL
Initiates an STK Push payment request.
stk_push(
phone_number: str,
amount: int,
account_reference: str,
transaction_type: str,
transaction_desc: str,
callback_url: str
) -> dictParameters:
phone_number(str): Customer's phone numberamount(int): Amount to charge (1 - 250,000 KES)account_reference(str): Account reference (e.g., invoice number, order ID)transaction_type(str): Usually"CustomerPayBillOnline"for Paybill or"CustomerBuyGoodsOnline"for Tilltransaction_desc(str): Transaction descriptioncallback_url(str): URL to receive payment notifications
Returns:
dict: Response from M-Pesa API containing:MerchantRequestID: Unique request IDCheckoutRequestID: Unique checkout IDResponseCode: Response codeResponseDescription: Response messageCustomerMessage: Message to display to customer
Example:
response = mpesa.stk_push(
phone_number="0712345678",
amount=1500.50,
account_reference="ORDER-12345",
transaction_type="CustomerPayBillOnline",
transaction_desc="Payment for Order 12345",
callback_url="https://yourdomain.com/mpesa/callback"
)The library automatically validates and formats phone numbers for Kenyan mobile networks.
# All these formats are valid and will be converted to 254712345678
"0712345678" # Local format
"254712345678" # International without +
"+254712345678" # International with +
"712345678" # Without leading zero- Must be a valid Kenyan phone number
- Automatically formatted to E.164 standard (without the + prefix)
Amounts are validated against M-Pesa transaction limits.
- Must be a positive number
- Minimum: 1 KES
- Maximum: 250,000 KES
- Decimal values are converted to integers by discarding the fractional part (e.g., 1500.75 becomes 1500)
# Valid amounts
mpesa.stk_push(..., amount=100, ...) # ✓
mpesa.stk_push(..., amount=1500.75, ...) # ✓ Converted to 1500
mpesa.stk_push(..., amount=250000, ...) # ✓
# Invalid amounts
mpesa.stk_push(..., amount=0, ...) # ✗ InvalidAmountError
mpesa.stk_push(..., amount=-100, ...) # ✗ InvalidAmountError
mpesa.stk_push(..., amount=300000, ...) # ✗ InvalidAmountErrorThe library provides comprehensive error handling with custom exceptions.
Raised when the phone number is invalid.
from python_mpesa.exceptions import InvalidPhoneNumberError
try:
response = mpesa.stk_push(
phone_number="1234", # Invalid
amount=100,
...
)
except InvalidPhoneNumberError as e:
print(f"Invalid phone number: {e}")Raised when the amount is invalid.
from python_mpesa.exceptions import InvalidAmountError
try:
response = mpesa.stk_push(
phone_number="0712345678",
amount=-100, # Invalid
...
)
except InvalidAmountError as e:
print(f"Invalid amount: {e}")Raised when there's a connection error to M-Pesa servers.
from python_mpesa.exceptions import MpesaConnectionError
try:
response = mpesa.stk_push(...)
except MpesaConnectionError as e:
print(f"Connection error: {e}")
# Retry logic hereRaised for general payment processing errors.
from python_mpesa.exceptions import PaymentError
try:
response = mpesa.stk_push(...)
except PaymentError as e:
print(f"Payment error: {e}")from python_mpesa import MpesaGateway
from python_mpesa.exceptions import (
InvalidPhoneNumberError,
InvalidAmountError,
MpesaConnectionError,
PaymentError
)
mpesa = MpesaGateway(...)
try:
response = mpesa.stk_push(
phone_number="0712345678",
amount=1000,
account_reference="INV-001",
transaction_type="CustomerPayBillOnline",
transaction_desc="Payment",
callback_url="https://yourdomain.com/callback"
)
print(f"Success! Checkout ID: {response['CheckoutRequestID']}")
except InvalidPhoneNumberError as e:
print(f"Invalid phone number: {e}")
except InvalidAmountError as e:
print(f"Invalid amount: {e}")
except MpesaConnectionError as e:
print(f"Connection error: {e}")
# Implement retry logic
except PaymentError as e:
print(f"Payment failed: {e}")The library automatically handles OAuth token generation and refresh.
- Automatic token generation on initialization
- Auto-refresh before expiration (tokens last ~3400 seconds)
- Transparent to the developer
While automatic refresh is built-in, you can also manually refresh:
# Token is automatically refreshed when needed
# No manual intervention required
mpesa.stk_push(...) # Token refreshed if expiredWhen a payment is processed, M-Pesa sends a callback to your specified URL.
{
"Body": {
"stkCallback": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResultCode": 0,
"ResultDesc": "The service request is processed successfully.",
"CallbackMetadata": {
"Item": [
{
"Name": "Amount",
"Value": 1.00
},
{
"Name": "MpesaReceiptNumber",
"Value": "NLJ7RT61SV"
},
{
"Name": "TransactionDate",
"Value": 20191219102115
},
{
"Name": "PhoneNumber",
"Value": 254712345678
}
]
}
}
}
}from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/mpesa/callback', methods=['POST'])
def mpesa_callback():
data = request.get_json()
callback = data['Body']['stkCallback']
result_code = callback['ResultCode']
if result_code == 0:
# Payment successful
metadata = callback['CallbackMetadata']['Item']
receipt = next(item['Value'] for item in metadata if item['Name'] == 'MpesaReceiptNumber')
print(f"Payment successful! Receipt: {receipt}")
else:
# Payment failed
print(f"Payment failed: {callback['ResultDesc']}")
return jsonify({"ResultCode": 0, "ResultDesc": "Success"})from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import json
@csrf_exempt
def mpesa_callback(request):
if request.method == 'POST':
data = json.loads(request.body)
callback = data['Body']['stkCallback']
result_code = callback['ResultCode']
if result_code == 0:
# Payment successful
metadata = callback['CallbackMetadata']['Item']
receipt = next(item['Value'] for item in metadata if item['Name'] == 'MpesaReceiptNumber')
# Save to database
return JsonResponse({"ResultCode": 0, "ResultDesc": "Success"})Store sensitive credentials in environment variables:
import os
from python_mpesa import MpesaGateway
mpesa = MpesaGateway(
business_shortcode=os.getenv("MPESA_SHORTCODE"),
consumer_key=os.getenv("MPESA_CONSUMER_KEY"),
consumer_secret=os.getenv("MPESA_CONSUMER_SECRET"),
pass_key=os.getenv("MPESA_PASS_KEY"),
access_token_url=os.getenv("MPESA_TOKEN_URL"),
stk_push_url=os.getenv("MPESA_STK_PUSH_URL")
)Add logging for debugging:
import logging
from python_mpesa import MpesaGateway
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
mpesa = MpesaGateway(...)
try:
response = mpesa.stk_push(...)
logger.info(f"STK Push initiated: {response['CheckoutRequestID']}")
except Exception as e:
logger.error(f"STK Push failed: {str(e)}")For testing, use M-Pesa sandbox credentials:
# Sandbox test credentials
BUSINESS_SHORTCODE = "174379"
PASS_KEY = "bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919"
# Test phone numbers (use actual Safaricom numbers registered in sandbox)
TEST_PHONE = "254712345678"- Use Environment Variables: Never hardcode credentials
- Implement Retry Logic: Handle connection errors gracefully
- Validate Callbacks: Verify callback authenticity using IP whitelisting
- Store Transaction Records: Log all CheckoutRequestIDs for reconciliation
- Handle Timeouts: Customer has 60 seconds to complete payment
- Test Thoroughly: Use sandbox environment before going live
Solution: Check that your consumer key and secret are correct.
Solution: Ensure the shortcode matches your Paybill/Till number.
Solution: Customer's phone is busy with another transaction. Retry after a few seconds.
Solution: Customer didn't complete payment within 60 seconds. This is normal behavior.
| Code | Description |
|---|---|
| 0 | Success |
| 1 | Insufficient Balance |
| 1032 | Request cancelled by user |
| 1037 | Timeout (user didn't enter PIN) |
| 2001 | Invalid initiator information |
- M-Pesa Daraja API: https://developer.safaricom.co.ke/
- API Documentation: https://developer.safaricom.co.ke/dashboard/apis?api=MpesaExpressSimulate
- Sandbox Portal: https://developer.safaricom.co.ke/dashboard/myapps
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details
- Initial release
- STK Push integration
- Phone number validation
- Amount validation
- Automatic token refresh
- Comprehensive error handling
Made with ❤️ for Developers integrating M-Pesa payments in Python applications.