├── __init__.py ├── pympesa.env.tmpl ├── pympesa ├── tests │ ├── __init__.py │ ├── test_encoding.py │ └── test_pympesa.py ├── urls.py └── __init__.py ├── setup.py ├── LICENSE.txt ├── .gitignore └── README.rst /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pympesa.env.tmpl: -------------------------------------------------------------------------------- 1 | # Dev credentials from developer.safaricom.co.ke 2 | TEST_MPESA_CONSUMER_KEY= 3 | TEST_MPESA_CONSUMER_SECRET= 4 | TEST_MPESA_SHORT_CODE_1= 5 | TEST_MPESA_INITIATOR_NAME_SC_1= 6 | TEST_MPESA_SECURITY_CREDENTIAL_SC_1= 7 | TEST_MPESA_SHORT_CODE_2= 8 | TEST_MPESA_MSISDN= 9 | TEST_MPESA_ONLINE_SHORT_CODE= 10 | TEST_MPESA_ONLINE_PASS_KEY= 11 | TEST_MPESA_INITIATOR_SECURITY_CREDENTIAL= 12 | TEST_MPESA_VALIDATION_URL= 13 | TEST_MPESA_CONFIRMATION_URL= 14 | -------------------------------------------------------------------------------- /pympesa/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | def load_tests(loader, tests, pattern): 4 | """ 5 | Discover and load all tests in this directory. 6 | """ 7 | suite = unittest.TestSuite() 8 | # Discover tests in test_pympesa.py and test_encoding.py 9 | for all_test_suite in unittest.defaultTestLoader.discover(".", pattern="test_*.py"): 10 | for test_suite in all_test_suite: 11 | suite.addTests(test_suite) 12 | return suite 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='pympesa', 4 | version='1.0.2', 5 | description='Daraja (Mpesa REST API) python library', 6 | url='https://github.com/pythias-io/pympesa', 7 | author='Andrew Kamau', 8 | license='MIT', 9 | packages=['pympesa'], 10 | install_requires=[ 11 | 'requests', 12 | ], 13 | python_requires='>=3.6', 14 | classifiers=[ 15 | 'Programming Language :: Python :: 3', 16 | 'Programming Language :: Python :: 3.6', 17 | 'Programming Language :: Python :: 3.7', 18 | 'Programming Language :: Python :: 3.8', 19 | 'Programming Language :: Python :: 3.9', 20 | 'Programming Language :: Python :: 3.10', 21 | 'Programming Language :: Python :: 3.11', 22 | 'License :: OSI Approved :: MIT License', 23 | 'Operating System :: OS Independent', 24 | ], 25 | zip_safe=False) 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017, Pythias Labs (http://pythias.io/) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /pympesa/tests/test_encoding.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import base64 # Import base64 for completeness, though encode_password should handle it 3 | from pympesa import encode_password 4 | 5 | class TestEncoding(unittest.TestCase): 6 | 7 | def test_encode_password(self): 8 | """Test that encode_password correctly encodes the password string.""" 9 | shortcode = "testshortcode" 10 | passkey = "testpasskey" 11 | timestamp = "20231027101010" 12 | 13 | # Expected output generated from: 14 | # import base64 15 | # password_str = "testshortcodetestpasskey20231027101010" 16 | # password_bytes = password_str.encode('utf-8') 17 | # encoded_bytes = base64.b64encode(password_bytes) 18 | # encoded_string = encoded_bytes.decode('utf-8') 19 | # print(encoded_string) # dGVzdHNob3J0Y29kZXRlc3RwYXNza2V5MjAyMzEwMjcxMDEwMTA= 20 | expected_encoded_password = "dGVzdHNob3J0Y29kZXRlc3RwYXNza2V5MjAyMzEwMjcxMDEwMTA=" 21 | 22 | result = encode_password(shortcode, passkey, timestamp) 23 | self.assertEqual(result, expected_encoded_password) 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Twisted things 10 | *.pid 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | 58 | # Sphinx documentation 59 | docs/_build/ 60 | 61 | # PyBuilder 62 | target/ 63 | 64 | # Ipython Notebook 65 | .ipynb_checkpoints 66 | 67 | # Mac stuff 68 | */.DS_Store 69 | .DS_Store 70 | .DS_Store? 71 | ._* 72 | .Spotlight-V100 73 | .Trashes 74 | Icon? 75 | ehthumbs.db 76 | Thumbs.db 77 | 78 | # Sass 79 | .sass-cache/ 80 | 81 | # Sublime 82 | _static/assets/sublime 83 | 84 | # other stuff 85 | _static/.sass-cache/ 86 | 87 | _static/base.html 88 | 89 | *.swp 90 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | M-PESA API | Python Client 2 | ========================== 3 | 4 | Mpesa Rest API python library implementing the following endpoints: 5 | 6 | * Lipa na mpesa online 7 | * B2B 8 | * B2C 9 | * C2B 10 | * Transaction status 11 | * Account balance 12 | * Reversal 13 | 14 | 15 | Getting Started 16 | --------------- 17 | Installation: 18 | 19 | .. code-block:: bash 20 | 21 | pip install pympesa 22 | 23 | Set up your environment by exporting the variables on `pympesa.env.tmpl` 24 | 25 | Usage: 26 | 27 | 1. Create your app on https://developer.safaricom.co.ke/user/me/apps [+ Add a new App] 28 | 2. Your app will be created with a and a 29 | 3. Generate a time bound oauth access token which will enable you to call this APIs. 30 | 31 | .. code-block:: python 32 | 33 | import pympesa 34 | 35 | response = pympesa.oauth_generate_token( 36 | consumer_key, consumer_secret).json() 37 | access_token = response.get("access_token") 38 | 39 | 4. With this access token you can now call the APIs. 40 | 41 | Register your callback URLs 42 | 43 | .. code-block:: python 44 | 45 | from pympesa import Pympesa 46 | 47 | mpesa_client = Pympesa(access_token) 48 | mpesa_client.c2b_register_url( 49 | ValidationUrl="https://your-app/validate", 50 | ConfirmationUrl="https://your-app/confirm", 51 | ResponseType="Completed", 52 | ShortCode="01234" 53 | ) 54 | 55 | Initiate Lipa na M-PESA online (triggering STK Push) 56 | 57 | .. code-block:: python 58 | 59 | mpesa_client.lipa_na_mpesa_online_payment( 60 | BusinessShortCode="600000", 61 | Password="xxxxx_yyyy_zzz", 62 | TransactionType="CustomerPayBillOnline", 63 | Amount="100", 64 | PartyA="254708374149", 65 | PartyB="600000", 66 | PhoneNumber="254708374149", 67 | CallBackURL="https://your-app/callback", 68 | AccountReference="ref-001", 69 | TransactionDesc="desc-001" 70 | ) 71 | 72 | 73 | 74 | Changelog 75 | --------- 76 | 77 | 0.3 - 2017/09/19 78 | ~~~~~~~~~~~~~~~~ 79 | 80 | - Initial version 81 | 82 | 0.4 - 2017/10/04 83 | ~~~~~~~~~~~~~~~~ 84 | 85 | - Added testcases 86 | 87 | 0.4.1 - 2017/10/04 88 | ~~~~~~~~~~~~~~~~~~ 89 | 90 | - Updated project home url on pypi from bitbucket to github 91 | -------------------------------------------------------------------------------- /pympesa/urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | 5 | URL = {} 6 | 7 | URL["sandbox"] = {} 8 | URL["sandbox"]["v1"] = {} 9 | URL["sandbox"]["v1"]["reversal_request"] = "https://sandbox.safaricom.co.ke/mpesa/reversal/v1/request" 10 | URL["sandbox"]["v1"]["b2c_payment_request"] = "https://sandbox.safaricom.co.ke/mpesa/b2c/v1/paymentrequest" 11 | URL["sandbox"]["v1"]["b2b_payment_request"] = "https://sandbox.safaricom.co.ke/mpesa/b2b/v1/paymentrequest" 12 | URL["sandbox"]["v1"]["c2b_register_url"] = "https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl" 13 | URL["sandbox"]["v1"]["c2b_simulate_transaction"] = "https://sandbox.safaricom.co.ke/mpesa/c2b/v1/simulate" 14 | URL["sandbox"]["v1"]["transation_status_request"] = "https://sandbox.safaricom.co.ke/mpesa/transactionstatus/v1/query" 15 | URL["sandbox"]["v1"]["account_balance_request"] = "https://sandbox.safaricom.co.ke/mpesa/accountbalance/v1/query" 16 | URL["sandbox"]["v1"]["lipa_na_mpesa_online_query"] = "https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query" 17 | URL["sandbox"]["v1"]["lipa_na_mpesa_online_payment"] = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest" 18 | URL["sandbox"]["v1"]["oauth_generate_token"] = "https://sandbox.safaricom.co.ke/oauth/v1/generate" 19 | 20 | URL["production"] = {} 21 | URL["production"]["v1"] = {} 22 | 23 | URL["production"]["v1"]["reversal_request"] = "https://api.safaricom.co.ke/mpesa/reversal/v1/request" 24 | URL["production"]["v1"]["b2c_payment_request"] = "https://api.safaricom.co.ke/mpesa/b2c/v1/paymentrequest" 25 | URL["production"]["v1"]["b2b_payment_request"] = "https://api.safaricom.co.ke/mpesa/b2b/v1/paymentrequest" 26 | URL["production"]["v1"]["c2b_register_url"] = "https://api.safaricom.co.ke/mpesa/c2b/v1/registerurl" 27 | URL["production"]["v1"]["c2b_simulate_transaction"] = "https://api.safaricom.co.ke/mpesa/c2b/v1/simulate" 28 | URL["production"]["v1"]["transation_status_request"] = "https://api.safaricom.co.ke/mpesa/transactionstatus/v1/query" 29 | URL["production"]["v1"]["account_balance_request"] = "https://api.safaricom.co.ke/mpesa/accountbalance/v1/query" 30 | URL["production"]["v1"]["lipa_na_mpesa_online_query"] = "https://api.safaricom.co.ke/mpesa/stkpushquery/v1/query" 31 | URL["production"]["v1"]["lipa_na_mpesa_online_payment"] = "https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest" 32 | URL["production"]["v1"]["oauth_generate_token"] = "https://api.safaricom.co.ke/oauth/v1/generate" 33 | -------------------------------------------------------------------------------- /pympesa/tests/test_pympesa.py: -------------------------------------------------------------------------------- 1 | import unittest, os, datetime 2 | import pympesa 3 | 4 | ENV = "production" 5 | #ENV = "development" 6 | 7 | class PympesaTests(unittest.TestCase): 8 | 9 | def setUp(self): 10 | # For testing purposes, use a dummy access token to avoid network calls in setUp. 11 | # Real integration tests would need proper handling of token generation and network calls. 12 | access_token = "dummy_access_token_for_testing" 13 | print("token: " + access_token) # Python 3 print 14 | self.client = pympesa.Pympesa(access_token, env="sandbox") # Specify env for URL construction 15 | self.test_msisdn = "254708374149" 16 | self.test_short_code_1 = "603082" 17 | self.test_short_code_2 = "600000" 18 | 19 | # Commenting out tests that make actual network calls for now. 20 | # These would require mocking the 'requests' library for proper unit testing. 21 | # def test_c2b_register_url(self): 22 | # ValidationURL = "https://4ffe7395.ngrok.io/safaricom" 23 | # ConfirmationURL = "https://4ffe7395.ngrok.io/safaricom" 24 | # ResponseType = "Cancelled" 25 | # ShortCode = self.test_short_code_1 26 | # resp = self.client.c2b_register_url( 27 | # ValidationURL=ValidationURL, 28 | # ConfirmationURL=ConfirmationURL, 29 | # ResponseType=ResponseType, 30 | # ShortCode=ShortCode 31 | # ) 32 | # result = resp.json() 33 | # self.assertEqual(resp.status_code, 200) 34 | # ResponseType = "Completed" 35 | # resp = self.client.c2b_register_url( 36 | # ValidationURL=ValidationURL, 37 | # ConfirmationURL=ConfirmationURL, 38 | # ResponseType=ResponseType, 39 | # ShortCode=ShortCode 40 | # ) 41 | # result = resp.json() 42 | # self.assertEqual(resp.status_code, 200) 43 | 44 | 45 | # def test_c2b_simulate_transaction(self): 46 | # resp = self.client.c2b_simulate_transaction( 47 | # CommandID="CustomerPayBillOnline", 48 | # Amount=50, 49 | # Msisdn=self.test_msisdn, 50 | # BillRefNumber="pympesa-test", 51 | # ShortCode=self.test_short_code_1 52 | # ) 53 | # self.assertEqual(resp.status_code, 200) 54 | # result = resp.json() 55 | # self.assertIn("ResponseDescription", result) 56 | 57 | 58 | def test_generate_timestamp(self): 59 | timestamp = pympesa.generate_timestamp() 60 | print(timestamp) # Python 3 print 61 | self.assertIsInstance(timestamp, str) 62 | self.assertTrue(timestamp.isdigit()) 63 | self.assertEqual(int(timestamp[:4]), datetime.datetime.now().year) 64 | self.assertEqual(int(timestamp[4:6]), datetime.datetime.now().month) 65 | self.assertEqual(int(timestamp[6:8]), datetime.datetime.now().day) 66 | 67 | if __name__ == "__main__": 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /pympesa/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """Mpesa rest API client. 4 | 5 | This exposes various mpesa functionalities to developers. 6 | 7 | All class functions take in keyword arguments (kwargs) and 8 | response returned is a response object from (python requests). 9 | From this object you can retrieve the response parameters 10 | as text or json. 11 | """ 12 | 13 | import base64 14 | import requests 15 | from datetime import datetime 16 | 17 | from .urls import URL 18 | 19 | 20 | class Pympesa: 21 | 22 | def __init__(self, access_token, env="production", version="v1", timeout=None): 23 | self.headers = {"Authorization": "Bearer %s" % access_token} 24 | self.env = env 25 | self.version = version 26 | self.timeout = timeout 27 | 28 | def b2b_payment_request(self, **kwargs): 29 | 30 | """Mpesa Transaction from one company to another. 31 | 32 | https://developer.safaricom.co.ke/b2b/apis/post/paymentrequest 33 | """ 34 | 35 | expected_keys = ["Initiator", "SecurityCredential", 36 | "CommandID", "SenderIdentifierType", 37 | "RecieverIdentifierType", "Amount", 38 | "PartyA", "PartyB", "AccountReference", 39 | "Remarks", "QueueTimeOutURL", "ResultURL"] 40 | payload = process_kwargs(expected_keys, kwargs) 41 | response = self.make_request(URL[self.env][self.version]["b2b_payment_request"], 42 | payload, "POST") 43 | return response 44 | 45 | def b2c_payment_request(self, **kwargs): 46 | 47 | """Mpesa Transaction from company to client. 48 | 49 | https://developer.safaricom.co.ke/b2c/apis/post/paymentrequest 50 | """ 51 | 52 | expected_keys = ["InitiatorName", "SecurityCredential", 53 | "CommandID", "Amount", "PartyA", 54 | "PartyB", "Remarks", "QueueTimeOutURL", 55 | "ResultURL", "Occassion"] 56 | 57 | payload = process_kwargs(expected_keys, kwargs) 58 | 59 | response = self.make_request( 60 | URL[self.env][self.version]["b2c_payment_request"], payload, "POST") 61 | 62 | return response 63 | 64 | def c2b_register_url(self, **kwargs): 65 | 66 | """Use this API to register validation and confirmation URLs on M-Pesa. 67 | 68 | https://developer.safaricom.co.ke/c2b/apis/post/registerurl 69 | """ 70 | expected_keys = ["ShortCode", "ResponseType", 71 | "ConfirmationURL", "ValidationURL"] 72 | payload = process_kwargs(expected_keys, kwargs) 73 | response = self.make_request(URL[self.env][self.version]["c2b_register_url"], 74 | payload, "POST") 75 | return response 76 | 77 | def c2b_simulate_transaction(self, **kwargs): 78 | 79 | """Use this API to simulate a C2B transaction. 80 | 81 | https://developer.safaricom.co.ke/c2b/apis/post/simulate 82 | """ 83 | 84 | expected_keys = ["ShortCode", "Amount", 85 | "Msisdn", "BillRefNumber"] 86 | payload = process_kwargs(expected_keys, kwargs) 87 | payload["CommandID"] = "CustomerPayBillOnline" 88 | response = self.make_request( 89 | URL[self.env][self.version]["c2b_simulate_transaction"], payload, "POST") 90 | return response 91 | 92 | def transation_status_request(self, **kwargs): 93 | 94 | """Use this API to check the status of transaction. 95 | 96 | https://developer.safaricom.co.ke/transaction-status/apis/post/query 97 | """ 98 | 99 | expected_keys = ["Initiator", "SecurityCredential", 100 | "TransactionID", "PartyA", 101 | "ResultURL", "QueueTimeOutURL", 102 | "Remarks", "Occasion"] 103 | payload = process_kwargs(expected_keys, kwargs) 104 | payload["CommandID"] = "TransactionStatusQuery" 105 | payload["IdentifierType"] = "1" 106 | response = self.make_request( 107 | URL[self.env][self.version]["transation_status_request"], payload, "POST") 108 | return response 109 | 110 | def account_balance_request(self, **kwargs): 111 | 112 | """Use this API to enquire the balance on an M-Pesa BuyGoods (Till Number). 113 | 114 | https://developer.safaricom.co.ke/account-balance/apis/post/query 115 | """ 116 | 117 | expected_keys = ["Initiator", "SecurityCredential", "PartyA", 118 | "Remarks", "QueueTimeOutURL", "ResultURL"] 119 | payload = process_kwargs(expected_keys, kwargs) 120 | payload["CommandID"] = "AccountBalance" 121 | payload["IdentifierType"] = "4" 122 | response = self.make_request( 123 | URL[self.env][self.version]["account_balance_request"], payload, "POST") 124 | return response 125 | 126 | def reversal_request(self, **kwargs): 127 | 128 | """Use this API for reversal transaction. 129 | 130 | https://developer.safaricom.co.ke/reversal/apis/post/request 131 | """ 132 | 133 | expected_keys = ["Initiator", "SecurityCredential", 134 | "TransactionID", "Amount", 135 | "ReceiverParty", "ResultURL", 136 | "QueueTimeOutURL", "Remarks", "Occasion"] 137 | payload = process_kwargs(expected_keys, kwargs) 138 | payload["CommandID"] = "TransactionReversal" 139 | payload["RecieverIdentifierType"] = "4" 140 | response = self.make_request( 141 | URL[self.env][self.version]["reversal_request"], payload, "POST") 142 | return response 143 | 144 | def lipa_na_mpesa_online_query(self, **kwargs): 145 | 146 | """For Lipa Na M-Pesa online payment using STK Push. 147 | 148 | https://developer.safaricom.co.ke/lipa-na-m-pesa-online/apis/post/stkpushquery/v1/query 149 | """ 150 | 151 | expected_keys = ["BusinessShortCode", "Password", "CheckoutRequestID", "Timestamp"] 152 | payload = process_kwargs(expected_keys, kwargs) 153 | response = self.make_request( 154 | URL[self.env][self.version]["lipa_na_mpesa_online_query"], payload, "POST") 155 | return response 156 | 157 | def lipa_na_mpesa_online_payment(self, **kwargs): 158 | 159 | """For Lipa Na M-Pesa online payment using STK Push. 160 | 161 | Use this API to initiate online payment on behalf of a customer. 162 | 163 | https://developer.safaricom.co.ke/docs#lipa-na-m-pesa-online-payment 164 | """ 165 | 166 | expected_keys = ["BusinessShortCode", "Password", "Timestamp", 167 | "Amount", "PartyA", "PartyB", "PhoneNumber", 168 | "CallBackURL", "AccountReference", "TransactionDesc"] 169 | payload = process_kwargs(expected_keys, kwargs) 170 | payload["TransactionType"] = "CustomerPayBillOnline" 171 | response = self.make_request( 172 | URL[self.env][self.version]["lipa_na_mpesa_online_payment"], payload, "POST") 173 | return response 174 | 175 | def make_request(self, url, payload, method): 176 | 177 | """Invoke url and return a python request object""" 178 | 179 | if self.timeout: 180 | return requests.request(method, url, headers=self.headers, json=payload, timeout=self.timeout) 181 | else: 182 | return requests.request(method, url, headers=self.headers, json=payload) 183 | 184 | 185 | def oauth_generate_token(consumer_key, consumer_secret, grant_type="client_credentials", env="production", version="v1"): 186 | 187 | """Authenticate your app and return an OAuth access token. 188 | 189 | This token gives you time bound access token to call allowed APIs. 190 | NOTE: The OAuth access token expires after an hour (3600 seconds), 191 | after which, you will need to generate another access token 192 | so you need to keep track of this. 193 | """ 194 | 195 | return requests.get(URL[env][version]["oauth_generate_token"], 196 | params=dict(grant_type=grant_type), 197 | auth=requests.auth.HTTPBasicAuth(consumer_key, consumer_secret)) 198 | 199 | 200 | def encode_password(shortcode, passkey, timestamp): 201 | """Generate and return a base64 encoded password for online access. 202 | """ 203 | password_str = shortcode + passkey + timestamp 204 | password_bytes = password_str.encode('utf-8') 205 | encoded_bytes = base64.b64encode(password_bytes) 206 | return encoded_bytes.decode('utf-8') 207 | 208 | 209 | 210 | def generate_timestamp(): 211 | """Return the current timestamp formated as YYYYMMDDHHMMSS""" 212 | return datetime.strftime(datetime.now(), "%Y%m%d%H%M%S") 213 | 214 | 215 | def process_kwargs(expected_keys, kwargs): 216 | """Check for any expected but missing keyword arguments 217 | and raise a TypeError else return the keywords arguments 218 | repackaged in a dictionary i.e the payload. 219 | """ 220 | payload = {} 221 | for key in expected_keys: 222 | value = kwargs.pop(key, False) 223 | if not value: 224 | raise TypeError("Missing keyword argument: %s" % key) 225 | else: 226 | payload[key] = value 227 | return payload 228 | --------------------------------------------------------------------------------