-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Completed transfer of mpesa b2c integration application to new reposi…
…tory.
- Loading branch information
1 parent
548aac5
commit fe4209b
Showing
25 changed files
with
2,117 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,21 @@ | ||
## MPesa B2C | ||
## EXPNext MPesa B2C Integration | ||
|
||
MPesa B2C Integration for ERPNext | ||
### Installation | ||
|
||
#### License | ||
Using bench, [install ERPNext](https://github.com/frappe/bench#installation) as mentioned here. | ||
|
||
mit | ||
Once ERPNext is installed, add MPesa B2c app to your bench by running | ||
|
||
```sh | ||
$ bench get-app https://github.com/navariltd/navari-mpesa-b2c.git | ||
``` | ||
|
||
After that, you can install MPesa B2C app on required site by running | ||
|
||
```sh | ||
$ bench --site [site.name] install-app navari_mpesa_b2c | ||
``` | ||
|
||
### License | ||
|
||
MIT. See [license.txt](https://github.com/navariltd/navari-mpesa-b2c/blob/develop/license.txt) for more information. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import frappe | ||
from frappe.utils import logger | ||
|
||
logger.set_log_level("DEBUG") | ||
api_logger = frappe.logger("api", allow_site=True, file_count=50) |
40 changes: 40 additions & 0 deletions
40
navari_mpesa_b2c/mpesa_b2c/doctype/csf_ke_custom_exceptions.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
"""Custom Exceptions and Errors raised by modules in the CSF_KE application""" | ||
|
||
|
||
class InvalidReceiverMobileNumberError(Exception): | ||
"""Raised when receiver's mobile number fails validation""" | ||
|
||
|
||
class InsufficientPaymentAmountError(Exception): | ||
"""Raised when the payment amount is less than the required KShs. 10""" | ||
|
||
|
||
class IncorrectStatusError(Exception): | ||
"""Raised when status is Errored but no errod description or error code has been supplied""" | ||
|
||
|
||
class InvalidTokenExpiryTime(Exception): | ||
""" | ||
Raised when the access token's expiry time is earlier | ||
or the same as the access token's fetch time. | ||
It should always be 1 hour after the fetch time. | ||
""" | ||
|
||
|
||
class InvalidURLError(Exception): | ||
"""Raised when URLs fail validation""" | ||
|
||
|
||
class InvalidAuthenticationCertificateFileError(Exception): | ||
"""Raised when an invalid certificate file, i.e. not a .cer or .pem, is uploaded""" | ||
|
||
|
||
class UnExistentB2CPaymentRecordError(Exception): | ||
"""Raised when referencing a B2C Payment that does not exist""" | ||
|
||
|
||
class InformationMismatchError(Exception): | ||
""" | ||
Raised when there's a mismatch in any of the B2C Payment's records | ||
and the corresponding B2C Payments Transaction's records | ||
""" |
Empty file.
22 changes: 22 additions & 0 deletions
22
navari_mpesa_b2c/mpesa_b2c/doctype/daraja_access_tokens/daraja_access_tokens.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright (c) 2023, Navari Limited and contributors | ||
// For license information, please see license.txt | ||
|
||
frappe.ui.form.on("Daraja Access Tokens", { | ||
validate: function (frm) { | ||
if (frm.doc.expiry_time && frm.doc.token_fetch_time) { | ||
expiryTime = new Date(frm.doc.expiry_time); | ||
fetchTime = new Date(frm.doc.token_fetch_time); | ||
|
||
if (expiryTime <= fetchTime) { | ||
frappe.msgprint({ | ||
message: __( | ||
"Token Expiry Time cannot be earlier than or the same as Token Fetch Time" | ||
), | ||
indicator: "red", | ||
title: "Validation Error", | ||
}); | ||
frappe.validated = false; | ||
} | ||
} | ||
}, | ||
}); |
66 changes: 66 additions & 0 deletions
66
navari_mpesa_b2c/mpesa_b2c/doctype/daraja_access_tokens/daraja_access_tokens.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
{ | ||
"actions": [], | ||
"autoname": "autoincrement", | ||
"creation": "2023-11-01 11:56:09.082783", | ||
"default_view": "List", | ||
"doctype": "DocType", | ||
"editable_grid": 1, | ||
"engine": "InnoDB", | ||
"field_order": [ | ||
"access_token", | ||
"token_fetch_time", | ||
"column_break_snir", | ||
"expiry_time" | ||
], | ||
"fields": [ | ||
{ | ||
"fieldname": "access_token", | ||
"fieldtype": "Password", | ||
"in_list_view": 1, | ||
"label": "Access Token", | ||
"reqd": 1 | ||
}, | ||
{ | ||
"fieldname": "token_fetch_time", | ||
"fieldtype": "Datetime", | ||
"label": "Token Fetch Time", | ||
"reqd": 1 | ||
}, | ||
{ | ||
"fieldname": "column_break_snir", | ||
"fieldtype": "Column Break" | ||
}, | ||
{ | ||
"fieldname": "expiry_time", | ||
"fieldtype": "Datetime", | ||
"in_list_view": 1, | ||
"label": "Expiry", | ||
"reqd": 1 | ||
} | ||
], | ||
"index_web_pages_for_search": 1, | ||
"links": [], | ||
"modified": "2023-11-15 10:42:44.625615", | ||
"modified_by": "Administrator", | ||
"module": "MPesa B2C", | ||
"name": "Daraja Access Tokens", | ||
"naming_rule": "Autoincrement", | ||
"owner": "Administrator", | ||
"permissions": [ | ||
{ | ||
"create": 1, | ||
"delete": 1, | ||
"email": 1, | ||
"export": 1, | ||
"print": 1, | ||
"read": 1, | ||
"report": 1, | ||
"role": "System Manager", | ||
"share": 1, | ||
"write": 1 | ||
} | ||
], | ||
"sort_field": "modified", | ||
"sort_order": "DESC", | ||
"states": [] | ||
} |
21 changes: 21 additions & 0 deletions
21
navari_mpesa_b2c/mpesa_b2c/doctype/daraja_access_tokens/daraja_access_tokens.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright (c) 2023, Navari Limited and contributors | ||
# For license information, please see license.txt | ||
|
||
from frappe.model.document import Document | ||
|
||
from ..csf_ke_custom_exceptions import InvalidTokenExpiryTime | ||
from .. import api_logger | ||
|
||
|
||
class DarajaAccessTokens(Document): | ||
"""Daraja Access Tokens controller class""" | ||
|
||
def validate(self) -> None: | ||
"""Run validations before saving document""" | ||
if self.expiry_time and self.expiry_time <= self.token_fetch_time: | ||
api_logger.error( | ||
"Access Token Expiry time cannot be same or early than the fetch time" | ||
) | ||
raise InvalidTokenExpiryTime( | ||
"Access Token Expiry time cannot be same or early than the fetch time" | ||
) |
134 changes: 134 additions & 0 deletions
134
navari_mpesa_b2c/mpesa_b2c/doctype/daraja_access_tokens/test_daraja_access_tokens.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
# Copyright (c) 2023, Navari Limited and Contributors | ||
# See license.txt | ||
|
||
import datetime | ||
from unittest.mock import patch | ||
|
||
import frappe | ||
import requests | ||
from frappe.tests.utils import FrappeTestCase | ||
from frappe.utils.password import get_decrypted_password | ||
|
||
from ..csf_ke_custom_exceptions import InvalidTokenExpiryTime | ||
from ..mpesa_b2c_payment import mpesa_b2c_payment | ||
from ..mpesa_b2c_payment.mpesa_b2c_payment import get_hashed_token | ||
|
||
TOKEN_ACCESS_TIME = datetime.datetime.now() | ||
|
||
|
||
def create_access_token() -> None: | ||
"""Creates a valid access token record for testing""" | ||
if frappe.flags.test_events_created: | ||
return | ||
|
||
frappe.set_user("Administrator") | ||
|
||
expiry_time = TOKEN_ACCESS_TIME + datetime.timedelta(hours=1) | ||
frappe.get_doc( | ||
{ | ||
"doctype": "Daraja Access Tokens", | ||
"access_token": "123456789", | ||
"token_fetch_time": TOKEN_ACCESS_TIME, | ||
"expiry_time": expiry_time, | ||
} | ||
).insert() | ||
|
||
frappe.flags.test_events_created = True | ||
|
||
|
||
class TestDarajaAccessTokens(FrappeTestCase): | ||
"""Testing the Daraja Access Tokens doctype""" | ||
|
||
def setUp(self) -> None: | ||
create_access_token() | ||
|
||
def tearDown(self) -> None: | ||
frappe.set_user("Administrator") | ||
|
||
def test_valid_access_token(self) -> None: | ||
"""Attempt to access an existing token""" | ||
token = frappe.db.get_value( | ||
"Daraja Access Tokens", | ||
{"token_fetch_time": TOKEN_ACCESS_TIME}, | ||
["name", "token_fetch_time", "expiry_time"], | ||
as_dict=True, | ||
) | ||
access_token = get_decrypted_password( | ||
"Daraja Access Tokens", token.name, "access_token" | ||
) | ||
|
||
self.assertEqual(access_token, "123456789") | ||
self.assertEqual(token.token_fetch_time, TOKEN_ACCESS_TIME) | ||
self.assertEqual( | ||
token.expiry_time, TOKEN_ACCESS_TIME + datetime.timedelta(hours=1) | ||
) | ||
|
||
def test_create_incomplete_access_token(self) -> None: | ||
"""Attemp to create a record from incomplete data""" | ||
with self.assertRaises(frappe.exceptions.MandatoryError): | ||
frappe.get_doc( | ||
{ | ||
"doctype": "Daraja Access Tokens", | ||
"access_token": "123456789", | ||
"token_fetch_time": TOKEN_ACCESS_TIME, | ||
} | ||
).insert() | ||
|
||
def test_incorrect_datetime_type(self) -> None: | ||
"""Test passing strings to datetime fields""" | ||
with self.assertRaises(TypeError): | ||
frappe.get_doc( | ||
{ | ||
"doctype": "Daraja Access Tokens", | ||
"access_token": TOKEN_ACCESS_TIME + datetime.timedelta(hours=1), | ||
"token_fetch_time": TOKEN_ACCESS_TIME, | ||
"expiry_time": "123456789", | ||
} | ||
).insert() | ||
|
||
def test_expiry_time_earlier_than_fetch_time(self) -> None: | ||
"""Test expiry time being early than fetch time""" | ||
with self.assertRaises(InvalidTokenExpiryTime): | ||
frappe.get_doc( | ||
{ | ||
"doctype": "Daraja Access Tokens", | ||
"access_token": "123456789", | ||
"token_fetch_time": TOKEN_ACCESS_TIME, | ||
"expiry_time": TOKEN_ACCESS_TIME - datetime.timedelta(hours=1), | ||
} | ||
).insert() | ||
|
||
def test_get_hashed_token(self) -> None: | ||
"""Tests the get_hashed_token() function from the b2c_payment module""" | ||
hashed_token = get_hashed_token() | ||
token = frappe.db.get_value( | ||
"Daraja Access Tokens", | ||
{"token_fetch_time": TOKEN_ACCESS_TIME}, | ||
["name"], | ||
) | ||
|
||
self.assertEqual(hashed_token, token) | ||
|
||
@patch.object(mpesa_b2c_payment.requests, "get") | ||
def test_get_access_tokens(self, mock_response) -> None: | ||
"""Tests the get_access_tokens() function from the b2c_payment module""" | ||
mock_response.return_value.status_code = 200 | ||
mock_response.return_value.text = { | ||
"access_token": "987654321", | ||
"expires_in": "3599", | ||
} | ||
|
||
token, status_code = mpesa_b2c_payment.get_access_tokens( | ||
"123456789", "secret", "https://example.com/authorise" | ||
) | ||
self.assertIsInstance(token, dict) | ||
self.assertEqual(token["access_token"], "987654321") | ||
self.assertEqual(token["expires_in"], "3599") | ||
self.assertEqual(status_code, 200) | ||
|
||
def test_get_access_tokens_error_response(self) -> None: | ||
"""Tests instances the get_access_tokens() function from the b2c_payment module fails""" | ||
with self.assertRaises(requests.HTTPError): | ||
mpesa_b2c_payment.get_access_tokens( | ||
"123456789", "secret", "https://example.com/authorise" | ||
) |
Empty file.
Oops, something went wrong.