Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
'django.contrib.staticfiles',

'crispy_forms',
'widget_tweaks',
]

MIDDLEWARE = [
Expand Down Expand Up @@ -140,3 +141,8 @@

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static'),]
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

#M-PESA PAYMENT CONFIGURATIONS
PUBLIC_KEY ='MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArv9yxA69XQKBo24BaF/D+fvlqmGdYjqLQ5WtNBb5tquqGvAvG3WMFETVUSow/LizQalxj2ElMVrUmzu5mGGkxK08bWEXF7a1DEvtVJs6nppIlFJc2SnrU14AOrIrB28ogm58JjAl5BOQawOXD5dfSk7MaAA82pVHoIqEu0FxA8BOKU+RGTihRU+ptw1j4bsAJYiPbSX6i71gfPvwHPYamM0bfI4CmlsUUR3KvCG24rB6FNPcRBhM3jDuv8ae2kC33w9hEq8qNB55uw51vK7hyXoAa+U7IqP1y6nBdlN25gkxEA8yrsl1678cspeXr+3ciRyqoRgj9RD/ONbJhhxFvt1cLBh+qwK2eqISfBb06eRnNeC71oBokDm3zyCnkOtMDGl7IvnMfZfEPFCfg5QgJVk1msPpRvQxmEsrX9MQRyFVzgy2CWNIb7c+jPapyrNwoUbANlN8adU1m6yOuoX7F49x+OjiG2se0EJ6nafeKUXw/+hiJZvELUYgzKUtMAZVTNZfT8jjb58j8GVtuS+6TM2AutbejaCV84ZK58E2CRJqhmjQibEUO6KPdD7oTlEkFy52Y1uOOBXgYpqMzufNPmfdqqqSM4dU70PO8ogyKGiLAIxCetMjjm6FCMEA3Kc8K0Ig7/XtFm9By6VxTJK1Mg36TlHaZKP6VzVLXMtesJECAwEAAQ=='

API_KEY = 'e0dlIJMW6qrit7c9XpP1olfhqgHQ05sw'
1 change: 1 addition & 0 deletions membership/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'membership.apps.MembershipConfig'
4 changes: 4 additions & 0 deletions membership/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _ #added


class MembershipConfig(AppConfig):
name = 'membership'

def ready(self):
import membership.signals #added
11 changes: 10 additions & 1 deletion membership/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from django import forms
from .models import Payment

Expand All @@ -6,4 +7,12 @@ class PaymentForm(forms.ModelForm):

class Meta:
model = Payment
fields = ['phone']
fields = ['phone']

def clean_phone(self):
phone = self.cleaned_data.get('phone')
x = re.search("^2557[0-9]{8}$", phone)

if not x:
raise forms.ValidationError("Phone number must in format 2557xxxxxxxx")
return phone
18 changes: 18 additions & 0 deletions membership/migrations/0003_auto_20201015_2212.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-10-15 19:12

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('membership', '0002_business_user'),
]

operations = [
migrations.AlterField(
model_name='payment',
name='phone',
field=models.CharField(max_length=12, verbose_name='Enter your M-PESA mobile number'),
),
]
18 changes: 18 additions & 0 deletions membership/migrations/0004_auto_20201015_2227.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.1 on 2020-10-15 19:27

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('membership', '0003_auto_20201015_2212'),
]

operations = [
migrations.AlterField(
model_name='payment',
name='phone',
field=models.CharField(help_text='example. 255700000000', max_length=12, verbose_name='Enter your M-PESA mobile number'),
),
]
25 changes: 25 additions & 0 deletions membership/migrations/0005_auto_20201015_2239.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Generated by Django 3.1 on 2020-10-15 19:39

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

dependencies = [
('membership', '0004_auto_20201015_2227'),
]

operations = [
migrations.AddField(
model_name='business',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='business',
name='modified_at',
field=models.DateTimeField(auto_now=True),
),
]
19 changes: 19 additions & 0 deletions membership/migrations/0006_auto_20201015_2336.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 3.1 on 2020-10-15 20:36

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('membership', '0005_auto_20201015_2239'),
]

operations = [
migrations.AlterField(
model_name='subscription',
name='plan',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='membership.plan'),
),
]
8 changes: 5 additions & 3 deletions membership/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@


