├── .github ├── FUNDING.yml └── workflows │ └── run_tests.yml ├── .styleci.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── USAGE.md ├── composer.json ├── config └── mpesa.php └── src ├── Console └── InstallMpesa.php ├── Facades └── Mpesa.php ├── Mpesa.php ├── MpesaServiceProvider.php ├── cert ├── production.cer └── sandbox.cer └── routes └── web.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: gathuku 2 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | tests: 7 | runs-on: ubuntu-latest 8 | if: "! contains(toJSON(github.event.commits.*.message), '[skip-ci]')" 9 | 10 | strategy: 11 | fail-fast: true 12 | matrix: 13 | php: [7.4] 14 | laravel: [6.*, 7.*, 8.*] 15 | dependency-version: [prefer-stable] 16 | include: 17 | - laravel: 8.* 18 | testbench: 6.* 19 | - laravel: 7.* 20 | testbench: 5.* 21 | - laravel: 6.* 22 | testbench: 4.* 23 | 24 | name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} 25 | 26 | steps: 27 | - name: Checkout code 28 | uses: actions/checkout@v1 29 | 30 | - name: Install SQLite 3 31 | run: | 32 | sudo apt-get update 33 | sudo apt-get install sqlite3 34 | 35 | - name: Cache dependencies 36 | uses: actions/cache@v1 37 | with: 38 | path: ~/.composer/cache/files 39 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 40 | 41 | - name: Setup PHP 42 | uses: shivammathur/setup-php@v2 43 | with: 44 | php-version: ${{ matrix.php }} 45 | extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv 46 | coverage: none 47 | 48 | - name: Install dependencies 49 | run: | 50 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update 51 | composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 52 | 53 | - name: Execute tests 54 | run: vendor/bin/phpunit 55 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel 2 | 3 | disabled: 4 | - single_class_element_per_statement 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `Laravel Mpesa` will be documented in this file 4 | 5 | ## 1.0.0 - 201X-XX-XX 6 | 7 | - initial release 8 | -------------------------------------------------------------------------------- /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) Moses Gathuku 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 | # Laravel Mpesa Package 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/414b845bd4ec44a1894d4f7a7499c227)](https://app.codacy.com/app/gathuku/laravel_mpesa?utm_source=github.com&utm_medium=referral&utm_content=gathuku/laravel_mpesa&utm_campaign=Badge_Grade_Dashboard) 4 | [![Latest Version](https://img.shields.io/github/release/gathuku/laravel_mpesa.svg?style=flat-square)](https://github.com/gathuku/laravel_mpesa/releases) 5 | [![Issues](https://img.shields.io/github/issues/gathuku/laravel_mpesa.svg?style=flat-square)](https://github.com/gathuku/laravel_mpesa/issues) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/gathuku/laravelmpesa.svg?style=flat-square)](https://packagist.org/packages/gathuku/laravelmpesa) 7 | [![Twiter](https://img.shields.io/twitter/url/https/github.com/gathuku/laravel_mpesa.svg?style=social?style=social)](https://twitter.com/Gathukumose) 8 | 9 | 10 | This package helps you integrate your laravel application with Mpesa daraja APIs. The package eliminates (almost)all the hassles and lets you concentrate on what is important. 11 | 12 | The package will help you integrate with the following APIs, available on mpesa daraja; 13 | 14 | - C2B (consumer to business) 15 | - B2C (business to cunsumer) 16 | - Lipa na mpesa online(Mpesa Express) 17 | - Reversal 18 | - Transaction status 19 | - Account balance 20 | 21 | ## Documentation 22 | You are looking at it. But we've also got [beautiful, fully navigable docs](https://beyode.co.ke/mpesa/). 23 | 24 | ## Installation 25 | You can install this awesome package via composer 26 | 27 | ```sh 28 | composer require gathuku/laravelmpesa 29 | ``` 30 | If you're using Laravel >=5.5, this is all you have to do. 31 | 32 | Should you still be on version 5.4 of Laravel, the final steps for you are to add the service provider of the package and alias the package. To do this open your `config/app.php` file. 33 | 34 | Add a new line to the `providers` array: 35 | ```php 36 | Gathuku\Mpesa\MpesaServiceProvider::class, 37 | ``` 38 | And optionally add a new line to the `aliases` array: 39 | ```php 40 | 'Mpesa' => Gathuku\Mpesa\Facades\Mpesa::class, 41 | ``` 42 | 43 | ### Happy Coding :tada: :100: 44 | 45 | ## Configuration 46 | Next, after the package has been installed run; 47 | ``` 48 | php artisan mpesa:install 49 | ``` 50 | or 51 | 52 | ```sh 53 | php artisan vendor:publish 54 | ``` 55 | This will help in publishing `config/mpesa.php` file. 56 | This *mpesa config* file is where you will add all configurations for Mpesa APIs. 57 | This includes the environment your application is running in(sandbox or production), callback URLs and required credentials. 58 | You will obtain credentials from your `app` on Safaricom's [developer portal](https://developer.safaricom.co.ke). 59 | 60 | ```php 61 | 'sandbox', 66 | /*----------------------------------------- 67 | |The App consumer key 68 | |------------------------------------------ 69 | */ 70 | 'consumer_key' => 'aR7R09zePq0OSfOttvuQDrfdM4n37i0C', 71 | 72 | /*----------------------------------------- 73 | |The App consumer Secret 74 | |------------------------------------------ 75 | */ 76 | 'consumer_secret' => 'F9AebI6azDlRjLiR', 77 | 78 | /*----------------------------------------- 79 | |The paybill number 80 | |------------------------------------------ 81 | */ 82 | 'paybill' => 601380, 83 | 84 | /*----------------------------------------- 85 | |The Lipa Na Mpesa Online shortcode 86 | |------------------------------------------ 87 | */ 88 | 'lipa_na_mpesa' => '174379', 89 | ]; 90 | ``` 91 | 92 | For production you need to replace with production credentials. 93 | 94 | For security reasons you may want to define your API credentials in `env` file. For example; 95 | ```php 96 | 'consumer_key' => env('CONSUMER_KEY'), 97 | ``` 98 | 99 | ### Usage 100 | Full usage and examples in [USAGE.md](./USAGE.md) 101 | 102 | ### Contributing 103 | Thank you for considering contributing to `laravelmpesa`. Pull requests and issues welcome. 104 | 105 | Be sure to read through [CONTRIBUTING.md](./CONTRIBUTING.md) and check open issues and PRs before continuing. 106 | 107 | ### Security Vulnerabilities 108 | If you discover a security vulnerability within `laravelmpesa`, please send an e-mail to Moses Gathuku via hey@gathuku.me. All security vulnerabilities will be promptly addressed. 109 | 110 | ### License 111 | The `laravelmpesa` package is open-source software licensed under the [MIT license](https://opensource.org/licenses/MIT) 112 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | For full Mpesa APIs documentation, read the [official safaricom documentation](https://developer.safaricom.co.ke/docs) or [simplified documentation by Peter Njeru](https://peternjeru.co.ke/safdaraja/ui/). 3 | 4 | ## Register C2B Urls 5 | This API enables you to register the callback URLs via which you will receive payment notifications for payments to your paybill/till number. 6 | 7 | To register Urls ensure `c2b_validate_callback` and `c2b_confirm_callback` are filled in `config/mpesa.php`. 8 | When testing on sandbox you can use [ngrok](https://ngrok.com/) to expose your callbacks to the internet. 9 | Then call `c2bRegisterUrls()` on `Mpesa` facade. 10 | 11 | ::: tip 12 | > Remember to import `Mpesa facade` 13 | >> `use Mpesa` 14 | ::: 15 | 16 | ```php 17 | $registerUrlsResponse=Mpesa::c2bRegisterUrls() 18 | ``` 19 | 20 | Upon successful urls registration you should get the following response; 21 | ```json 22 | { 23 | "ConversationID": "", 24 | "OriginatorCoversationID": "", 25 | "ResponseDescription": "success" 26 | } 27 | ``` 28 | 29 | ## C2B API 30 | You can use C2B API to simulate payment from clients and safaricom API. Before simulating you need to have registered your urls using `Register C2B Urls API`. 31 | 32 | To use C2B ensure `consumer_key`,`consumer_secret` and `paybill` are set in `config/mpesa.php`. 33 | To simulate you need to pass three parameters to `simulateC2B` function. 34 | 35 | 1. The `amount` being simulated 36 | 2. Test `MSISDN`- from API test credentials 37 | 3. Bill `reference` 38 | 39 | ```php 40 | $simulateResponse=Mpesa::simulateC2B(100, "254708374149", "Testing"); 41 | ``` 42 | 43 | Upon successful simulation you will receive a response similar to; 44 | ```json 45 | { 46 | "ConversationID": "AG_20190115_00007fc37fc3db6e9562", 47 | "OriginatorCoversationID": "10028-4198443-1", 48 | "ResponseDescription": "Accept the service request successfully." 49 | } 50 | ``` 51 | 52 | For you to receive payment confirmation you need to register a route in `routes/api.php` according to the confirmation and validation callbacks you registered. 53 | Then you can use laravel log facade to log the request content in `storage/logs/laravel.log` file. 54 | 55 | For example, for the following callback urls you can get validation and confirmation requests using the code below 56 | ``` 57 | //Validation callback 58 | https://b2d7e6a4.ngrok.io/api/validate?key=ertyuiowwws 59 | 60 | //Confirmation callback 61 | https://b2d7e6a4.ngrok.io/api/confirm?key=ertyuiowwws 62 | ``` 63 | For validation callback; 64 | ```php 65 | Route::post('validate', function(Request $request){ 66 | Log::info($request->getContent()); 67 | }); 68 | ``` 69 | 70 | For confirmation callback; 71 | ```php 72 | Route::post('confirm', function(Request $request){ 73 | Log::info($request->getContent()); 74 | }); 75 | ``` 76 | 77 | You will receive a confirmation similar to the below, to the confirmation url you registered; 78 | ```json 79 | { 80 | "TransactionType": "Pay Bill", 81 | "TransID": "NAF81H7Y72", 82 | "TransTime": "20190115234802", 83 | "TransAmount": "100.00", 84 | "BusinessShortCode": "601380", 85 | "BillRefNumber": "Testing", 86 | "InvoiceNumber": "", 87 | "OrgAccountBalance": "900.00", 88 | "ThirdPartyTransID": "", 89 | "MSISDN": "254708374149", 90 | "FirstName": "John", 91 | "MiddleName": "J.", 92 | "LastName": "Doe" 93 | } 94 | ``` 95 | 96 | ## B2C API 97 | To use this API you need to call `b2c()` on `Mpesa` facade. This function accept the following parameters 98 | 1. `Amount` - Amount of money sent to a consumer 99 | 2. `MSISDN` - Phone number of consumer 100 | 3. `command_id` -This specifies the type of transaction.There are three allowed values on the API: `SalaryPayment`, `BusinessPayment` or `PromotionPayment` 101 | 4. `Remarks` - small decription of the payment being made. 102 | 103 | ```php 104 | $b2cResponse=Mpesa::b2c(100,'254708374149','PromotionPayment','testing'); 105 | ``` 106 | Upon success you should receive a response similar to the one below; 107 | ```json 108 | 109 | { 110 | "ConversationID": "AG_20190117_00004636fb3ac56655df", 111 | "OriginatorConversationID": "17503-13504109-1", 112 | "ResponseCode":"0", 113 | "ResponseDescription": "Accept the service request successfully." 114 | } 115 | ``` 116 | 117 | After a successful transaction you will get a callback via the `b2c_result` url you specified in `mpesa.php` config. A sample success callback is below. 118 | ```json 119 | { 120 | "Result": { 121 | "ResultType": 0, 122 | "ResultCode": 0, 123 | "ResultDesc": "The service request is processed successfully.", 124 | "OriginatorConversationID": "10030-6237802-1", 125 | "ConversationID": "AG_20190119_000053c075d4e13cbeae", 126 | "TransactionID": "NAJ41H7YJQ", 127 | "ResultParameters": { 128 | "ResultParameter": [ 129 | { 130 | "Key": "TransactionReceipt", 131 | "Value": "NAJ41H7YJQ" 132 | }, 133 | { 134 | "Key": "TransactionAmount", 135 | "Value": 100 136 | }, 137 | { 138 | "Key": "B2CChargesPaidAccountAvailableFunds", 139 | "Value": -495 140 | }, 141 | { 142 | "Key": "B2CRecipientIsRegisteredCustomer", 143 | "Value": "Y" 144 | }, 145 | { 146 | "Key": "TransactionCompletedDateTime", 147 | "Value": "19.01.2019 17:01:27" 148 | }, 149 | { 150 | "Key": "ReceiverPartyPublicName", 151 | "Value": "254708374149 - John Doe" 152 | }, 153 | { 154 | "Key": "B2CWorkingAccountAvailableFunds", 155 | "Value": 600000 156 | }, 157 | { 158 | "Key": "B2CUtilityAccountAvailableFunds", 159 | "Value": 235 160 | } 161 | ] 162 | }, 163 | "ReferenceData": { 164 | "ReferenceItem": { 165 | "Key": "QueueTimeoutURL", 166 | "Value": "https://internalsandbox.safaricom.co.ke/mpesa/b2cresults/v1/submit" 167 | } 168 | } 169 | } 170 | } 171 | ``` 172 | 173 | ## Lipa na Mpesa Online 174 | The following must be filled in `config/mpesa.php` 175 | - 'consumer_key' => '' 176 | - 'consumer_secret' => '' 177 | - 'lipa_na_mpesa' => '' 178 | - 'lipa_na_mpesa_passkey' => '' 179 | - 'lnmocallback' => '', 180 | 181 | To run this api call `express` function on `Mpesa` Facade. The function requires the following parameters 182 | 1. `amount` - Amount of money to charge 183 | 2. `msisdn` - Phone Number of debit party 184 | 3. `AccountReference` - Payment reference 185 | 4. `TransactionDesc` - a small description of the payment 186 | 187 | ```php 188 | $expressResponse=Mpesa::express(100,'2547112855','24242524','Testing Payment'); 189 | ``` 190 | 191 | Upon success `$expressREsponse` will return a response like below; 192 | ```json 193 | { 194 | "MerchantRequestID": "10029-6178310-1", 195 | "CheckoutRequestID": "ws_CO_DMZ_291417540_19012019145720246", 196 | "ResponseCode": "0", 197 | "ResponseDescription": "Success. Request accepted for processing", 198 | "CustomerMessage": "Success. Request accepted for processing" 199 | } 200 | ``` 201 | After success you will get a callback via the `lnmocallback` url you specified in config `mpesa` file. 202 | ```json 203 | { 204 | "Body": { 205 | "stkCallback": { 206 | "MerchantRequestID": "5913-662870-1", 207 | "CheckoutRequestID": "ws_CO_DMZ_224117480_19012019164445976", 208 | "ResultCode": 0, 209 | "ResultDesc": "The service request is processed successfully.", 210 | "CallbackMetadata": { 211 | "Item": [ 212 | { 213 | "Name": "Amount", 214 | "Value": 1 215 | }, 216 | { 217 | "Name": "MpesaReceiptNumber", 218 | "Value": "NAJ3ABAMIR" 219 | }, 220 | { 221 | "Name": "Balance" 222 | }, 223 | { 224 | "Name": "TransactionDate", 225 | "Value": 20190119164514 226 | }, 227 | { 228 | "Name": "PhoneNumber", 229 | "Value": 254705112855 230 | } 231 | ] 232 | } 233 | } 234 | } 235 | } 236 | ``` 237 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gathuku/laravelmpesa", 3 | "description": "A simple mpesa package for laravel framework", 4 | "homepage": "https://github/gathuku/laravel_mpesa", 5 | "keywords": [ 6 | "mpesa", 7 | "laravel", 8 | "gathuku" 9 | ], 10 | "type": "laravel", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "moses gathuku", 15 | "email": "mosesgathuku95@gmail.com" 16 | } 17 | ], 18 | "minimum-stability": "dev", 19 | "require": { 20 | "php": "^7.1|^8.0", 21 | "illuminate/support": "^5.0|^6.0|^7.0|^8.0" 22 | }, 23 | "require-dev": { 24 | "orchestra/testbench": "^4.0", 25 | "phpunit/phpunit": "^8.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "Gathuku\\Mpesa\\":"src/" 30 | } 31 | }, 32 | "autoload-dev": { 33 | "psr-4": { 34 | "Gathuku\\Mpesa\\Tests\\": "tests" 35 | } 36 | }, 37 | "scripts":{ 38 | "test": "vendor/bin/phpunit", 39 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 40 | }, 41 | "config": { 42 | "sort-packages": true 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "providers": [ 47 | "Gathuku\\Mpesa\\MpesaServiceProvider" 48 | ], 49 | "aliases":{ 50 | "Mpesa":"Gathuku\\Mpesa\\Facades\\Mpesa" 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config/mpesa.php: -------------------------------------------------------------------------------- 1 | 'sandbox', 7 | /*----------------------------------------- 8 | |The App consumer key 9 | |------------------------------------------ 10 | */ 11 | 'consumer_key' => 'ZtkRW6ATbVtFpNml5w5SfG26Adfyagn9', 12 | 13 | /*----------------------------------------- 14 | |The App consumer Secret 15 | |------------------------------------------ 16 | */ 17 | 'consumer_secret' => 'dosFI1yQ8bvHEVFw', 18 | 19 | /*----------------------------------------- 20 | |The paybill number 21 | |------------------------------------------ 22 | */ 23 | 'paybill' => 601380, 24 | 25 | /*----------------------------------------- 26 | |Lipa Na Mpesa Online Shortcode 27 | |------------------------------------------ 28 | */ 29 | 'lipa_na_mpesa' => '174379', 30 | 31 | /*----------------------------------------- 32 | |Lipa Na Mpesa Online Passkey 33 | |------------------------------------------ 34 | */ 35 | 'lipa_na_mpesa_passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919', 36 | 37 | /*----------------------------------------- 38 | |Initiator Username. 39 | |------------------------------------------ 40 | */ 41 | 'initiator_username' => 'testapi113', 42 | 43 | /*----------------------------------------- 44 | |Initiator Password 45 | |------------------------------------------ 46 | */ 47 | 'initiator_password' => 'Safaricom007@', 48 | 49 | /*----------------------------------------- 50 | |Test phone Number 51 | |------------------------------------------ 52 | */ 53 | 'test_msisdn ' => '254708374149', 54 | 55 | /*----------------------------------------- 56 | |Lipa na Mpesa Online callback url 57 | |------------------------------------------ 58 | */ 59 | 'lnmocallback' => 'https://b2d7e6a4.ngrok.io/api/validate?key=ertyuiowwws', 60 | 61 | /*----------------------------------------- 62 | |C2B Validation url 63 | |------------------------------------------ 64 | */ 65 | 'c2b_validate_callback' => 'https://b2d7e6a4.ngrok.io/api/validate?key=ertyuiowwws', 66 | 67 | /*----------------------------------------- 68 | |C2B confirmation url 69 | |------------------------------------------ 70 | */ 71 | 'c2b_confirm_callback' => 'https://b2d7e6a4.ngrok.io/api/confirm?key=ertyuiowwws', 72 | 73 | /*----------------------------------------- 74 | |B2C timeout url 75 | |------------------------------------------ 76 | */ 77 | 'b2c_timeout' => 'https://b2d7e6a4.ngrok.io/api/validate?key=ertyuiowwws', 78 | 79 | /*----------------------------------------- 80 | |B2C results url 81 | |------------------------------------------ 82 | */ 83 | 'b2c_result' => 'https://b2d7e6a4.ngrok.io/api/validate?key=ertyuiowwws' 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /src/Console/InstallMpesa.php: -------------------------------------------------------------------------------- 1 | info('Installing Laravel Mpesa...'); 16 | $this->info('Publishing Configuration...'); 17 | 18 | $this->call('vendor:publish', [ 19 | '--provider' => "Gathuku\Mpesa\MpesaServiceProvider", 20 | '--tag' => "mpesa-config" 21 | ]); 22 | 23 | $this->info('Installed Laravel Mpesa'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Facades/Mpesa.php: -------------------------------------------------------------------------------- 1 | base_url = 'https://sandbox.safaricom.co.ke'; 100 | } else { 101 | $this->base_url = 'https://api.safaricom.co.ke'; 102 | } 103 | //Base URL for the API endpoints. This is basically the 'common' part of the API endpoints 104 | $this->consumer_key = config('mpesa.consumer_key'); //App Key. Get it at https://developer.safaricom.co.ke 105 | $this->consumer_secret = config('mpesa.consumer_secret'); //App Secret Key. Get it at https://developer.safaricom.co.ke 106 | $this->paybill =config('mpesa.paybill'); //The paybill/till/lipa na mpesa number 107 | $this->lipa_na_mpesa = config('mpesa.lipa_na_mpesa'); //Lipa Na Mpesa online checkout 108 | $this->lipa_na_mpesa_key = config('mpesa.lipa_na_mpesa_passkey'); //Lipa Na Mpesa online checkout password 109 | $this->initiator_username = config('mpesa.initiator_username'); //Initiator Username. I dont where how to get this. 110 | $this->initiator_password = config('mpesa.initiator_password'); //Initiator password. I dont know where to get this either. 111 | 112 | $this->callback_baseurl = 'https://91c77dd6.ngrok.io/api/callback'; 113 | $this->lnmocallback = config('mpesa.lnmocallback'); 114 | $this->test_msisdn = config('mpesa.test_msisdn'); 115 | // c2b the urls 116 | $this->cbvalidate=config('mpesa.c2b_validate_callback'); 117 | $this->cbconfirm=config('mpesa.c2b_confirm_callback'); 118 | 119 | // b2c URLs 120 | $this->bctimeout=config('mpesa.b2c_timeout'); 121 | $this->bcresult=config('mpesa.b2c_result'); 122 | 123 | $this->access_token = $this->getAccessToken(); //Set up access token 124 | } 125 | 126 | /** 127 | * Submit Request 128 | * 129 | * Handles submission of all API endpoints queries 130 | * 131 | * @param string $url The API endpoint URL 132 | * @param json $data The data to POST to the endpoint $url 133 | * @return object|boolean Curl response or FALSE on failure 134 | * @throws exception if the Access Token is not valid 135 | */ 136 | 137 | public function setCred() 138 | { 139 | if (config('mpesa.mpesa_env')=='sandbox') { 140 | $pubkey=File::get(__DIR__.'/cert/sandbox.cer'); 141 | } else { 142 | $pubkey=File::get(__DIR__.'/cert/production.cer'); 143 | } 144 | openssl_public_encrypt($this->initiator_password, $output, $pubkey, OPENSSL_PKCS1_PADDING); 145 | $this->cred = base64_encode($output); 146 | } 147 | 148 | 149 | public function getAccessToken() 150 | { 151 | $credentials = base64_encode($this->consumer_key.':'.$this->consumer_secret); 152 | $ch = curl_init(); 153 | curl_setopt($ch, CURLOPT_URL, $this->base_url.'/oauth/v1/generate?grant_type=client_credentials'); 154 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 155 | curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Basic '.$credentials, 'Content-Type: application/json')); 156 | $response = curl_exec($ch); 157 | $info = curl_getinfo($ch); 158 | curl_close($ch); 159 | $response = json_decode($response); 160 | 161 | if ($info["http_code"] == 200) { 162 | $access_token = $response->access_token; 163 | $this->access_token = $access_token; 164 | return $access_token; 165 | } else { 166 | //throw new Exception("Invalid Consumer key or secret"); 167 | return false; 168 | } 169 | } 170 | 171 | private function submit_request($url, $data) 172 | { // Returns cURL response 173 | 174 | if (isset($this->access_token)) { 175 | $access_token = $this->access_token; 176 | } else { 177 | $access_token = $this->getAccessToken(); 178 | } 179 | 180 | if ($access_token != '' || $access_token !== false) { 181 | $curl = curl_init(); 182 | curl_setopt($curl, CURLOPT_URL, $url); 183 | curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type: application/json','Authorization: Bearer '.$access_token)); 184 | 185 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); 186 | curl_setopt($curl, CURLOPT_POST, true); 187 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 188 | 189 | $response = curl_exec($curl); 190 | curl_close($curl); 191 | return $response; 192 | } else { 193 | return false; 194 | } 195 | } 196 | 197 | /** 198 | * Business to Client 199 | * 200 | * This method is used to send money to the clients Mpesa account. 201 | * 202 | * @param int $amount The amount to send to the client 203 | * @param int $phone The phone number of the client in the format 2547xxxxxxxx 204 | * @return object Curl Response from submit_request, FALSE on failure 205 | */ 206 | 207 | public function b2c($amount, $phone, $command_id, $remarks) 208 | { 209 | //this function will set b2c credentials 210 | $this->setCred(); 211 | $request_data = array( 212 | 'InitiatorName' => $this->initiator_username, 213 | 'SecurityCredential' => $this->cred, 214 | 'CommandID' => $command_id, 215 | 'Amount' => $amount, 216 | 'PartyA' => $this->paybill, 217 | 'PartyB' => $phone, 218 | 'Remarks' => $remarks, 219 | 'QueueTimeOutURL' => $this->bctimeout, 220 | 'ResultURL' => $this->bcresult, 221 | 'Occasion' => '' //Optional 222 | ); 223 | $data = json_encode($request_data); 224 | $url = $this->base_url.'/mpesa/b2c/v1/paymentrequest'; 225 | $response = $this->submit_request($url, $data); 226 | return $response; 227 | } 228 | 229 | /** 230 | * Business to Business 231 | * 232 | * This method is used to send money to other business Mpesa paybills. 233 | * 234 | * @param int $amount The amount to send to the business 235 | * @param int $shortcode The shortcode of the business to send to 236 | * @return object Curl Response from submit_request, FALSE on failure 237 | */ 238 | 239 | public function b2b($amount, $shortcode) 240 | { 241 | $request_data = array( 242 | 'Initiator' => $this->initiator_username, 243 | 'SecurityCredential' => $this->cred, 244 | 'CommandID' => 'BusinessToBusinessTransfer', 245 | 'SenderIdentifierType' => 'Shortcode', 246 | 'RecieverIdentifierType' => 'Shortcode', 247 | 'Amount' => 100, 248 | 'PartyA' => $this->paybill, 249 | 'PartyB' => 600000, 250 | 'AccountReference' => 'Bennito', 251 | 'Remarks' => 'This is a test comment or remark', 252 | 'QueueTimeOutURL' => $this->bbtimeout, 253 | 'ResultURL' => $this->bbresult, 254 | ); 255 | $data = json_encode($request_data); 256 | $url = $this->base_url.'/mpesa/b2b/v1/paymentrequest'; 257 | $response = $this->submit_request($url, $data); 258 | return $response; 259 | } 260 | 261 | /** 262 | * Client to Business 263 | * 264 | * This method is used to register URLs for callbacks when money is sent from the MPesa toolkit menu 265 | * 266 | * @param string $confirmURL The local URL that MPesa calls to confirm a payment 267 | * @param string $ValidationURL The local URL that MPesa calls to validate a payment 268 | * @return object Curl Response from submit_request, FALSE on failure 269 | */ 270 | 271 | public function c2bRegisterUrls() 272 | { 273 | $request_data = array( 274 | 'ShortCode' => $this->paybill, 275 | 'ResponseType' => 'Completed', 276 | 'ConfirmationURL' => $this->cbconfirm, 277 | 'ValidationURL' => $this->cbvalidate 278 | ); 279 | $data = json_encode($request_data); 280 | //header('Content-Type: application/json'); 281 | 282 | $url = $this->base_url.'/mpesa/c2b/v1/registerurl'; 283 | $response = $this->submit_request($url, $data); 284 | //\Log::info($response); 285 | return $response; 286 | } 287 | 288 | /** 289 | * C2B Simulation 290 | * 291 | * This method is used to simulate a C2B Transaction to test your ConfirmURL and ValidationURL in the Client to Business method 292 | * 293 | * @param int $amount The amount to send to Paybill number 294 | * @param int $msisdn A dummy Safaricom phone number to simulate transaction in the format 2547xxxxxxxx 295 | * @param string $ref A reference name for the transaction 296 | * @return object Curl Response from submit_request, FALSE on failure 297 | */ 298 | 299 | public function simulateC2B($amount, $msisdn, $ref) 300 | { 301 | $data = array( 302 | 'ShortCode' => $this->paybill, 303 | 'CommandID' => 'CustomerPayBillOnline', 304 | 'Amount' => $amount, 305 | 'Msisdn' => $msisdn, 306 | 'BillRefNumber' => $ref 307 | ); 308 | $data = json_encode($data); 309 | $url = $this->base_url.'/c2b/v1/simulate'; 310 | $response = $this->submit_request($url, $data); 311 | return $response; 312 | } 313 | 314 | /** 315 | * Check Balance 316 | * 317 | * Check Paybill balance 318 | * 319 | * @return object Curl Response from submit_request, FALSE on failure 320 | */ 321 | public function check_balance() 322 | { 323 | $data = array( 324 | 'CommandID' => 'AccountBalance', 325 | 'PartyA' => $this->paybill, 326 | 'IdentifierType' => '4', 327 | 'Remarks' => 'Remarks or short description', 328 | 'Initiator' => $this->initiator_username, 329 | 'SecurityCredential' => $this->cred, 330 | 'QueueTimeOutURL' => $this->baltimeout, 331 | 'ResultURL' => $this->balresult 332 | ); 333 | $data = json_encode($data); 334 | $url = $this->base_url.'/mpesa/accountbalance/v1/query'; 335 | $response = $this->submit_request($url, $data); 336 | return $response; 337 | } 338 | 339 | /** 340 | * Transaction status request 341 | * 342 | * This method is used to check a transaction status 343 | * 344 | * @param string $transaction ID eg LH7819VXPE 345 | * @return object Curl Response from submit_request, FALSE on failure 346 | */ 347 | 348 | public function status_request($transaction = 'LH7819VXPE') 349 | { 350 | $data = array( 351 | 'CommandID' => 'TransactionStatusQuery', 352 | 'PartyA' => $this->paybill, 353 | 'IdentifierType' => 4, 354 | 'Remarks' => 'Testing API', 355 | 'Initiator' => $this->initiator_username, 356 | 'SecurityCredential' => $this->cred, 357 | 'QueueTimeOutURL' => $this->statustimeout, 358 | 'ResultURL' => $this->statusresult, 359 | 'TransactionID' => $transaction, 360 | 'Occassion' => 'Test' 361 | ); 362 | $data = json_encode($data); 363 | $url = $this->base_url.'/mpesa/transactionstatus/v1/query'; 364 | $response = $this->submit_request($url, $data); 365 | return $response; 366 | } 367 | 368 | /** 369 | * Transaction Reversal 370 | * 371 | * This method is used to reverse a transaction 372 | * 373 | * @param int $receiver Phone number in the format 2547xxxxxxxx 374 | * @param string $trx_id Transaction ID of the Transaction you want to reverse eg LH7819VXPE 375 | * @param int $amount The amount from the transaction to reverse 376 | * @return object Curl Response from submit_request, FALSE on failure 377 | */ 378 | 379 | public function reverse_transaction($receiver, $trx_id, $amount) 380 | { 381 | $data = array( 382 | 'CommandID' => 'TransactionReversal', 383 | 'ReceiverParty' => $this->test_msisdn, 384 | 'RecieverIdentifierType' => 1, //1=MSISDN, 2=Till_Number, 4=Shortcode 385 | 'Remarks' => 'Testing', 386 | 'Amount' => $amount, 387 | 'Initiator' => $this->initiator_username, 388 | 'SecurityCredential' => $this->cred, 389 | 'QueueTimeOutURL' => $this->reversetimeout, 390 | 'ResultURL' => $this->reverseresult, 391 | 'TransactionID' => 'LIE81C8EFI' 392 | ); 393 | $data = json_encode($data); 394 | $url = $this->base_url.'/mpesa/reversal/v1/request'; 395 | $response = $this->submit_request($url, $data); 396 | return $response; 397 | } 398 | 399 | /********************************************************************* 400 | * 401 | * LNMO APIs 402 | * 403 | * *******************************************************************/ 404 | 405 | public function express($amount, $phone, $ref = "Payment", $desc="Payment") 406 | { 407 | if (!is_numeric($amount) || $amount < 1 || !is_numeric($phone)) { 408 | throw new Exception("Invalid amount and/or phone number. Amount should be 10 or more, phone number should be in the format 254xxxxxxxx"); 409 | return false; 410 | } 411 | $timestamp = date('YmdHis'); 412 | $passwd = base64_encode($this->lipa_na_mpesa.$this->lipa_na_mpesa_key.$timestamp); 413 | $data = array( 414 | 'BusinessShortCode' => $this->lipa_na_mpesa, 415 | 'Password' => $passwd, 416 | 'Timestamp' => $timestamp, 417 | 'TransactionType' => 'CustomerPayBillOnline', 418 | 'Amount' => $amount, 419 | 'PartyA' => $phone, 420 | 'PartyB' => $this->lipa_na_mpesa, 421 | 'PhoneNumber' => $phone, 422 | 'CallBackURL' => $this->lnmocallback, 423 | 'AccountReference' => $ref, 424 | 'TransactionDesc' => $desc, 425 | ); 426 | $data = json_encode($data); 427 | $url = $this->base_url.'/mpesa/stkpush/v1/processrequest'; 428 | $response = $this->submit_request($url, $data); 429 | 430 | if (isset($response)) { 431 | return $response; 432 | } else { 433 | return false; 434 | } 435 | // $result = json_decode($response); 436 | // if(isset($result) && isset($result->CheckoutRequestID)){ 437 | // $c_id = $result->CheckoutRequestID; 438 | // return $this->lnmo_query($c_id); 439 | // }else{ 440 | // return FALSE; 441 | // } 442 | } 443 | 444 | private function lnmoQuery($checkoutRequestID = null) 445 | { 446 | $timestamp = date('YmdHis'); 447 | $passwd = base64_encode($this->lipa_na_mpesa.$this->lipa_na_mpesa_key.$timestamp); 448 | 449 | if ($checkoutRequestID == null || $checkoutRequestID == '') { 450 | //throw new Exception("Checkout Request ID cannot be null"); 451 | return false; 452 | } 453 | 454 | $data = array( 455 | 'BusinessShortCode' => $this->lipa_na_mpesa, 456 | 'Password' => $passwd, 457 | 'Timestamp' => $timestamp, 458 | 'CheckoutRequestID' => $checkoutRequestID 459 | ); 460 | $data = json_encode($data); 461 | $url = $this->base_url.'/mpesa/stkpushquery/v1/query'; 462 | $response = $this->submit_request($url, $data); 463 | return $response; 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/MpesaServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadRoutesFrom(__DIR__.'/routes/web.php'); 21 | 22 | if ($this->app->runningInConsole()) { 23 | //publish the config files 24 | $this->publishes([ 25 | __DIR__.'/../config/mpesa.php' => config_path('mpesa.php'), 26 | ], 'mpesa-config'); 27 | 28 | // Register commands 29 | $this->commands([ 30 | InstallMpesa::class, 31 | ]); 32 | } 33 | } 34 | 35 | /** 36 | * Register any application services. 37 | * 38 | * @return void 39 | */ 40 | public function register() 41 | { 42 | $this->mergeConfigFrom(__DIR__.'/../config/mpesa.php', 'mpesa'); 43 | 44 | $this->app->bind('gathuku-mpesa', function () { 45 | return new Mpesa(); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/cert/production.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGkzCCBXugAwIBAgIKXfBp5gAAAD+hNjANBgkqhkiG9w0BAQsFADBbMRMwEQYK 3 | CZImiZPyLGQBGRYDbmV0MRkwFwYKCZImiZPyLGQBGRYJc2FmYXJpY29tMSkwJwYD 4 | VQQDEyBTYWZhcmljb20gSW50ZXJuYWwgSXNzdWluZyBDQSAwMjAeFw0xNzA0MjUx 5 | NjA3MjRaFw0xODAzMjExMzIwMTNaMIGNMQswCQYDVQQGEwJLRTEQMA4GA1UECBMH 6 | TmFpcm9iaTEQMA4GA1UEBxMHTmFpcm9iaTEaMBgGA1UEChMRU2FmYXJpY29tIExp 7 | bWl0ZWQxEzARBgNVBAsTClRlY2hub2xvZ3kxKTAnBgNVBAMTIGFwaWdlZS5hcGlj 8 | YWxsZXIuc2FmYXJpY29tLmNvLmtlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 9 | CgKCAQEAoknIb5Tm1hxOVdFsOejAs6veAai32Zv442BLuOGkFKUeCUM2s0K8XEsU 10 | t6BP25rQGNlTCTEqfdtRrym6bt5k0fTDscf0yMCoYzaxTh1mejg8rPO6bD8MJB0c 11 | FWRUeLEyWjMeEPsYVSJFv7T58IdAn7/RhkrpBl1dT7SmIZfNVkIlD35+Cxgab+u7 12 | +c7dHh6mWguEEoE3NbV7Xjl60zbD/Buvmu6i9EYz+27jNVPI6pRXHvp+ajIzTSsi 13 | eD8Ztz1eoC9mphErasAGpMbR1sba9bM6hjw4tyTWnJDz7RdQQmnsW1NfFdYdK0qD 14 | RKUX7SG6rQkBqVhndFve4SDFRq6wvQIDAQABo4IDJDCCAyAwHQYDVR0OBBYEFG2w 15 | ycrgEBPFzPUZVjh8KoJ3EpuyMB8GA1UdIwQYMBaAFOsy1E9+YJo6mCBjug1evuh5 16 | TtUkMIIBOwYDVR0fBIIBMjCCAS4wggEqoIIBJqCCASKGgdZsZGFwOi8vL0NOPVNh 17 | ZmFyaWNvbSUyMEludGVybmFsJTIwSXNzdWluZyUyMENBJTIwMDIsQ049U1ZEVDNJ 18 | U1NDQTAxLENOPUNEUCxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2 19 | aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPXNhZmFyaWNvbSxEQz1uZXQ/Y2VydGlm 20 | aWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP29iamVjdENsYXNzPWNSTERpc3RyaWJ1 21 | dGlvblBvaW50hkdodHRwOi8vY3JsLnNhZmFyaWNvbS5jby5rZS9TYWZhcmljb20l 22 | MjBJbnRlcm5hbCUyMElzc3VpbmclMjBDQSUyMDAyLmNybDCCAQkGCCsGAQUFBwEB 23 | BIH8MIH5MIHJBggrBgEFBQcwAoaBvGxkYXA6Ly8vQ049U2FmYXJpY29tJTIwSW50 24 | ZXJuYWwlMjBJc3N1aW5nJTIwQ0ElMjAwMixDTj1BSUEsQ049UHVibGljJTIwS2V5 25 | JTIwU2VydmljZXMsQ049U2VydmljZXMsQ049Q29uZmlndXJhdGlvbixEQz1zYWZh 26 | cmljb20sREM9bmV0P2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0 27 | aWZpY2F0aW9uQXV0aG9yaXR5MCsGCCsGAQUFBzABhh9odHRwOi8vY3JsLnNhZmFy 28 | aWNvbS5jby5rZS9vY3NwMAsGA1UdDwQEAwIFoDA9BgkrBgEEAYI3FQcEMDAuBiYr 29 | BgEEAYI3FQiHz4xWhMLEA4XphTaE3tENhqCICGeGwcdsg7m5awIBZAIBDDAdBgNV 30 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwJwYJKwYBBAGCNxUKBBowGDAKBggr 31 | BgEFBQcDAjAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAQEAC/hWx7KTwSYr 32 | x2SOyyHNLTRmCnCJmqxA/Q+IzpW1mGtw4Sb/8jdsoWrDiYLxoKGkgkvmQmB2J3zU 33 | ngzJIM2EeU921vbjLqX9sLWStZbNC2Udk5HEecdpe1AN/ltIoE09ntglUNINyCmf 34 | zChs2maF0Rd/y5hGnMM9bX9ub0sqrkzL3ihfmv4vkXNxYR8k246ZZ8tjQEVsKehE 35 | dqAmj8WYkYdWIHQlkKFP9ba0RJv7aBKb8/KP+qZ5hJip0I5Ey6JJ3wlEWRWUYUKh 36 | gYoPHrJ92ToadnFCCpOlLKWc0xVxANofy6fqreOVboPO0qTAYpoXakmgeRNLUiar 37 | 0ah6M/q/KA== 38 | -----END CERTIFICATE----- 39 | -------------------------------------------------------------------------------- /src/cert/sandbox.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGgDCCBWigAwIBAgIKMvrulAAAAARG5DANBgkqhkiG9w0BAQsFADBbMRMwEQYK 3 | CZImiZPyLGQBGRYDbmV0MRkwFwYKCZImiZPyLGQBGRYJc2FmYXJpY29tMSkwJwYD 4 | VQQDEyBTYWZhcmljb20gSW50ZXJuYWwgSXNzdWluZyBDQSAwMjAeFw0xNDExMTIw 5 | NzEyNDVaFw0xNjExMTEwNzEyNDVaMHsxCzAJBgNVBAYTAktFMRAwDgYDVQQIEwdO 6 | YWlyb2JpMRAwDgYDVQQHEwdOYWlyb2JpMRAwDgYDVQQKEwdOYWlyb2JpMRMwEQYD 7 | VQQLEwpUZWNobm9sb2d5MSEwHwYDVQQDExhhcGljcnlwdC5zYWZhcmljb20uY28u 8 | a2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCotwV1VxXsd0Q6i2w0 9 | ugw+EPvgJfV6PNyB826Ik3L2lPJLFuzNEEJbGaiTdSe6Xitf/PJUP/q8Nv2dupHL 10 | BkiBHjpQ6f61He8Zdc9fqKDGBLoNhNpBXxbznzI4Yu6hjBGLnF5Al9zMAxTij6wL 11 | GUFswKpizifNbzV+LyIXY4RR2t8lxtqaFKeSx2B8P+eiZbL0wRIDPVC5+s4GdpFf 12 | Y3QIqyLxI2bOyCGl8/XlUuIhVXxhc8Uq132xjfsWljbw4oaMobnB2KN79vMUvyoR 13 | w8OGpga5VoaSFfVuQjSIf5RwW1hitm/8XJvmNEdeY0uKriYwbR8wfwQ3E0AIW1Fl 14 | MMghAgMBAAGjggMkMIIDIDAdBgNVHQ4EFgQUwUfE+NgGndWDN3DyVp+CAiF1Zkgw 15 | HwYDVR0jBBgwFoAU6zLUT35gmjqYIGO6DV6+6HlO1SQwggE7BgNVHR8EggEyMIIB 16 | LjCCASqgggEmoIIBIoaB1mxkYXA6Ly8vQ049U2FmYXJpY29tJTIwSW50ZXJuYWwl 17 | MjBJc3N1aW5nJTIwQ0ElMjAwMixDTj1TVkRUM0lTU0NBMDEsQ049Q0RQLENOPVB1 18 | YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRp 19 | b24sREM9c2FmYXJpY29tLERDPW5ldD9jZXJ0aWZpY2F0ZVJldm9jYXRpb25MaXN0 20 | P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnSGR2h0dHA6Ly9j 21 | cmwuc2FmYXJpY29tLmNvLmtlL1NhZmFyaWNvbSUyMEludGVybmFsJTIwSXNzdWlu 22 | ZyUyMENBJTIwMDIuY3JsMIIBCQYIKwYBBQUHAQEEgfwwgfkwgckGCCsGAQUFBzAC 23 | hoG8bGRhcDovLy9DTj1TYWZhcmljb20lMjBJbnRlcm5hbCUyMElzc3VpbmclMjBD 24 | QSUyMDAyLENOPUFJQSxDTj1QdWJsaWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2 25 | aWNlcyxDTj1Db25maWd1cmF0aW9uLERDPXNhZmFyaWNvbSxEQz1uZXQ/Y0FDZXJ0 26 | aWZpY2F0ZT9iYXNlP29iamVjdENsYXNzPWNlcnRpZmljYXRpb25BdXRob3JpdHkw 27 | KwYIKwYBBQUHMAGGH2h0dHA6Ly9jcmwuc2FmYXJpY29tLmNvLmtlL29jc3AwCwYD 28 | VR0PBAQDAgWgMD0GCSsGAQQBgjcVBwQwMC4GJisGAQQBgjcVCIfPjFaEwsQDhemF 29 | NoTe0Q2GoIgIZ4bBx2yDublrAgFkAgEMMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr 30 | BgEFBQcDATAnBgkrBgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMB 31 | MA0GCSqGSIb3DQEBCwUAA4IBAQBMFKlncYDI06ziR0Z0/reptIJRCMo+rqo/cUuP 32 | KMmJCY3sXxFHs5ilNXo8YavgRLpxJxdZMkiUIVuVaBanXkz9/nMriiJJwwcMPjUV 33 | 9nQqwNUEqrSx29L1ARFdUy7LhN4NV7mEMde3MQybCQgBjjOPcVSVZXnaZIggDYIU 34 | w4THLy9rDmUIasC8GDdRcVM8xDOVQD/Pt5qlx/LSbTNe2fekhTLFIGYXJVz2rcsj 35 | k1BfG7P3pXnsPAzu199UZnqhEF+y/0/nNpf3ftHZjfX6Ws+dQuLoDN6pIl8qmok9 36 | 9E/EAgL1zOIzFvCRYlnjKdnsuqL1sIYFBlv3oxo6W1O+X9IZ 37 | -----END CERTIFICATE----- 38 | -------------------------------------------------------------------------------- /src/routes/web.php: -------------------------------------------------------------------------------- 1 |