Django Mpesa Integration
Tags: webdev, javascript, beginners, programming
Published: 2024-04-05 18:42:10

To demonstrate M-Pesa integration in a Django project for selling a product, we will create a simple e-commerce application where users can purchase a product using M-Pesa. Here's a step-by-step guide:

1. **Setup Django Project:**
   First, create a Django project and an app within it. Let's name the project `test_mpesa` and the app `product`.

   ```bash
   django-admin startproject test_mpesa
   cd test_mpesa
   python manage.py startapp product
   ```

2. **Define Models:**
   In the `shop` app, define models for the product and transactions.

   ```python
   # shop/models.py
   from django.db import models

   class Product(models.Model):
       name = models.CharField(max_length=100)
       price = models.DecimalField(max_digits=10, decimal_places=2)
       quantity = models.PositiveIntegerField()

       def __str__(self):
           return self.name

   class Transaction(models.Model):
       product = models.ForeignKey(Product, on_delete=models.CASCADE)
       phone_number = models.CharField(max_length=15)
       amount = models.DecimalField(max_digits=10, decimal_places=2)
       checkout_request_id = models.CharField(max_length=100)
       status = models.CharField(max_length=20, default='PENDING')
       transaction_code = models.CharField(max_length=100, blank=True, null=True)

       def __str__(self):
           return f"Transaction for {self.product.name}"
   ```

3. **URLs Configuration:**
   Configure URLs to handle views.

   ```python
   # ecommerce_project/urls.py
   from django.contrib import admin
   from django.urls import path, include

   urlpatterns = [
       path('admin/', admin.site.urls),
       path('', include('product.urls')),
   ]
   ```

4. **Views for Subscription and Callback:**
   Implement views for subscribing to a product and handling M-Pesa callback.

   ```python
   # shop/views.py
   from django.shortcuts import render, redirect, reverse, get_object_or_404
   from django.http import HttpResponse
   from django.views.decorators.csrf import csrf_exempt
   from .models import Product, Transaction
   from django_daraja.mpesa.core import MpesaClient
   import json

   def subscribe(request, product_id):
       product = get_object_or_404(Product, pk=product_id)
       if request.method == 'POST':
           phone_number = request.POST.get('phone_number')

           amount = product.price
           account_reference = 'Product Payment'
           transaction_desc = f'Purchase of {product.name}'
           callback_url = request.build_absolute_uri(reverse('mpesa_callback')).replace('http://', 'https://')

           mpesa_client = MpesaClient()

           try:
               response = mpesa_client.stk_push(
                   phone_number=phone_number,
                   amount=amount,
                   account_reference=account_reference,
                   transaction_desc=transaction_desc,
                   callback_url=callback_url
               )

               transaction = Transaction.objects.create(
                   product=product,
                   phone_number=phone_number,
                   amount=amount,
                   checkout_request_id=response.checkout_request_id,
               )

               return render(request, 'shop/subscribe_success.html', {'transaction': transaction})
           except Exception as e:
               return render(request, 'shop/subscribe.html', {'error': str(e)})

       return render(request, 'shop/subscribe.html', {'product': product})

   @csrf_exempt
   def mpesa_callback(request):
       if request.method == 'POST':
           data = json.loads(request.body.decode('utf-8'))

           stk_callback = data.get('Body', {}).get('stkCallback', {})
           checkout_request_id = stk_callback.get('CheckoutRequestID')
           result_code = stk_callback.get('ResultCode')

           transaction = Transaction.objects.get(checkout_request_id=checkout_request_id)

           if result_code == 0:
               transaction.status = 'COMPLETED'
               transaction.transaction_code = stk_callback.get('CallbackMetadata')['Item'][1]['Value']
           else:
               transaction.status = 'FAILED'
               
           transaction.save()
           return HttpResponse('Success', content_type='text/plain')
       else:
           return HttpResponse('Method Not Allowed', status=405)
   ```

5. **Templates:**
   Create HTML templates for subscription and success pages.

6. **URLs Configuration:**
   Configure URLs to map views.

   ```python
   # shop/urls.py
   from django.urls import path
   from . import views

   urlpatterns = [
       path('subscribe/<int:product_id>/', views.subscribe, name='subscribe'),
       path('mpesa/callback/', views.mpesa_callback, name='mpesa_callback'),
   ]
   ```

7. **Integrate with M-Pesa:**
   Finally, integrate with M-Pesa by installing the required package (`django-daraja`).

   ```bash
   pip install django-daraja
   ```