class Business(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
user = models.OneToOneField(User, on_delete=models.PROTECT)
name = models.CharField(max_length=100)
location = models.CharField(max_length=100)
reference_no = models.CharField(max_length=150, blank=True, null=True) #payment gateway

def __str__(self):
return self.name
return self.user.email


class Plan(models.Model):
Expand All @@ -32,7 +34,7 @@ def __str__(self):
class Subscription(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
plan = models.OneToOneField(Plan, on_delete=models.PROTECT)
plan = models.ForeignKey(Plan, on_delete=models.PROTECT)
business = models.OneToOneField(Business, on_delete=models.PROTECT)
start_time = models.DateTimeField()
ends_time = models.DateTimeField()
Expand All @@ -55,7 +57,7 @@ class Payment(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
modified_at = models.DateTimeField(auto_now=True)
subscription = models.OneToOneField(Subscription, on_delete=models.PROTECT)
phone = models.CharField(max_length=12)
phone = models.CharField(max_length=12, verbose_name='Enter your M-PESA mobile number', help_text='example. 255700000000')
transactionID = models.CharField(max_length=100, blank=True, null=True)
conversationID = models.CharField(max_length=100, blank=True, null=True)

Expand Down
12 changes: 12 additions & 0 deletions membership/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Business


@receiver(post_save, sender=Business)
def business_reference_no(sender, instance, created, **kwargs):
if created:
reference_no = str(instance.id) + str(instance.created_at.hour) + str(instance.created_at.minute) + str(instance.created_at.year)[-2:]

Business.objects.filter(pk=instance.pk).update(reference_no=reference_no)
print("reference_no:", reference_no)
18 changes: 15 additions & 3 deletions membership/templates/membership/payment.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% load widget_tweaks %}

{% block content %}
<div class="container">
Expand All @@ -17,10 +17,22 @@
{% endif %}
<div class="card">
<div class="card-body text-center">
<form id="paymentForm" action="." method="POST" autocomplete="off">
<form action="." method="POST" autocomplete="off">
{% csrf_token %}

{{form|crispy}}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}

{% for field in form.visible_fields %}
<div class="form-group">
<label class="mb-2" for="{{ field.id_for_label }}"><h5>{{ field.label }}</h5></label>
{{form.phone|add_class:'form-control'|attr:'placeholder:Phone (eg. 255700000000)'}}
{% for error in field.errors %}
<small class="help-block text-danger">{{ error }}</small>
{% endfor %}
</div>
{% endfor %}

<div class="mb-3 mt-3 form-group">
<button class="btn btn-danger btn-block btn-lg" id="make-payment">Pay Tshs. {{plans.price|floatformat:2}}/=</button>
Expand Down
135 changes: 128 additions & 7 deletions membership/views.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import datetime
from django.utils import timezone
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views.generic import TemplateView, ListView
from django.contrib import messages
from .models import Plan, Subscription
from .models import Plan, Subscription, Business
from .forms import PaymentForm

#Vodacom mpesa intergrations
from django.conf import settings
from portalsdk import APIContext, APIMethodType, APIRequest
from time import sleep

from datetime import datetime, timedelta, date


def get_user_plan(request):
user_plan_qs = Subscription.objects.filter(business=request.user.business)
Expand Down Expand Up @@ -61,21 +70,133 @@ def post(self, request, *args, **kwargs):
def paymentView(request):

selected_plan = get_selected_plan(request)

plans = Plan.objects.get(name=selected_plan)


business = Business.objects.get(user=request.user)
reference_no = business.reference_no
print('reference_no:', reference_no)

if request.method == 'POST':
form = PaymentForm(request.POST)
if form.is_valid():
form.save()

return HttpResponse('Payment successfully')
#Begin payment processing
public_key = settings.PUBLIC_KEY

# Create Context with API to request a Session ID
api_context = APIContext()

# Api key
api_context.api_key = settings.API_KEY

# Public key
api_context.public_key = public_key

# Use ssl/https
api_context.ssl = True

# Method type (can be GET/POST/PUT)
api_context.method_type = APIMethodType.GET

# API address
api_context.address = 'openapi.m-pesa.com'

# API Port
api_context.port = 443

# API Path
api_context.path = '/sandbox/ipg/v2/vodacomTZN/getSession/'

# Add/update headers
api_context.add_header('Origin', '*')

# Parameters can be added to the call as well that on POST will be in JSON format and on GET will be URL parameters
# api_context.add_parameter('key', 'value')

#Do the API call and put result in a response packet
api_request = APIRequest(api_context)

# Do the API call and put result in a response packet
result = None
try:
result = api_request.execute()
except Exception as e:
print('Call Failed: ' + e)

if result is None:
raise Exception('SessionKey call failed to get result. Please check.')

# Display results
print(result.status_code)
print(result.headers)
print(result.body)

# The above call issued a sessionID
api_context = APIContext()
api_context.api_key = result.body['output_SessionID']
api_context.public_key = public_key
api_context.ssl = True
api_context.method_type = APIMethodType.POST
api_context.address = 'openapi.m-pesa.com'
api_context.port = 443
api_context.path = '/sandbox/ipg/v2/vodacomTZN/c2bPayment/singleStage/'
api_context.add_header('Origin', '*')

#Input Variables
amount = plans.price
phone = request.POST.get('phone')
desc = plans.name

api_context.add_parameter('input_Amount', amount)
api_context.add_parameter('input_Country', 'TZN')
api_context.add_parameter('input_Currency', 'TZS')
api_context.add_parameter('input_CustomerMSISDN', '000000000001') #phone number from customer
api_context.add_parameter('input_ServiceProviderCode', '000000')
api_context.add_parameter('input_ThirdPartyConversationID', 'asv02e5958774f7ba228d83d0d689761')
api_context.add_parameter('input_TransactionReference', reference_no)
api_context.add_parameter('input_PurchasedItemsDesc', desc)

api_request = APIRequest(api_context)

sleep(30)

result = None

try:
result = api_request.execute()
except Exception as e:
print('Call Failed: ' + e)

if result is None:
raise Exception('API call failed to get result. Please check.')

print(result.status_code)
print(result.headers)
print(result.body)

if result.body['output_ResponseCode'] == 'INS-0':

ends_time = timezone.now() + timedelta(days=plans.duration_days)
Subscription.objects.filter(business=request.user.business).update(
plan=plans.id,
start_time = timezone.now(),
ends_time = ends_time,
paid_status = True,
)
subscription = Subscription.objects.filter(business=request.user.business)

#save transactionID,transactionID
payment = form.save(commit=False)
payment.subscription = subscription
payment.transactionID = result.body['output_TransactionID']
payment.conversationID = result.body['output_ConversationID']
payment.save()

return HttpResponse('Your Payment was Successfully sent!')


else:
form = PaymentForm()
context = {'form':form, 'plans':plans}
context = {'form':form, 'plans':plans}

return render(request, 'membership/payment.html', context)

Expand Down