├── .github └── workflows │ ├── documentation.yml │ └── php.yml ├── .styleci.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── composer.json ├── examples ├── pesa-demo │ ├── .env │ ├── .gitignore │ ├── composer.json │ └── index.php ├── simple.php └── simple2.php └── src └── Pesa.php /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | push: 7 | branches: [main] 8 | 9 | jobs: 10 | checks: 11 | if: github.event_name != 'push' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | - name: Test Build 19 | run: | 20 | cd docs 21 | if [ -e yarn.lock ]; then 22 | yarn install --frozen-lockfile 23 | elif [ -e package-lock.json ]; then 24 | npm ci 25 | else 26 | npm i 27 | fi 28 | npm run build 29 | gh-release: 30 | if: github.event_name != 'pull_request' 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v1 34 | - uses: actions/setup-node@v1 35 | with: 36 | node-version: '12.x' 37 | - name: Add key to allow access to repository 38 | env: 39 | SSH_AUTH_SOCK: /tmp/ssh_agent.sock 40 | run: | 41 | mkdir -p ~/.ssh 42 | ssh-keyscan github.com >> ~/.ssh/known_hosts 43 | echo "${{ secrets.GH_PAGES_DEPLOY }}" > ~/.ssh/id_rsa 44 | chmod 600 ~/.ssh/id_rsa 45 | cat <> ~/.ssh/config 46 | Host github.com 47 | HostName github.com 48 | IdentityFile ~/.ssh/id_rsa 49 | EOT 50 | - name: Release to GitHub Pages 51 | env: 52 | USE_SSH: true 53 | GIT_USER: git 54 | run: | 55 | git config --global user.email "actions@gihub.com" 56 | git config --global user.name "gh-actions" 57 | cd docs 58 | if [ -e yarn.lock ]; then 59 | yarn install --frozen-lockfile 60 | elif [ -e package-lock.json ]; then 61 | npm ci 62 | else 63 | npm i 64 | fi 65 | npx docusaurus deploy 66 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Validate composer.json and composer.lock 17 | run: composer validate 18 | - name: Cache Composer packages 19 | id: composer-cache 20 | uses: actions/cache@v2 21 | with: 22 | path: vendor 23 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 24 | restore-keys: | 25 | ${{ runner.os }}-php- 26 | - name: Install dependencies 27 | if: steps.composer-cache.outputs.cache-hit != 'true' 28 | run: composer install --prefer-dist --no-progress --no-suggest 29 | - name: Run test suite 30 | run: composer run-script test 31 | - uses: codecov/codecov-action@v1 32 | with: 33 | files: ./coverage.xml 34 | verbose: true # optional (default = false) 35 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | disabled: 4 | - single_class_element_per_statement 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "Openpesa", 4 | "Pesa", 5 | "phpseclib" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `php-pesa` will be documented in this file 4 | 5 | 6 | ## 0.1.0 - 2020-10-12 7 | 8 | 9 | ### Add 10 | - Support for PHP v8 11 | - Support for phpunit v9 12 | - Improve enviroment configs 13 | - Revamp Documentation 14 | - Switch from Travis CI to Github Actions for CI/CD 15 | 16 | 17 | 18 | ## 0.0.3 - 2020-10-19 19 | 20 | - Minor bug fixes 21 | - Support for Guzzle v7 and v7 22 | 23 | 24 | ## 0.0.1 - 2020-10-12 25 | 26 | - initial release 27 | - Support for C2B, B2C, REVERSAL 28 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are **welcome** and will be fully **credited**. 4 | 5 | Please read and understand the contribution guide before creating an issue or pull request. 6 | 7 | ## Etiquette 8 | 9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code 10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be 11 | extremely unfair for them to suffer abuse or anger for their hard work. 12 | 13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the 14 | world that developers are civilized and selfless people. 15 | 16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient 17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. 18 | 19 | ## Viability 20 | 21 | When requesting or submitting new features, first consider whether it might be useful to others. Open 22 | source projects are used by many developers, who may have entirely different needs to your own. Think about 23 | whether or not your feature is likely to be used by other users of the project. 24 | 25 | ## Procedure 26 | 27 | Before filing an issue: 28 | 29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. 30 | - Check to make sure your feature suggestion isn't already present within the project. 31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. 32 | - Check the pull requests tab to ensure that the feature isn't already in progress. 33 | 34 | Before submitting a pull request: 35 | 36 | - Check the codebase to ensure that your feature doesn't already exist. 37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. 38 | 39 | ## Requirements 40 | 41 | If the project maintainer has any additional requirements, you will find them listed here. 42 | 43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer). 44 | 45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 46 | 47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date. 48 | 49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option. 50 | 51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 52 | 53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 54 | 55 | **Happy coding**! 56 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Openpesa 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 |

