""" Base implementation of the MTN API client @author: Moses Mugisha """ import json try: from json.decoder import JSONDecodeError except ImportError: JSONDecodeError = ValueError import requests from requests import Request, Session from requests._internal_utils import to_native_string from requests.auth import AuthBase from requests.auth import HTTPBasicAuth from .config import MomoConfig from .errors import APIError from .utils import requests_retry_session class Response: def __init__(self, body, code, headers): self.body = body self.code = code self.headers = headers self.data = body class MoMoAuth(AuthBase): """Attaches Authentication to the given Request object.""" def __init__(self, token): self.token = token def __call__(self, r): # modify and return the request r.headers['Authorization'] = "Bearer " + to_native_string(self.token) return r class ClientInterface(): def getAuthToken(self): raise NotImplementedError def getBalance(self): raise NotImplementedError def getTransactionStatus(self): raise NotImplementedError class Client(ClientInterface): def getAuthToken(self): return super(Client, self).getAuthToken() def getBalance(self): return super(Client, self).getBalance() def getTransactionStatus(self): return super(Client, self).getTransactionStatus() class MomoApi(ClientInterface, object): def __init__( self, config, ** kwargs): super(MomoApi, self).__init__(**kwargs) self._session = Session() self._config = MomoConfig(config) @property def config(self): return self._config def request(self, method, url, headers, post_data=None): self.authToken = self.getAuthToken().json()["access_token"] request = Request( method, url, data=json.dumps(post_data), headers=headers, auth=MoMoAuth(self.authToken)) prepped = self._session.prepare_request(request) resp = requests_retry_session(sesssion=self._session).send(prepped, verify=False ) return self.interpret_response(resp) def interpret_response(self, resp): rcode = resp.status_code rheaders = resp.headers try: rbody = resp.json() except JSONDecodeError: rbody = resp.text resp = Response(rbody, rcode, rheaders) if not (200 <= rcode < 300): self.handle_error_response(rbody, rcode, resp.text, rheaders) return resp def handle_error_response(self, rbody, rcode, resp, rheaders): raise APIError( "Invalid response object from API: {0} (HTTP response code " "was {1})".format(rbody, rcode), rbody, rcode, resp) def request_headers(self, api_key, method): headers = {} return headers def getAuthToken(self, product, url, subscription_key): data = json.dumps({}) headers = { "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": subscription_key } response = requests.post( "{0}{1}".format(self.config.baseUrl, url), auth=HTTPBasicAuth( self.config.userId(product), self.config.APISecret(product)), data=data, headers=headers) return response def getBalance(self, url, subscription_key): headers = { "X-Target-Environment": self.config.environment, "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": subscription_key } url = "{0}{1}".format(self.config.baseUrl, url) res = self.request("GET", url, headers) return res.json() def getTransactionStatus( self, transaction_id, url, subscription_key, ** kwargs): headers = { "X-Target-Environment": self.config.environment, "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": subscription_key } _url = self.config.baseUrl + url + transaction_id res = self.request("GET", _url, headers) return res.json() @classmethod def generateToken( cls, host, api_user, api_key, base_url, environment="sandbox", **kwargs): data = {"providerCallbackHost": host} headers = { "Content-Type": "application/json", "Ocp-Apim-Subscription-Key": api_key, "X-Target-Environment": environment, } url = base_url + "/v1_0/apiuser/{0}/apikey".format(api_user) res = requests.post(url, data=json.dumps(data), headers=headers) return res.json() def close(self): if self._session is not None: self._session.close()