Skip to content

Commit 6f34fe3

Browse files
committed
Improved STK push functionality.
1 parent 85ed00e commit 6f34fe3

File tree

6 files changed

+92
-24
lines changed

6 files changed

+92
-24
lines changed

django_daraja/mpesa/core.py

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(self):
2424
def access_token(self):
2525
return mpesa_access_token()
2626

27-
def stk_push(self, phone_number, amount, account_reference, transaction_desc = 'Description'):
27+
def stk_push(self, phone_number, amount, account_reference, transaction_desc, callback_url):
2828
phone_number = format_phone_number(phone_number)
2929
url = api_base_url() + 'mpesa/stkpush/v1/processrequest'
3030
passkey = mpesa_config('MPESA_PASSKEY')
@@ -40,35 +40,32 @@ def stk_push(self, phone_number, amount, account_reference, transaction_desc = '
4040
transaction_type = 'CustomerPayBillOnline'
4141
party_a = phone_number
4242
party_b = business_short_code
43-
callback_url = 'https://darajambili.herokuapp.com/express-payment'
4443

4544
data = {
4645
'BusinessShortCode': business_short_code,
47-
'Password': password,
48-
'Timestamp': timestamp,
49-
'TransactionType': transaction_type,
50-
'Amount': '1',
51-
'PartyA': party_a,
52-
'PartyB': party_b,
53-
'PhoneNumber': phone_number,
54-
'CallBackURL': callback_url,
55-
'AccountReference': account_reference,
56-
'TransactionDesc': transaction_desc
46+
'Password': password,
47+
'Timestamp': timestamp,
48+
'TransactionType': transaction_type,
49+
'Amount': '1',
50+
'PartyA': party_a,
51+
'PartyB': party_b,
52+
'PhoneNumber': phone_number,
53+
'CallBackURL': callback_url,
54+
'AccountReference': account_reference,
55+
'TransactionDesc': transaction_desc
5756
}
5857

5958
headers = {
6059
'Authorization': 'Bearer ' + mpesa_access_token(),
6160
'Content-type': 'application/json'
6261
}
6362

64-
print('Password: ', password)
65-
print('Password: ', password)
66-
6763
try:
6864
r = requests.post(url, json=data, headers=headers)
65+
response = mpesa_response(r)
6966
return r
7067
except requests.exceptions.ConnectionError:
7168
raise MpesaConnectionError('Connection failed')
72-
except Exception:
73-
return ex.message
69+
except Exception as ex:
70+
raise MpesaConnectionError(str(ex))
7471

django_daraja/mpesa/utils.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,27 @@
99
from django.utils import timezone
1010
from decouple import config, UndefinedValueError
1111
import os
12+
from requests import Response
13+
14+
class MpesaResponse(Response):
15+
response_description = ''
16+
error_code = None
17+
error_message = ''
18+
19+
def mpesa_response(r):
20+
'''
21+
Creates MpesaResponse object from requests.Response object
22+
23+
Arguments:
24+
r {requests.Response} -- The response to convert
25+
'''
26+
27+
r.__class__ = MpesaResponse
28+
json_response = r.json()
29+
r.response_description = json_response.get('ResponseDescription', '')
30+
r.error_code = json_response.get('errorCode')
31+
r.error_message = json_response.get('errorMessage', '')
32+
return r
1233

1334
def mpesa_config(key):
1435
'''
@@ -21,6 +42,7 @@ def mpesa_config(key):
2142
try:
2243
value = config(key)
2344
except UndefinedValueError:
45+
# Check key in settings file
2446
raise MpesaConfigurationException('Mpesa environment not configured properly - ' + key + ' not found')
2547

2648
return value
@@ -48,7 +70,14 @@ def generate_access_token_request(consumer_key = None, consumer_secret = None):
4870
url = api_base_url() + 'oauth/v1/generate?grant_type=client_credentials'
4971
consumer_key = consumer_key if consumer_key is not None else mpesa_config('MPESA_CONSUMER_KEY')
5072
consumer_secret = consumer_secret if consumer_secret is not None else mpesa_config('MPESA_CONSUMER_SECRET')
51-
r = requests.get(url, auth=(consumer_key, consumer_secret))
73+
74+
try:
75+
r = requests.get(url, auth=(consumer_key, consumer_secret))
76+
except requests.exceptions.ConnectionError:
77+
raise MpesaConnectionError('Connection failed')
78+
except Exception:
79+
return ex.message
80+
5281
return r
5382

5483
def generate_access_token():
@@ -79,6 +108,7 @@ def mpesa_access_token():
79108
else:
80109
delta = timezone.now() - access_token.created_at
81110
minutes = (delta.total_seconds()//60)%60
111+
print('minutes: ', minutes)
82112
if minutes > 50:
83113
# Access token expired
84114
access_token = generate_access_token()

django_daraja/views.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
from django.http import HttpResponse, JsonResponse
66
from django.views.generic import View
77
from django_daraja.mpesa.core import *
8-
8+
from decouple import config
99
from datetime import datetime
10-
1110
cl = MpesaClient()
1211

1312
def index(request):
@@ -19,8 +18,10 @@ def oauth_success(request):
1918
return JsonResponse(r, safe=False)
2019

2120
def stk_push_success(request):
22-
phone_number = '0719748260'
21+
phone_number = config('LNM_PHONE_NUMBER')
2322
amount = '1'
2423
account_reference = 'ABC001'
25-
r = cl.stk_push(phone_number, amount, account_reference)
26-
return JsonResponse(r.json())
24+
transaction_desc = 'Description'
25+
callback_url = 'https://darajambili.herokuapp.com/express-payment'
26+
r = cl.stk_push(phone_number, amount, account_reference, transaction_desc, callback_url)
27+
return JsonResponse(r.response_description, safe=False)

tests/test_oauth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ def test_access_token_valid(self):
3636
token = generate_access_token()
3737
delta = timezone.now() - token.created_at
3838
minutes = (delta.total_seconds()//60)%60
39-
self.assertLessEqual(minutes, 50)
39+
self.assertLessEqual(minutes, 30)

tests/test_stk_push.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# -*- coding: utf-8 -*-
2+
'''
3+
Test STK Push
4+
'''
5+
6+
from django.test import TestCase
7+
from django_daraja.mpesa.core import MpesaClient
8+
from decouple import config
9+
10+
class MpesaStkPushTestCase(TestCase):
11+
12+
cl = MpesaClient()
13+
14+
def test_stk_push_success(self):
15+
'''
16+
Test successful STK push
17+
'''
18+
19+
phone_number = config('LNM_PHONE_NUMBER')
20+
amount = 1
21+
account_reference = 'reference'
22+
transaction_desc = 'Description'
23+
callback_url = 'https://darajambili.herokuapp.com/express-payment'
24+
response = self.cl.stk_push(phone_number, amount, account_reference, transaction_desc, callback_url)
25+
self.assertEqual(response.response_description, 'Success. Request accepted for processing')
26+
27+
def test_stk_push_invalid_phone_number(self):
28+
'''
29+
Test STK push with invalid phone number
30+
'''
31+
32+
phone_number = '254733000'
33+
amount = 1
34+
account_reference = 'reference'
35+
transaction_desc = 'Description'
36+
callback_url = 'https://darajambili.herokuapp.com/express-payment'
37+
response = self.cl.stk_push(phone_number, amount, account_reference, transaction_desc, callback_url)
38+
self.assertEqual(response.status_code, 500)
39+

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ passenv =
2626
MPESA_EXPRESS_SHORTCODE
2727
MPESA_SHORTCODE_TYPE
2828
MPESA_PASSKEY
29+
LNM_PHONE_NUMBER
2930

3031
commands =
3132
python {toxinidir}/setup.py test

0 commit comments

Comments
 (0)