Skip to content

hellen-22/python-mpesa

Repository files navigation

M-Pesa Python Integration Library

A simple Python library for integrating M-Pesa STK Push (Lipa Na M-Pesa Online) payments into your applications.

Features

  • 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

Installation

pip install python-mpesa-daraja

Requirements

  • Python 3.8+
  • requests
  • phonenumbers

Quick Start

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)

Configuration

Sandbox Environment

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"

Production Environment

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"

API Reference

MpesaGateway

The main class for interacting with the M-Pesa API.

Initialization

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 API
  • consumer_secret (str): Consumer secret from Daraja API
  • pass_key (str): Lipa Na M-Pesa Online passkey
  • access_token_url (str): OAuth token endpoint URL
  • stk_push_url (str): STK Push API endpoint URL

Methods

stk_push()

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
) -> dict

Parameters:

  • phone_number (str): Customer's phone number
  • amount (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 Till
  • transaction_desc (str): Transaction description
  • callback_url (str): URL to receive payment notifications

Returns:

  • dict: Response from M-Pesa API containing:
    • MerchantRequestID: Unique request ID
    • CheckoutRequestID: Unique checkout ID
    • ResponseCode: Response code
    • ResponseDescription: Response message
    • CustomerMessage: 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"
)

Phone Number Validation

The library automatically validates and formats phone numbers for Kenyan mobile networks.

Supported Formats

# All these formats are valid and will be converted to 254712345678
"0712345678"      # Local format
"254712345678"    # International without +
"+254712345678"   # International with +
"712345678"       # Without leading zero

Validation Rules

  • Must be a valid Kenyan phone number
  • Automatically formatted to E.164 standard (without the + prefix)

Amount Validation

Amounts are validated against M-Pesa transaction limits.

Rules

  • 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, ...)   # ✗ InvalidAmountError

Error Handling

The library provides comprehensive error handling with custom exceptions.

Exception Types

InvalidPhoneNumberError

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}")

InvalidAmountError

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}")

MpesaConnectionError

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 here

PaymentError

Raised 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}")

Complete Error Handling Example

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}")

Token Management

The library automatically handles OAuth token generation and refresh.

Features

  • Automatic token generation on initialization
  • Auto-refresh before expiration (tokens last ~3400 seconds)
  • Transparent to the developer

Manual Token Refresh

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 expired

Callback Handling

When a payment is processed, M-Pesa sends a callback to your specified URL.

Example Callback Response

{
  "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
          }
        ]
      }
    }
  }
}

Flask Example

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"})

Django Example

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"})

Advanced Usage

Environment Variables

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")
)

Logging

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)}")

Testing

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"

Best Practices

  1. Use Environment Variables: Never hardcode credentials
  2. Implement Retry Logic: Handle connection errors gracefully
  3. Validate Callbacks: Verify callback authenticity using IP whitelisting
  4. Store Transaction Records: Log all CheckoutRequestIDs for reconciliation
  5. Handle Timeouts: Customer has 60 seconds to complete payment
  6. Test Thoroughly: Use sandbox environment before going live

Common Issues

Issue: "Invalid Access Token"

Solution: Check that your consumer key and secret are correct.

Issue: "Invalid Shortcode"

Solution: Ensure the shortcode matches your Paybill/Till number.

Issue: "Unable to lock subscriber"

Solution: Customer's phone is busy with another transaction. Retry after a few seconds.

Issue: "Timeout"

Solution: Customer didn't complete payment within 60 seconds. This is normal behavior.

Response Codes

Code Description
0 Success
1 Insufficient Balance
1032 Request cancelled by user
1037 Timeout (user didn't enter PIN)
2001 Invalid initiator information

Support & Resources

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details

Changelog

Version 1.0.0

  • 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.

About

A Python SDK for integrating Safaricom M-Pesa services into Python applications.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages