M-Pesa Integration Using Python

Published on December 07, 2025

M-Pesa Integration In Django (Python)

Reusable for Different Python Projects with Minimal Changes

Before We Start: Creating the Django Project

Please ensure you have a Django project already running. If not, run the following:


# Make sure Python & pip are installed

# Create and activate a virtual environment (recommended)
python -m venv myenv
source myenv/bin/activate  # Windows: myenv\Scripts\activate

# Install Django
pip install django

# Create Django project
django-admin startproject myproject
cd myproject

# Create M-Pesa integration app
python manage.py startapp mpesa

# Add "mpesa" to INSTALLED_APPS in settings.py

# Learn more:
# https://docs.djangoproject.com/en/4.2/intro/tutorial01/
                    
Step One: M-Pesa Configuration File

Create mpesa_config.py inside your mpesa app and insert:


from decimal import Decimal
import requests, json, base64
from requests.auth import HTTPBasicAuth
from datetime import datetime
from django.conf import settings

class MpesaC2bCredential:
    consumer_key = 'YOUR_CONSUMER_KEY_HERE'
    consumer_secret = 'YOUR_CONSUMER_SECRET_HERE'
    api_URL = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials'
    call_back_url = 'https://www.yourdomain.com/mpesa-callback/'
    request_api_url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest"

class MpesaAccessToken:
    @staticmethod
    def get_access_token():
        r = requests.get(MpesaC2bCredential.api_URL,
            auth=HTTPBasicAuth(MpesaC2bCredential.consumer_key, MpesaC2bCredential.consumer_secret))
        return json.loads(r.text)["access_token"]

class LipanaMpesaPassword:
    @staticmethod
    def get_password():
        timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
        shortcode = "174379"
        passkey = 'YOUR_PASSKEY_HERE'
        data = shortcode + passkey + timestamp
        password = base64.b64encode(data.encode()).decode('utf-8')
        return password, timestamp, shortcode
                    
Step Two: M-Pesa Utils File

Create mpesa_utils.py inside mpesa app:


import requests
from .mpesa_config import MpesaAccessToken, MpesaC2bCredential, LipanaMpesaPassword

def trigger_stk_push(phone_number, amount):
    if phone_number.startswith('0'):
        phone_number = '254' + phone_number[1:]

    access_token = MpesaAccessToken.get_access_token()
    api_url = MpesaC2bCredential.request_api_url
    headers = {"Authorization": f"Bearer {access_token}"}

    password, timestamp, shortcode = LipanaMpesaPassword.get_password()

    payload = {
        "BusinessShortCode": shortcode,
        "Password": password,
        "Timestamp": timestamp,
        "TransactionType": "CustomerPayBillOnline",
        "Amount": int(float(amount)),
        "PartyA": phone_number,
        "PartyB": shortcode,
        "PhoneNumber": phone_number,
        "CallBackURL": MpesaC2bCredential.call_back_url,
        "AccountReference": "Webinar payment",
        "TransactionDesc": "STK Push"
    }

    response = requests.post(api_url, json=payload, headers=headers)
    data = response.json()
    return data.get('CheckoutRequestID'), data.get('MerchantRequestID')
                    
Step Three: Views Logic — Initiate Payment

Add this in your views.py:


from django.shortcuts import redirect, render
from django.contrib import messages
from django.http import JsonResponse
from mpesa.models import PaymentTransaction
from django.utils.timezone import make_aware
from .mpesa_utils import trigger_stk_push
from datetime import datetime
import json

def payment_page(request):
    return render(request, "payment_page.html")

def initiate_mpesa_payment(request):
    if request.method == 'POST':
        phone_number = request.POST.get('phone_number')
        amount = 100

        checkout_id, merchant_id = trigger_stk_push(phone_number, amount)

        PaymentTransaction.objects.create(
            amount=amount,
            phone_number=phone_number,
            checkout_request_id=checkout_id,
            merchant_request_id=merchant_id,
            is_complete=False
        )

        messages.success(request, "STK push sent! Check your phone.")
        return redirect("payment_page")

    return redirect("payment_page")
                    
Step Four: Callback Logic

def mpesa_callback(request):
    data = json.loads(request.body)
    callback = data['Body']['stkCallback']
    checkout_id = callback['CheckoutRequestID']
    result_code = callback['ResultCode']

    tx = PaymentTransaction.objects.get(checkout_request_id=checkout_id)

    if result_code != 0:
        tx.is_complete = False
        tx.save()
        return JsonResponse({"error": "Failed"})

    metadata = callback['CallbackMetadata']['Item']
    tx.is_complete = True
    tx.save()
    return JsonResponse({"success": "OK"})
                    
Step Five: URL Endpoints

path("pay/", views.payment_page, name="payment_page"),
path("initiate-payment/", views.initiate_mpesa_payment, name="initiate_payment"),
path("mpesa-callback/", views.mpesa_callback, name="mpesa_callback"),
                    

🎯 You have successfully integrated M-Pesa STK Push into Django!

Test thoroughly in Safaricom Sandbox before going live. If stuck, reach out. I’m happy to help!