Pesa SDK for PHP

3 |
4 | 5 | >

Version 2 | Work in Progress 🚧

6 | 7 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/openpesa/pesa.svg?style=flat-square&?include_prereleases)](https://packagist.org/packages/openpesa/pesa) 8 | ![Test](https://github.com/openpesa/php-pesa/workflows/Test/badge.svg) 9 | ![documentation](https://github.com/openpesa/php-pesa/workflows/documentation/badge.svg) 10 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fopenpesa%2Fphp-pesa&count_bg=%2379C83D&title_bg=%23555555&icon=codeigniter.svg&icon_color=%23E7E7E7&title=hits&edge_flat=true)](https://hits.seeyoufarm.com) 11 | [![codecov.io](https://img.shields.io/codecov/c/github/openpesa/php-pesa/main?style=flat-square)](https://codecov.io/github/openpesa/php-pesa) 12 | [![Total Downloads](https://img.shields.io/packagist/dt/openpesa/pesa.svg?style=flat-square)](https://packagist.org/packages/openpesa/pesa) 13 | 14 | The **Pesa SDK for PHP** makes it easy for developers to access [OpenAPI](https://openapiportal.m-pesa.com/) in their PHP code, and build robust applications and software using services like Customber 2 Bussiness, Query etc. 15 | 16 | ## Documentation 17 | 18 | Take a look at the [API docs here](https://openpesa.github.io/php-pesa/). 19 | 20 | ## Getting Started 21 | 22 | 1. **Sign up for OpenAPI Portal** – Before you begin, you need to 23 | sign up for an account and retrieve your credentials. 24 | 25 | 1. **Minimum requirements** – To run the SDK, your system will need to meet the 26 | [minimum requirements](https://openpesa.github.io/php-pesa/guide/installation#system-requirements), including having **PHP >= 7.1**. 27 | 29 | 1. **Install the SDK** – Using [Composer] is the recommended way to install the 30 | Pesa SDK for PHP. The SDK is available via [Packagist] under the 31 | [`openpesa/php-pesa`](https://packagist.org/packages/openpesa/pesa) package. If Composer is installed globally on your system, you can run the following in the base directory of your project to add the SDK as a dependency: 32 | ```sh 33 | composer require openpesa/pesa 34 | ``` 35 | Please see the 36 | [Installation section of the User Guide](https://openpesa.github.io/php-pesa/guide/installation) for more 37 | detailed information about installing the SDK through Composer and other 38 | means. 39 | 1. **Using the SDK** – The best way to become familiar with how to use the SDK 40 | is to read the [User Guide](https://openpesa.github.io/php-pesa/guide/quick_guide). 41 | 42 | 44 | 45 | ## Usage 46 | 47 | ### Quick Examples 48 | 49 | ```php 50 | 51 | require 'vendor/autoload.php'; 52 | 53 | use Openpesa\SDK\Pesa; 54 | 55 | // Intiate with credentials 56 | $pesa = new Pesa([ 57 | 'api_key' => 'YOUR_API_KEY', 58 | 'public_key' => 'PUBLIC_KEY', 59 | 'client_options' => [], 60 | ],'sandbox'); 61 | 62 | // Setup the transaction 63 | $data = [ 64 | 'input_Amount' => '10000', 65 | 'input_Country' => 'TZN', 66 | 'input_Currency' => 'TZS', 67 | 'input_CustomerMSISDN' => '255766303775', 68 | 'input_ServiceProviderCode' => '000000', 69 | 'input_ThirdPartyConversationID' => 'rerekf', 70 | 'input_TransactionReference' => rand(), 71 | 'input_PurchasedItemsDesc' => 'Test Two Item' 72 | ]; 73 | 74 | // Execute 75 | $result = $pesa->c2b($data); 76 | 77 | // Print results 78 | var_dump($result); 79 | 80 | ``` 81 | 82 | For more example check [pesa-demo-example](https://github.com/openpesa/php-pesa/tree/develop/examples). 83 | 84 | ### Testing 85 | 86 | ```bash 87 | composer test 88 | ``` 89 | 90 | ## Opening Issues 91 | 92 | If you have a feature requrest or you encounter a bug, please file an issue on [our issue tracker on GitHub](https://github.com/openpesa/php-pesa/issues). 93 | 94 | ## Resources 95 | 96 | * [User Guide](https://openpesa.github.io/php-pesa/) – For both getting started and in-depth SDK usage information 97 | * [API Docs](https://openapiportal.m-pesa.com/) – For details about operations, parameters, and responses 98 | * [Blog](https://openpesa.github.io/blog/) – Tips & tricks, articles, and announcements 99 | * [Sample Project](https://github.com/alphaolomi/laravel-pesa-demo) - A quick, sample project to help get you started 100 | * [Issues](https://github.com/openpesa/php-pesa/issues) – Report issues, submit pull requests, and get involved 101 | * [@openpesa](https://twitter.com/openpesa) – Follow us on Twitter 102 | 103 | ### Changelog 104 | 105 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently. 106 | 107 | ## Contributing 108 | 109 | Please review our [CONTRIBUTING](CONTRIBUTING.md) for details. 110 | 111 | ### Security 112 | 113 | If you discover any security related issues, please email [alphaolomi@gmail.com](mailto:alphaolomi@gmail.com) instead of using the issue tracker. 114 | 115 | ## Credits 116 | 117 | - [Alpha Olomi](https://github.com/openpesa) 118 | - [Ley](https://github.com/leyluj) 119 | - [All Contributors](../../contributors) 120 | 121 | ## License 122 | 123 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 124 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openpesa/pesa", 3 | "description": "Pesa PHP SDK", 4 | "keywords": [ 5 | "api", 6 | "mobile-money", 7 | "tanzania", 8 | "pesa", 9 | "pesa-sdk", 10 | "pesa-sdk-php", 11 | "openpesa" 12 | ], 13 | "homepage": "https://github.com/openpesa/pesa", 14 | "license": "MIT", 15 | "type": "library", 16 | "authors": [ 17 | { 18 | "name": "Alpha Olomi", 19 | "email": "alphaolomi@gmail.com", 20 | "role": "Developer" 21 | }, 22 | { 23 | "name": "Leylow Lujuo", 24 | "email": "leyluj21@gmail.com", 25 | "role": "Developer" 26 | } 27 | ], 28 | "require": { 29 | "php": "^7.2|^8.0", 30 | "ext-json": "*", 31 | "ext-openssl": "*", 32 | "guzzlehttp/guzzle": "^6.0|^7.4", 33 | "phpseclib/phpseclib": "~3.0" 34 | }, 35 | "require-dev": { 36 | "phpunit/phpunit": "^8.5|^9.5" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Openpesa\\SDK\\": "src" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Openpesa\\SDK\\Tests\\": "tests" 46 | } 47 | }, 48 | "scripts": { 49 | "test": "vendor/bin/phpunit", 50 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 51 | }, 52 | "config": { 53 | "sort-packages": true 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/pesa-demo/.env: -------------------------------------------------------------------------------- 1 | USERNAME=sandbox 2 | PUBLICKEY= 3 | AUTHURL= 4 | APIKEY= -------------------------------------------------------------------------------- /examples/pesa-demo/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | composer.lock 3 | docs 4 | vendor 5 | coverage 6 | 7 | .idea 8 | /examples/http-client.private.env.json 9 | /examples/my-simple.php 10 | -------------------------------------------------------------------------------- /examples/pesa-demo/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openpesa/pesa-demo", 3 | "description": "Simple SDK usage", 4 | "type": "project", 5 | "require": { 6 | "openpesa/pesa": "^0.0.7" 7 | }, 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Alpha Olomi", 12 | "email": "alphaolomi@gmail.com" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/pesa-demo/index.php: -------------------------------------------------------------------------------- 1 | $apiKey, 11 | 'public_key' => $publicKey, 12 | ]); 13 | 14 | 15 | $data = [ 16 | 'input_Amount' => 5000, 17 | 'input_CustomerMSISDN' => '000000000001', 18 | 'input_Country' => 'TZN', 19 | 'input_Currency' => 'TZS', 20 | 'input_ServiceProviderCode' => '000000', 21 | 'input_TransactionReference' => 'T12644Z', 22 | 'input_ThirdPartyConversationID' => '1g9b774d1da34af78412a498cbc28f5d', 23 | 'input_PurchasedItemsDesc' => 'Test Three Item' 24 | ]; 25 | 26 | 27 | try { 28 | $result = $pesa->c2b($data); 29 | print_r($result); 30 | } catch (Throwable $th) { 31 | echo $th->getMessage(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/simple.php: -------------------------------------------------------------------------------- 1 | '', 9 | 'public_key' => Fixture::$publicKey, 10 | ]); 11 | 12 | 13 | // $t = $f->getSession(); 14 | 15 | $t = $f->c2b(Fixture::data()); 16 | var_dump($t); 17 | -------------------------------------------------------------------------------- /examples/simple2.php: -------------------------------------------------------------------------------- 1 | '', 9 | 'public_key' => Fixture::$publicKey, 10 | 'country' => 'TZN', 11 | 'currency' => 'TZS', 12 | 'service_provider_code' => '000000', 13 | ]); 14 | 15 | 16 | // $t = $f->getSession(); 17 | 18 | $t = $f->c2b(Fixture::dataMin()); 19 | var_dump($t); 20 | -------------------------------------------------------------------------------- /src/Pesa.php: -------------------------------------------------------------------------------- 1 | [ 49 | 'name' => 'Consumer 2 Business', 50 | 'url' => "c2bPayment/singleStage/", 51 | 'encryptSessionKey' => true, 52 | 'rules' => [] 53 | ], 54 | 'b2c' => [ 55 | 'name' => 'Business 2 Consumer', 56 | 'url' => "b2cPayment/singleStage/", 57 | 'encryptSessionKey' => true, 58 | 'rules' => [] 59 | ], 60 | 61 | 'b2b' => [ 62 | 'name' => 'Business 2 Business', 63 | 'url' => "b2bPayment/", 64 | 'encryptSessionKey' => true, 65 | 'rules' => [] 66 | ], 67 | 'rt' => [ 68 | 'name' => 'Reverse Transaction', 69 | 'url' => "reversal/", 70 | 'encryptSessionKey' => true, 71 | 'rules' => [] 72 | ], 73 | 'query' => [ 74 | 'name' => 'Query Transaction Status', 75 | 'url' => "queryTransactionStatus/", 76 | 'encryptSessionKey' => true, 77 | 'rules' => [] 78 | ], 79 | 'ddc' => [ 80 | 'name' => 'Direct Debits create', 81 | 'url' => "directDebitCreation/", 82 | 'encryptSessionKey' => true, 83 | 'rules' => [] 84 | ], 85 | 'ddp' => [ 86 | 'name' => 'Direct Debits payment', 87 | 'url' => "directDebitPayment/", 88 | 'encryptSessionKey' => false, 89 | ] 90 | 91 | ]; 92 | 93 | 94 | /** 95 | * Pesa constructor. 96 | * 97 | * 98 | * @param $options array 99 | * @param null $client 100 | * @param null $rsa 101 | */ 102 | public function __construct(array $options, $client = null) 103 | { 104 | if (!key_exists('api_key', $options)) throw new InvalidArgumentException("api_key is required"); 105 | if (!key_exists('public_key', $options)) throw new InvalidArgumentException("public_key is required"); 106 | 107 | $options['client_options'] = $options['client_options'] ?? []; 108 | $options['persistent_session'] = $options['persistent_session'] ?? false; 109 | 110 | $options['service_provider_code'] = $options['service_provider_code'] ?? null; 111 | $options['country'] = $options['country'] ?? null; 112 | $options['currency'] = $options['currency'] ?? null; 113 | 114 | $this->options = $options; 115 | $this->client = $this->makeClient($options, $client); 116 | } 117 | 118 | 119 | public function setPublicKey(string $publicKey) 120 | { 121 | $this->options['public_key'] = $publicKey; 122 | } 123 | 124 | 125 | public function setApiKey(string $apiKey) 126 | { 127 | $this->options['api_key'] = $apiKey; 128 | } 129 | 130 | public function setSessionToken($sessionToken) 131 | { 132 | $this->sessionToken = $sessionToken; 133 | } 134 | 135 | 136 | /** 137 | * @param $options 138 | * @param null $client 139 | * @return Client 140 | */ 141 | private function makeClient(array $options, $client = null): Client 142 | { 143 | $apiUrl = ""; 144 | if (array_key_exists("env", $options)) { 145 | $apiUrl = ($options['env'] === "sandbox") ? self::BASE_DOMAIN . "/sandbox" : self::BASE_DOMAIN . "/openapi"; 146 | } else { 147 | $apiUrl = self::BASE_DOMAIN . "/sandbox"; 148 | } 149 | $apiUrl .= "/ipg/v2/vodacomTZN/"; 150 | 151 | return ($client instanceof Client) 152 | ? $client 153 | : new Client(array_merge([ 154 | 'http_errors' => false, 155 | 'base_uri' => $apiUrl, 156 | 'headers' => [ 157 | 'Accept' => 'application/json', 158 | 'Origin' => '*' 159 | ] 160 | ], $options['client_options'])); 161 | } 162 | 163 | /** 164 | * Encrypts data with public key 165 | * 166 | *- Generate an instance of an RSA cipher and use the Base 64 string as the input 167 | * - Encode the API Key with the RSA cipher and digest as Base64 string format 168 | * - The result is your encrypted API Key. 169 | * 170 | * @internal 171 | * @param $key 172 | * @return string 173 | */ 174 | public function encryptKey($key): string 175 | { 176 | $pKey = PublicKeyLoader::load($this->options['public_key']); 177 | openssl_public_encrypt($key, $encrypted, $pKey); 178 | return base64_encode($encrypted); 179 | } 180 | 181 | /** 182 | * Get Session Key from API 183 | * 184 | * @api 185 | * @return mixed 186 | * @throws GuzzleException 187 | */ 188 | public function getSession() 189 | { 190 | $response = $this->client->get( 191 | 'getSession/', 192 | ['headers' => ['Authorization' => "Bearer {$this->encryptKey($this->options['api_key'])}"]] 193 | ); 194 | 195 | return json_decode($response->getBody(), true); 196 | } 197 | 198 | /** 199 | * Get Session Token 200 | * When using persistent session, you can use this method 201 | * to get the session token. If you don't have a persistent session, 202 | * you can use getSession() method to get the session token from API 203 | * 204 | * @return string 205 | * @throws GuzzleException 206 | * @throws Exception 207 | * @api 208 | */ 209 | public function getSessionToken($session = null) 210 | { 211 | if ($session) return $session; 212 | 213 | if ($this->options['persistent_session'] == true && $this->sessionToken) { 214 | return $this->sessionToken; 215 | } 216 | 217 | $resSession = $this->getSession(); 218 | 219 | if ($resSession['output_ResponseCode'] == 'INS-0') { 220 | if ($this->options['persistent_session'] == true) 221 | $this->sessionToken = $resSession['output_SessionID']; 222 | return $resSession['output_SessionID']; 223 | } else { 224 | throw new Exception($resSession['output_ResponseDesc'] ?? "Error Processing Request", $resSession['output_ResponseCode'] ?? 0); 225 | } 226 | } 227 | 228 | /** 229 | * Build a Request Data 230 | * 231 | * @internal 232 | * @param $data mixed 233 | * @return mixed 234 | */ 235 | private function makeRequestData($data) 236 | { 237 | 238 | $data['input_ServiceProviderCode'] = $data['input_ServiceProviderCode'] ?? $this->options['service_provider_code']; 239 | $data['input_Country'] = $data['input_Country'] ?? $this->options['country']; 240 | $data['input_Currency'] = $data['input_Currency'] ?? $this->options['currency']; 241 | 242 | return $data; 243 | } 244 | 245 | /** 246 | * The Query Transaction Status API call is used to query the 247 | * status of the transaction that has been initiated. 248 | * 249 | * @api 250 | * @param $data mixed 251 | * @param $session null|mixed 252 | * @return mixed 253 | * @throws GuzzleException 254 | */ 255 | public function query($data, $session = null) 256 | { 257 | $session = $this->getSessionToken($session); 258 | 259 | $transData = $this->makeRequestData($data); 260 | 261 | $response = $this->client->get(self::TRANSACT_TYPE['query']['url'], [ 262 | 'json' => $transData, 263 | 'headers' => ['Authorization' => "Bearer {$this->encryptKey($session)}"] 264 | ]); 265 | return json_decode($response->getBody(), true); 266 | } 267 | 268 | /** 269 | * Customer to Business (C2B) 270 | * 271 | * The C2B API call is used as a standard customer-to-business transaction. 272 | * Funds from the customer’s mobile money wallet will be deducted and be 273 | * transferred to the mobile money wallet of the business. To authenticate and 274 | * authorize this transaction, M-Pesa Payments Gateway will initiate 275 | * a USSD Push message to the customer to gather and verify the mobile money PIN number. 276 | * This number is not stored and is used only to authorize the transaction. 277 | * 278 | * @param $data mixed 279 | * @param $session null|string 280 | * @return mixed 281 | * @throws GuzzleException 282 | * @api 283 | */ 284 | public function c2b($data, $session = null) 285 | { 286 | 287 | $sessionToken = $this->getSessionToken($session); 288 | $transData = $this->makeRequestData($data); 289 | 290 | $token = $this->encryptKey($sessionToken); 291 | 292 | $response = $this->client->post(self::TRANSACT_TYPE['c2b']['url'], [ 293 | 'json' => $transData, 294 | 'headers' => ['Authorization' => "Bearer {$token}"] 295 | ]); 296 | return json_decode($response->getBody(), true); 297 | } 298 | 299 | 300 | /** 301 | * Business to Customer (B2C) 302 | * 303 | * The B2C API Call is used as a standard business-to-customer 304 | * funds disbursement. Funds from the business account’s wallet 305 | * will be deducted and paid to the mobile money wallet of the customer. 306 | * Use cases for the B2C includes: 307 | * - Salary payments 308 | * - Funds transfers from business 309 | * - Charity pay-out 310 | * 311 | * @param $data mixed 312 | * @param $session null|string 313 | * @return mixed 314 | * @throws GuzzleException 315 | * @api 316 | */ 317 | public function b2c($data, $session = null) 318 | { 319 | 320 | $sessionToken = $this->getSessionToken($session); 321 | 322 | $token = $this->encryptKey($sessionToken); 323 | 324 | $transData = $this->makeRequestData($data); 325 | 326 | $response = $this->client->post(self::TRANSACT_TYPE['b2c']['url'], [ 327 | 'json' => $transData, 328 | 'headers' => ['Authorization' => "Bearer {$token}"] 329 | ]); 330 | return json_decode($response->getBody(), true); 331 | } 332 | 333 | 334 | /** 335 | * business to business (B2B) 336 | * 337 | * The B2B API Call is used for business-to-business transactions. Funds from the business’ mobile money wallet will be deducted and transferred to the mobile money wallet of the other business. Use cases for the B2C includes: 338 | * - Stock purchases 339 | * - Bill payment 340 | * - Ad-hoc payment 341 | * 342 | * @param $data mixed 343 | * @param $session null|string 344 | * @return mixed 345 | * @throws GuzzleException 346 | * @api 347 | */ 348 | public function b2b($data, $session = null) 349 | { 350 | 351 | $sessionToken = $this->getSessionToken($session); 352 | 353 | $token = $this->encryptKey($sessionToken); 354 | 355 | $transData = $this->makeRequestData($data); 356 | 357 | $response = $this->client->post(self::TRANSACT_TYPE['rt']['url'], [ 358 | 'json' => $transData, 359 | 'headers' => ['Authorization' => "Bearer {$token}"] 360 | ]); 361 | return json_decode($response->getBody(), true); 362 | } 363 | 364 | /** 365 | * Payment reversals 366 | * 367 | * The Reversal API is used to reverse a successful transaction. 368 | * Using the Transaction ID of a previously successful transaction, 369 | * the OpenAPI will withdraw the funds from the recipient 370 | * party’s mobile money wallet and revert the funds to the mobile money 371 | * wallet of the initiating party of the original transaction. 372 | * 373 | * @param $data mixed 374 | * @param $session null|string 375 | * @return mixed 376 | * @throws GuzzleException 377 | * @api 378 | */ 379 | public function reverse($data, $session = null) 380 | { 381 | 382 | $sessionToken = $this->getSessionToken($session); 383 | 384 | $token = $this->encryptKey($sessionToken); 385 | 386 | $transData = $this->makeRequestData($data); 387 | 388 | $response = $this->client->post(self::TRANSACT_TYPE['rt']['url'], [ 389 | 'json' => $transData, 390 | 'headers' => ['Authorization' => "Bearer {$token}"] 391 | ]); 392 | return json_decode($response->getBody(), true); 393 | } 394 | 395 | 396 | /** 397 | * 398 | * Direct Debit Create Mandate 399 | * 400 | * 401 | * Direct Debits are payments in M-Pesa that are initiated by 402 | * the Payee alone without any Payer interaction, but permission must 403 | * first be granted by the Payer. The granted permission from the Payer 404 | * to Payee is commonly termed a 'Mandate', and M-Pesa must hold 405 | * details of this Mandate. The Direct Debit API set allows an 406 | * organization to get the initial consent of their customers to create 407 | * the Mandate that allows the organization to debit customer's account 408 | * at an agreed frequency and amount for services rendered. After the 409 | * initial consent, the debit of the account will not involve any customer 410 | * interaction. The Direct Debit feature makes use of the following API calls: 411 | * - Create a Direct Debit mandate 412 | * - Pay a mandate 413 | * The customer is able to view and cancel the Direct Debit mandate from 414 | * G2 menu accessible via USSD menu or the Smartphone Application. 415 | * 416 | * @param $data mixed 417 | * @param $session null|string 418 | * @return mixed 419 | * @throws GuzzleException 420 | * @api 421 | */ 422 | public function debit_create($data, $session = null) 423 | { 424 | 425 | $sessionToken = $this->getSessionToken($session); 426 | 427 | $token = $this->encryptKey($sessionToken); 428 | 429 | $transData = $this->makeRequestData($data); 430 | 431 | $response = $this->client->post(self::TRANSACT_TYPE['ddc']['url'], [ 432 | 'json' => $transData, 433 | 'headers' => ['Authorization' => "Bearer {$token}"] 434 | ]); 435 | return json_decode($response->getBody(), true); 436 | } 437 | 438 | /** 439 | * Direct Debit Payment 440 | * 441 | * Direct Debits are payments in M-Pesa that are initiated by 442 | * the Payee alone without any Payer interaction, but permission 443 | * must first be granted by the Payer. The granted permission 444 | * from the Payer to Payee is commonly termed a ‘Mandate’, and 445 | * M-Pesa must hold details of this Mandate. 446 | * The Direct Debit API set allows an organization to get the initial 447 | * consent of their customers to create the Mandate that allows 448 | * the organization to debit customer's account at an agreed frequency 449 | * and amount for services rendered. After the initial consent, 450 | * the debit of the account will not involve any customer interaction. 451 | * The Direct Debit feature makes use of the following API calls: 452 | * • Create a Direct Debit mandate 453 | * • Pay a mandate 454 | * The customer is able to view and cancel the Direct Debit mandate 455 | * from G2 menu accessible via USSD menu or the Smartphone Application. 456 | * 457 | * @param $data mixed 458 | * @param $session null|string 459 | * @return mixed 460 | * @throws GuzzleException 461 | * @api 462 | */ 463 | public function debit_payment($data, $session = null) 464 | { 465 | 466 | $sessionToken = $this->getSessionToken($session); 467 | 468 | $transData = $this->makeRequestData($data); 469 | 470 | $response = $this->client->post(self::TRANSACT_TYPE['ddp']['url'], [ 471 | 'json' => $transData, 472 | 'headers' => ['Authorization' => "Bearer {$sessionToken}"] 473 | ]); 474 | return json_decode($response->getBody(), true); 475 | } 476 | } 477 | --------------------------------------------------------------------------------