From aef8379165e85fe55562c906570aaf379685e776 Mon Sep 17 00:00:00 2001 From: Mpande Andrew Date: Fri, 19 Apr 2019 01:16:17 +0300 Subject: [PATCH] initial commit --- .gitignore | 2 + composer.json | 20 ++ composer.lock | 291 +++++++++++++++++++++++++ src/Products/Collection.php | 57 +++++ src/Products/Disbursement.php | 53 +++++ src/Products/Product.php | 261 ++++++++++++++++++++++ src/Products/Remittance.php | 16 ++ src/Traits/SandboxUserProvisioning.php | 65 ++++++ 8 files changed, 765 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/Products/Collection.php create mode 100644 src/Products/Disbursement.php create mode 100644 src/Products/Product.php create mode 100644 src/Products/Remittance.php create mode 100644 src/Traits/SandboxUserProvisioning.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab27d1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +/vendor \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..e0ad835 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "fannypack/momo", + "description": "MTN MOMO api implementation", + "type": "library", + "require": { + "guzzlehttp/guzzle": "^6.3", + "ramsey/uuid": "^3.8" + }, + "authors": [ + { + "name": "Mpande Andrew", + "email": "andrewmvp007@gmail.com" + } + ], + "autoload": { + "psr-4": { + "FannyPack\\Momo\\": "src" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..4c37909 --- /dev/null +++ b/composer.lock @@ -0,0 +1,291 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c9f566c284ab79aab45d7b20c36c956b", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "9f83dded91781a01c63574e387eaa769be769115" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2018-12-04T20:46:45+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/Products/Collection.php b/src/Products/Collection.php new file mode 100644 index 0000000..6954d80 --- /dev/null +++ b/src/Products/Collection.php @@ -0,0 +1,57 @@ +getProductBaseUrl() . ($this->preApproval ? self::PRE_APPROVAL_URI: self::REQUEST_TO_PAY_URI); + } + + protected function transactionStatusUrl() { + return $this->getProductBaseUrl() . ($this->preApproval ? self::PRE_APPROVAL_URI: self::REQUEST_TO_PAY_URI); + } + + /** + * Start request to pay transaction + * + * @param $externalId + * @param $partyId + * @param $amount + * @param $currency + * @param string $payerMessage + * @param string $payeeNote + * @return mixed + * @throws \Exception + */ + public function requestToPay($externalId, $partyId, $amount, $currency, $payerMessage = '', $payeeNote = '') { + return $this->transact($externalId, $partyId, $amount, $currency, $payerMessage, $payeeNote); + } + + /** + * Get request to pay transaction status + * + * @param $paymentRef + * @return mixed + * @throws \Exception + */ + public function getRequestToPayStatus($paymentRef) { + return $this->getTransactionStatus($paymentRef); + } +} \ No newline at end of file diff --git a/src/Products/Disbursement.php b/src/Products/Disbursement.php new file mode 100644 index 0000000..50aaf00 --- /dev/null +++ b/src/Products/Disbursement.php @@ -0,0 +1,53 @@ +getProductBaseUrl() . self::TRANSFER_URI; + } + + protected function transactionStatusUrl() { + return $this->getProductBaseUrl() . self::TRANSFER_URI; + } + + /** + * Start transfer transaction + * + * @param $externalId + * @param $partyId + * @param $amount + * @param $currency + * @param string $payerMessage + * @param string $payeeNote + * @return mixed + * @throws \Exception + */ + public function transfer($externalId, $partyId, $amount, $currency, $payerMessage = '', $payeeNote = '') { + return $this->transact($externalId, $partyId, $amount, $currency, $payerMessage, $payeeNote); + } + + /** + * Get transfer transaction status + * + * @param $paymentRef + * @return mixed + * @throws \Exception + */ + public function transferStatus($paymentRef) { + return $this->getTransactionStatus($paymentRef); + } +} \ No newline at end of file diff --git a/src/Products/Product.php b/src/Products/Product.php new file mode 100644 index 0000000..b837eef --- /dev/null +++ b/src/Products/Product.php @@ -0,0 +1,261 @@ + '', + * 'environment' => '', + * 'accountHolderIdType' => '', + * 'subscriptionKey' => '', + * 'xReferenceId' => '', + * 'apiKey' => '', + * 'preApproval' => '' + * ]); + * + */ + +namespace FannyPack\Momo\Products; + + +use FannyPack\Momo\Traits\SandboxUserProvisioning; +use GuzzleHttp\Client; +use Ramsey\Uuid\Uuid; + +/** + * Class Product + * @package FannyPack\Momo\Products + */ +abstract class Product +{ + use SandboxUserProvisioning; + + const BASE_URL = "https://ericssonbasicapi2.azure-api.net"; + + const TOKEN_URI = "/token"; + + const BALANCE_URI = "/v1_0/account/balance"; + + const ACCOUNT_HOLDER_URI = "/v1_0/accountholder"; + + const API_USER_URI = "/v1_0/apiuser"; + + /** + * @var string $callbackUrl + */ + protected $callbackUrl = "http://localhost:8000"; + + /** + * @var string $environment + */ + protected $environment = "sandbox"; + + /** + * @var string $accountHolderIdType + */ + protected $accountHolderIdType = "MSISDN"; + + /** + * @var string $subscriptionKey + */ + protected $subscriptionKey; + + /** + * @var string $xReferenceId + */ + protected $xReferenceId; + + /** + * @var string $apiKey + */ + protected $apiKey; + + /** + * Product constructor. + * @param $options + */ + public function __construct($options) { + if(!isset($config['subscriptionKey'])) + throw new \InvalidArgumentException("subscriptionKey should be specified"); + + if(!isset($config['xReferenceId'])) + throw new \InvalidArgumentException("xReferenceId should be specified"); + + foreach ($options as $option => $value) { + try{ + $this->{$option} = $value; + }catch (\Exception $exception) {} + } + } + + /** + * New product instance + * + * @param $options + * @return static + */ + public static function create($options) { + return new static($options); + } + + /** + * create new http client + * + * @return Client + */ + protected function newClient() { + return new Client([ + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'Ocp-Apim-Subscription-Key' => $this->subscriptionKey + ], + 'verify' => false + ]); + } + + /** + * Get product base url + * + * @return string + */ + protected function getProductBaseUrl() { + $path = explode('\\', __CLASS__); + return self::BASE_URL . "/" . strtolower(array_pop($path)); + } + + /** + * Get token + * + * @return mixed + * @throws \Exception + */ + public function getToken() { + try { + $resource = $this->getProductBaseUrl() . self::TOKEN_URI; + $response = $this->newClient()->post($resource, [ + 'headers' => [ + 'Authorization' => 'Basic '.base64_encode($this->xReferenceId . ':' . $this->apiKey), + ], + 'json' => [ + 'grant_type' => 'client_credentials', + ], + ]); + + return json_decode($response->getBody(), true); + } catch (\Exception $exception) { + throw new \Exception("Unable to generate token"); + } + } + + /** + * Get account balance + * + * @return mixed + * @throws \Exception + */ + public function getAccountBalance() { + try { + $resource = $this->getProductBaseUrl() . self::BALANCE_URI; + $response = $this->newClient()->get($resource, [ + 'headers' => [ + 'X-Target-Environment' => $this->environment, + ], + ]); + + return json_decode($response->getBody(), true); + } catch (\Exception $exception) { + throw new \Exception("Unable to get account balance"); + } + } + + /** + * Get account holder information + * + * @param $accountHolderId + * @return mixed + * @throws \Exception + */ + public function getAccountHolderInfo($accountHolderId) { + $resource = $this->getProductBaseUrl() . self::ACCOUNT_HOLDER_URI . "/" . $this->accountHolderIdType . "/" . $accountHolderId . "/active"; + try { + $response = $this->newClient()->get($resource, [ + 'headers' => [ + 'X-Target-Environment' => $this->environment, + ], + ]); + + return json_decode($response->getBody(), true); + } catch (\Exception $exception) { + throw new \Exception("Unable to get account holder information"); + } + } + + protected abstract function transactionUrl(); + + protected abstract function transactionStatusUrl(); + + /** + * Start a payment transaction + * + * @param $externalId + * @param $partyId + * @param $amount + * @param $currency + * @param string $payerMessage + * @param string $payeeNote + * @return mixed + * @throws \Exception + */ + protected function transact($externalId, $partyId, $amount, $currency, $payerMessage = '', $payeeNote = '') { + try { + $paymentRef = Uuid::uuid4()->toString(); + $response = $this->newClient()->post($this->transactionUrl(), [ + 'headers' => [ + 'X-Reference-Id' => $paymentRef, + 'X-Callback-Url' => $this->callbackUrl, + 'X-Target-Environment' => $this->environment, + ], + 'json' => [ + 'amount' => $amount, + 'currency' => $currency, + 'externalId' => $externalId, + 'payer' => [ + 'partyIdType' => $this->accountHolderIdType, + 'partyId' => $partyId, + ], + 'payerMessage' => $payerMessage, + 'payeeNote' => $payeeNote, + ], + ]); + + return json_decode($response->getBody(), true); + } catch (\Exception $exception) { + throw new \Exception("Unable to complete transaction"); + } + } + + /** + * Get transaction status + * + * @param $paymentRef + * @return mixed + * @throws \Exception + */ + protected function getTransactionStatus($paymentRef) { + try { + $resource = $this->transactionStatusUrl() . "/" . $paymentRef; + $response = $this->newClient()->get($resource, [ + 'headers' => [ + 'X-Target-Environment' => $this->environment, + ], + ]); + + return json_decode($response->getBody(), true); + } catch (\Exception $exception) { + throw new \Exception("Unable to get transaction status"); + } + } +} \ No newline at end of file diff --git a/src/Products/Remittance.php b/src/Products/Remittance.php new file mode 100644 index 0000000..db5fd51 --- /dev/null +++ b/src/Products/Remittance.php @@ -0,0 +1,16 @@ +newClient()->post(self::BASE_URL . self::API_USER_URI, [ + 'headers' => ['X-Reference-Id' => $this->xReferenceId], + 'json' => ['providerCallbackHost' => $this->callbackUrl] + ]); + return json_decode($response->getBody(), true); + } catch (\Exception $e) { + throw new \Exception("Unable to create an api user"); + } + } + + /** + * Validate api user + * + * @return mixed + * @throws \Exception + */ + public function validateApiUser() { + try { + $response = $this->newClient()->get(self::BASE_URL . self::API_USER_URI . "/" . $this->xReferenceId); + return json_decode($response->getBody(), true); + } catch (\Exception $e) { + throw new \Exception("Unable to validate api user"); + } + } + + /** + * Create api key + * + * @return mixed + * @throws \Exception + */ + public function createApiKey() { + try { + $response = $this->newClient()->post(self::BASE_URL . self::API_USER_URI . "/" . $this->xReferenceId . "/apikey"); + return json_decode($response->getBody(), true); + } catch (\Exception $e) { + throw new \Exception("Unable to create api key"); + } + } +} \ No newline at end of file