Full Code of SmoDav/mpesa for AI

master 8191b77fdab3 cached
46 files
82.0 KB
21.8k tokens
124 symbols
1 requests
Download .txt
Repository: SmoDav/mpesa
Branch: master
Commit: 8191b77fdab3
Files: 46
Total size: 82.0 KB

Directory structure:
gitextract_nvdhfbhw/

├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .php_cs
├── .phpunit.result.cache
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── composer.json
├── config/
│   └── mpesa.php
├── phpunit.xml
├── sonar-project.properties
├── src/
│   └── Mpesa/
│       ├── Auth/
│       │   └── Authenticator.php
│       ├── C2B/
│       │   ├── Identity.php
│       │   ├── Registrar.php
│       │   ├── STK.php
│       │   └── Simulate.php
│       ├── Contracts/
│       │   ├── CacheStore.php
│       │   └── ConfigurationStore.php
│       ├── Engine/
│       │   └── Core.php
│       ├── Exceptions/
│       │   ├── ConfigurationException.php
│       │   └── ErrorException.php
│       ├── Laravel/
│       │   ├── Facades/
│       │   │   ├── Identity.php
│       │   │   ├── Registrar.php
│       │   │   ├── STK.php
│       │   │   └── Simulate.php
│       │   ├── ServiceProvider.php
│       │   └── Stores/
│       │       ├── LaravelCache.php
│       │       └── LaravelConfig.php
│       ├── Native/
│       │   ├── NativeCache.php
│       │   └── NativeConfig.php
│       ├── Repositories/
│       │   ├── ConfigurationRepository.php
│       │   └── Endpoint.php
│       ├── Support/
│       │   ├── Installer.php
│       │   └── helpers.php
│       └── Traits/
│           ├── UsesCore.php
│           ├── UsesSTKMethods.php
│           └── Validates.php
└── tests/
    ├── TestCase.php
    ├── Unit/
    │   ├── AuthenticatorTest.php
    │   ├── ConfigurationRepositoryTest.php
    │   ├── NativeImplementationsTest.php
    │   ├── RegistrarTest.php
    │   └── STKTest.php
    └── files/
        ├── .gitignore
        └── mpesa.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/build.yml
================================================
name: Build

concurrency:
  group: production
  cancel-in-progress: true

on:
  push:
    branches:
      - master
  pull_request:
    types: [opened, synchronize, reopened]
jobs:
  tests:
    name: Tests and SonarCloud
    runs-on: ubuntu-latest

    steps:
      - uses: shivammathur/setup-php@v2
        with:
          php-version: "8.4"
          coverage: "xdebug"
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Install composer dependencies
        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
      - name: Static Checks & Test
        run: ./vendor/bin/phpunit
      - name: SonarCloud Scan
        uses: SonarSource/sonarqube-scan-action@v7.0.0
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}


================================================
FILE: .gitignore
================================================
/.idea
/vendor
composer.lock
.php_cs.cache
index.php
/cache
/.phpintel
.php-cs-fixer.cache
clover.xml
junit.xml


================================================
FILE: .php_cs
================================================
<?php
return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules(
        [
            'array_syntax' => ['syntax' => 'short'],
            'binary_operator_spaces' => [
                'align_double_arrow' => true,
                'align_equals' => true
            ],
            'blank_line_after_namespace' => true,
            'blank_line_before_return' => true,
            'braces' => true,
            'cast_spaces' => true,
            'concat_space' => ['spacing' => 'one'],
            'elseif' => true,
            'encoding' => true,
            'full_opening_tag' => true,
            'function_declaration' => true,
            'indentation_type' => true,
            'line_ending' => true,
            'lowercase_constants' => true,
            'lowercase_keywords' => true,
            'method_argument_space' => true,
            'native_function_invocation' => true,
            'no_alias_functions' => true,
            'no_blank_lines_after_class_opening' => true,
            'no_blank_lines_after_phpdoc' => true,
            'no_closing_tag' => true,
            'no_empty_phpdoc' => true,
            'no_empty_statement' => true,
            'no_extra_consecutive_blank_lines' => true,
            'no_leading_namespace_whitespace' => true,
            'no_singleline_whitespace_before_semicolons' => true,
            'no_spaces_after_function_name' => true,
            'no_spaces_inside_parenthesis' => true,
            'no_trailing_comma_in_list_call' => true,
            'no_trailing_whitespace' => true,
            'no_unused_imports' => true,
            'no_whitespace_in_blank_line' => true,
            'phpdoc_align' => true,
            'phpdoc_indent' => true,
            'phpdoc_no_access' => true,
            'phpdoc_no_empty_return' => true,
            'phpdoc_no_package' => true,
            'phpdoc_scalar' => true,
            'phpdoc_separation' => true,
            'phpdoc_to_comment' => true,
            'phpdoc_trim' => true,
            'phpdoc_types' => true,
            'phpdoc_var_without_name' => true,
            'self_accessor' => true,
            'simplified_null_return' => true,
            'single_blank_line_at_eof' => true,
            'single_import_per_statement' => true,
            'single_line_after_imports' => true,
            'single_quote' => true,
            'ternary_operator_spaces' => true,
            'trim_array_spaces' => true,
            'visibility_required' => true,
        ]
    )
    ->setFinder(
        PhpCsFixer\Finder::create()
        ->files()
        ->in(__DIR__ . '/src')
        ->in(__DIR__ . '/tests')
        ->name('*.php')
    );


================================================
FILE: .phpunit.result.cache
================================================
{"version":1,"defects":{"SmoDav\\Mpesa\\Tests\\Unit\\AuthenticatorTest::testCanAuthenticateUsingRequestAndCached":8,"SmoDav\\Mpesa\\Tests\\Unit\\ConfigurationRepositoryTest::testCanConstruct":8,"SmoDav\\Mpesa\\Tests\\Unit\\ConfigurationRepositoryTest::testCanExtractConfigAndAccountValues":8,"SmoDav\\Mpesa\\Tests\\Unit\\ConfigurationRepositoryTest::testCanResolveUrl":8,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanUseNativeConfig":8,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanGetWholeConfiguration":7,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanConstructWithoutConfig":8,"SmoDav\\Mpesa\\Tests\\Unit\\RegistrarTest::testRegisterUrls":8,"SmoDav\\Mpesa\\Tests\\Unit\\STKTest::testPushRequest":8,"SmoDav\\Mpesa\\Tests\\Unit\\STKTest::testValidateTransaction":8},"times":{"SmoDav\\Mpesa\\Tests\\Unit\\AuthenticatorTest::testCanAuthenticateUsingRequestAndCached":0.06,"SmoDav\\Mpesa\\Tests\\Unit\\ConfigurationRepositoryTest::testCanConstruct":0,"SmoDav\\Mpesa\\Tests\\Unit\\ConfigurationRepositoryTest::testCanExtractConfigAndAccountValues":0,"SmoDav\\Mpesa\\Tests\\Unit\\ConfigurationRepositoryTest::testCanResolveUrl":0,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanUseNativeConfig":0,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanGetWholeConfiguration":0,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanUseNativeCache":0,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanOverwriteCacheItem":0.001,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCacheExpires":1.001,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testCanConstructWithoutConfig":0.03,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testTTLImplementation":0,"SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest::testShouldPullCacheItem":0.001,"SmoDav\\Mpesa\\Tests\\Unit\\RegistrarTest::testRegisterUrls":0.011,"SmoDav\\Mpesa\\Tests\\Unit\\STKTest::testPushRequest":0.02,"SmoDav\\Mpesa\\Tests\\Unit\\STKTest::testValidateTransaction":0.002}}

================================================
FILE: .travis.yml
================================================
dist: trusty
language: php
php:
  - '8.2'
  - '8.3'
  - '8.4'
  - '8.5'

before_script:
  - composer self-update
  - composer install --prefer-source --no-interaction --dev

script: vendor/bin/phpunit


================================================
FILE: CHANGELOG.md
================================================
## CHANGELOG

### 2019-09-26 :: v5.0.0

#### NativeCache

 - Takes a custom storage path as first constructor argument. If none is provided it uses the default configuration cache location. >v5 takes the `ConfigurationStore` as the first argument.
 - Uses seconds instead of minutes as ttl to conform to PSR-16 spec.
 - Added a new method `pull` to extract the key and delete it from the cache if present.

#### NativeConfig

 - Takes the path of the custom configuration file location as the first argument. If none is provided it uses the default configuration file location.
 - Moved the extracting of configuration values to the `ConfigurationRepository`.

#### Core

 - The core can now be initialized without passing the Configuration and Cache store argument. Only the Client is required. If either of the Configuration and Cache stores are not provided, the default will be used in vanilla, and the Laravel defaults will be used in Laravel.
 - The account to be used is now set via the `Core` method `useAccount`.

#### Authenticator

 - Removed all static methods.
 - Added a `flushTokens` method to remove all authentication tokens from the store. `$core->auth()->flushTokens();` or `app(Core::class)->auth()->flushTokens();`
 - 

#### Authenticator
 - `Identity`, `Registrar`, `Simulate` and `STK` all receive the `Core` instance as the first constructor argument.

================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2016 SmoDav

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

================================================
FILE: README.md
================================================
# M-PESA API Package
[![Build Status](https://travis-ci.org/SmoDav/mpesa.svg?branch=master)](https://travis-ci.org/SmoDav/mpesa)
[![Total Downloads](https://poser.pugx.org/smodav/mpesa/d/total.svg)](https://packagist.org/packages/smodav/mpesa)
[![Latest Stable Version](https://poser.pugx.org/smodav/mpesa/v/stable.svg)](https://packagist.org/packages/smodav/mpesa)
[![Latest Unstable Version](https://poser.pugx.org/smodav/mpesa/v/unstable.svg)](https://packagist.org/packages/smodav/mpesa)
[![License](https://poser.pugx.org/smodav/mpesa/license.svg)](https://packagist.org/packages/smodav/mpesa)

This is a PHP package for the Safaricom's M-Pesa API. 
The API allows a merchant to initiate C2B online checkout (paybill via web) transactions.
The merchant submits authentication details, transaction details, callback url and callback method. 

After request submission, the merchant receives instant feedback with validity status of their requests. 
The C2B API handles customer validation and authentication via USSD push. 
The customer then confirms the transaction. If the validation of the customer fails or the customer declines the transaction, the API makes a callback to merchant. Otherwise the transaction is processed and its status is made through a callback.

If you enjoy using this package, please take a moment and [buy me some coffee.](https://rave.flutterwave.com/donate/fiqyumudlt6t)



## Installation

Pull in the package through Composer.

### Native Addon

When using vanilla PHP, modify your `composer.json` file to include:

```json
  "scripts": {
    "post-update-cmd": [
        "SmoDav\\Mpesa\\Support\\Installer::install"
    ]
  },
```

This script will copy the default configuration file to a config folder in the root directory of your project.
Now proceed to require the package.

### General Install

Run `composer require smodav/mpesa` to get the latest stable version of the package.

## Migration from previous versions

v5 of the package changes the implementation and introduces some breaking changes. Please have a look at the [CHANGELOG](https://github.com/SmoDav/mpesa/blob/master/CHANGELOG.md).

v4 of this package uses a new configuration setup. You will need to update your config file in order to upgrade v3 to v4. v2 is still incompatible since it uses the older API version.

### Laravel

When using Laravel 5.5+, the package will automatically register. For laravel 5.4 and below,
include the service provider and its alias within your `config/app.php`.

```php
'providers' => [
    SmoDav\Mpesa\Laravel\ServiceProvider::class,
],

'aliases' => [
    'STK'       => SmoDav\Mpesa\Laravel\Facades\STK::class,
    'Simulate'  => SmoDav\Mpesa\Laravel\Facades\Simulate::class,
    'Registrar' => SmoDav\Mpesa\Laravel\Facades\Registrar::class,
    'Identity'  => SmoDav\Mpesa\Laravel\Facades\Identity::class,
],
```

Publish the package specific config using:
```bash
php artisan vendor:publish
```

### Other Frameworks

To implement this package, a configuration repository is needed, thus any other framework will need to create its own implementation of the `ConfigurationStore` and `CacheStore` interfaces.

### Configuration

The package allows you to have multiple accounts. Each account will have its specific credentials and endpoints that are independent of the rest.
You will be required to set the default account to be used for all transactions, which you can override on each request you make. The package comes
with two default accounts that you can modify.

```
/*
|--------------------------------------------------------------------------
| Default Account
|--------------------------------------------------------------------------
|
| This is the default account to be used when none is specified.
*/

'default' => 'staging',

/*
|--------------------------------------------------------------------------
| File Cache Location
|--------------------------------------------------------------------------
|
| When using the Native Cache driver, this will be the relative directory
| where the cache information will be stored.
*/

'cache_location' => '../cache',

/*
|--------------------------------------------------------------------------
| Accounts
|--------------------------------------------------------------------------
|
| These are the accounts that can be used with the package. You can configure
| as many as needed. Two have been setup for you.
|
| Sandbox: Determines whether to use the sandbox, Possible values: sandbox | production
| Initiator: This is the username used to authenticate the transaction request
| LNMO:
|    shortcode: The till number
|    passkey: The passkey for the till number
|    callback: Endpoint that will be be queried on completion or failure of the transaction.
|
*/

'accounts' => [
    'staging' => [
        'sandbox' => true,
        'key' => 'your development consumer key',
        'secret' => 'your development consumer secret',
        'initiator' => 'your development username',
        'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
        'lnmo' => [
            'paybill' => 'your development paybill number',
            'shortcode' => 'your development business code',
            'passkey' => 'your development passkey',
            'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
        ]
    ],

    'paybill_1' => [
        'sandbox' => false,
        'key' => 'your production consumer key',
        'secret' => 'your production consumer secret',
        'initiator' => 'your production username',
        'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
        'lnmo' => [
            'paybill' => 'your production paybill number',
            'shortcode' => 'your production business code',
            'passkey' => 'your production passkey',
            'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
        ]
    ],

    'paybill_2' => [
        'sandbox' => false,
        'key' => 'your production consumer key',
        'secret' => 'your production consumer secret',
        'initiator' => 'your production username',
        'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
        'lnmo' => [
            'paybill' => 'your production paybill number',
            'shortcode' => 'your production business code',
            'passkey' => 'your production passkey',
            'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
        ]
    ],
],
```

You can add as many accounts as required and switch the connection using the method `usingAccount` on `STK`, `Register` and `Simulate` as shown below.

Also, note the difference between the `business shortcode` and your `paybill number` in the configuration as getting them wrong will cost you a lot of time debugging.


## Usage

For Vanilla PHP you will need to initialize the core engine before any requests as shown below. The package comes with a vanilla php implementation of the cache and configuration store, `NativeCache` and `NativeConfig`.

The `NativeConfig` receives the custom location for the configuration file to be used as the first constructor argument. If no value is passed when creating the instance, it will use the default configuration and look for a configuration file on the root of the project under `configs` directory.

The `NativeCache` receives a custom directory path as the first constructor argument. The path denotes where the cache should store its files. If no path is provided, the default cache location in the config will be used.

```php
use GuzzleHttp\Client;
use SmoDav\Mpesa\Engine\Core;
use SmoDav\Mpesa\Native\NativeCache;
use SmoDav\Mpesa\Native\NativeConfig;

require "vendor/autoload.php";

$config = new NativeConfig();
$cache = new NativeCache($config->get('cache_location'));
// or
$cache = new NativeCache(__DIR__ . '/../some/awesome/directory');

$core = new Core(new Client, $config, $cache);

```

### URL Registration
#### submit(shortCode = null, confirmationURL = null, validationURL = null, onTimeout = 'Completed|Cancelled', account = null)

Register callback URLs

##### Vanilla

```php
use SmoDav\Mpesa\C2B\Registrar;

$conf = 'http://example.com/mpesa/confirm?secret=some_secret_hash_key';
$val = 'http://example.com/mpesa/validate?secret=some_secret_hash_key';


$response = (new Registrar($core))->register(600000)
    ->onConfirmation($conf)
    ->onValidation($val)
    ->submit();

/****** OR ********/
$response = (new Registrar($core))->submit(600000, $conf, $val);

```

When having multiple accounts, switch using the `usingAccount` method. We currently have `staging`, `paybill_1` and `paybill_2` with `staging` as the default:

```php
$response = (new Registrar($core))
    ->register(600000)
    ->usingAccount('paybill_1')
    ->onConfirmation($conf)
    ->onValidation($val)
    ->submit();

/****** OR ********/
$response = (new Registrar($core))->submit(600000, $conf, $val, null, 'paybill_1');
```

##### Laravel

```php
use SmoDav\Mpesa\Laravel\Facades\Registrar;

$conf = 'http://example.com/mpesa/confirm?secret=some_secret_hash_key';
$val = 'http://example.com/mpesa/validate?secret=some_secret_hash_key';

$response = Registrar::register(600000)
    ->onConfirmation($conf)
    ->onValidation($val)
    ->submit();

/****** OR ********/
$response = Registrar::submit(600000, $conf, $val);
```

Using the `paybill_1` account:

```php
use SmoDav\Mpesa\Laravel\Facades\Registrar;

$response = Registrar::register(600000)
    ->usingAccount('paybill_1')
    ->onConfirmation($conf)
    ->onValidation($val)
    ->submit();

/****** OR ********/
$response = Registrar::submit(600000, $conf, $val, null, 'paybill_1');
```

### Simulate Transaction
#### push(amount = null, number = null, reference = null, account = null, command = null)

Initiate a C2B simulation transaction request.

Note that when initiating a C2B simulation, setting the command type is optional and by default `CustomerPaybillOnline`
will be used.

##### Vanilla

```php
use SmoDav\Mpesa\C2B\Simulate;

$simulate = new Simulate($core)

$response = $simulate->request(10)
    ->from(254722000000)
    ->usingReference('Some Reference')
    ->push();

/****** OR ********/
$response = $simulate->push(10, 254722000000, 'Some Reference');
```

Using the `paybill_1` account:

```php
$response = $simulate->request(10)
    ->from(254722000000)
    ->usingReference('Some Reference')
    ->usingAccount('paybill_1')
    ->push();

/****** OR ********/
$response = $simulate->push(10, 254722000000, 'Some Reference', 'paybill_1');
```

Using the `CustomerBuyGoodsOnline` command:

```php
$response = $simulate->request(10)
    ->from(254722000000)
    ->usingReference('Some Reference')
    ->setCommand(CUSTOMER_BUYGOODS_ONLINE)
    ->push();

/****** OR ********/
$response = $simulate->push(10, 254722000000, 'Some Reference', null, CUSTOMER_BUYGOODS_ONLINE);
```


##### Laravel

```php
use SmoDav\Mpesa\Laravel\Facades\Simulate;

$response = Simulate::request(10)
    ->from(254722000000)
    ->usingReference('Some Reference')
    ->push();

/****** OR ********/
$response = Simulate::push(10, 254722000000, 'Some Reference');
```

Using the `paybill_1` account:

```php
use SmoDav\Mpesa\Laravel\Facades\Simulate;

$response = Simulate::request(10)
    ->from(254722000000)
    ->usingReference('Some Reference')
    ->usingAccount('paybill_1')
    ->push();

/****** OR ********/
$response = Simulate::push(10, 254722000000, 'Some Reference', 'paybill_1');
```

Using the `CustomerBuyGoodsOnline` command:

```php
use SmoDav\Mpesa\Laravel\Facades\Simulate;

$response = Simulate::request(10)
    ->from(254722000000)
    ->usingReference('Some Reference')
    ->setCommand(CUSTOMER_BUYGOODS_ONLINE) 
    ->push();

/****** OR ********/
$response = Simulate::push(10, 254722000000, 'Some Reference', null, CUSTOMER_BUYGOODS_ONLINE);
```

### STK PUSH
#### push(amount = null, number = null, reference = null, description = null, account = null, command = null)

Initiate a C2B STK Push request.

Note that when initiating an STK Push, setting the command type is optional and by default `CustomerPaybillOnline`
will be used.

##### Vanilla

```php
use SmoDav\Mpesa\C2B\STK;

$stk = new STK($core);

$response = $stk->request(10)
    ->from(254722000000)
    ->usingReference('Some Reference', 'Test Payment')
    ->push();

/****** OR ********/
$response = $stk->push(10, 254722000000, 'Some Reference', 'Test Payment');
```

Using the `paybill_2` account:

```php

$response = $stk->request(10)
    ->from(254722000000)
    ->usingAccount('paybill_2')
    ->usingReference('Some Reference', 'Test Payment')
    ->push();

/****** OR ********/
$response = $stk->push(10, 254722000000, 'Some Reference', 'Test Payment', 'paybill_2');
```

Using `CustomerBuyGoodsOnline` command:

```php

$response = $stk->request(10)
    ->from(254722000000)
    ->usingReference('Some Reference', 'Test Payment')
    ->setCommand(CUSTOMER_BUYGOODS_ONLINE)
    ->push();

/****** OR ********/
$response = $stk->push(10, 254722000000, 'Some Reference', 'Test Payment', null, CUSTOMER_BUYGOODS_ONLINE);
```


##### Laravel

```php
use SmoDav\Mpesa\Laravel\Facades\STK;

$response = STK::request(10)
    ->from(254722000000)
    ->usingReference('Some Reference', 'Test Payment')
    ->push();

/****** OR ********/
$response = STK::push(10, 254722000000, 'Some Reference', 'Test Payment');
```

Using the `paybill_2` account:

```php
use SmoDav\Mpesa\Laravel\Facades\STK;

$response = STK::request(10)
    ->from(254722000000)
    ->usingAccount('paybill_2')
    ->usingReference('Some Reference', 'Test Payment')
    ->push();

$response = STK::push(10, 254722000000, 'Some Reference', 'Test Payment', 'paybill_2');
```

Using the `CustomerGoodsOnline` command:

```php
use SmoDav\Mpesa\Laravel\Facades\STK;

$response = STK::request(10)
    ->from(254722000000)
    ->usingReference('Some Reference', 'Test Payment')
    ->setCommand(CUSTOMER_BUYGOODS_ONLINE) 
    ->push();

$response = STK::push(10, 254722000000, 'Some Reference', 'Test Payment', null, CUSTOMER_BUYGOODS_ONLINE);
```

### STK PUSH Transaction Validation
#### validate(merchantReferenceId, account = null)

Validate a C2B STK Push transaction.

##### Vanilla

```php
use SmoDav\Mpesa\C2B\STK;

$stk = new STK($core);
    
$response = $stk->validate('ws_CO_16022018125');
```

Using the `paybill_2` account:

```php
$response = $stk->validate('ws_CO_16022018125', 'paybill_2');
```

##### Laravel

```php
use SmoDav\Mpesa\Laravel\Facades\STK;

$response = STK::validate('ws_CO_16022018125');
```

Using the `paybill_1` account:

```php
use SmoDav\Mpesa\Laravel\Facades\STK;

$response = STK::validate('ws_CO_16022018125', 'paybill_2');
```

##### When going live, you should change the `default` value of the config file to the production account.

## License

The M-Pesa Package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).


================================================
FILE: composer.json
================================================
{
    "name": "smodav/mpesa",
    "description": "M-Pesa API implementation",
    "type": "library",
    "keywords": [
        "mpesa",
        "safaricom",
        "laravel",
        "transactions",
        "api"
    ],
    "license": "MIT",
    "authors": [
        {
            "name": "SmoDav",
            "email": "smodavprivate@gmail.com"
        }
    ],
    "autoload": {
        "files": [
            "src/Mpesa/Support/helpers.php"
        ],
        "psr-4": {
            "SmoDav\\Mpesa\\": "src/Mpesa/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "SmoDav\\Mpesa\\Tests\\": "tests/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "SmoDav\\Mpesa\\Laravel\\ServiceProvider"
            ],
            "aliases": {
                "STK": "SmoDav\\Mpesa\\Laravel\\Facades\\STK",
                "Simulate": "SmoDav\\Mpesa\\Laravel\\Facades\\Simulate",
                "Registrar": "SmoDav\\Mpesa\\Laravel\\Facades\\Registrar",
                "Identity": "SmoDav\\Mpesa\\Laravel\\Facades\\Identity"
            }
        }
    },
    "require": {
        "php": ">=8.2",
        "guzzlehttp/guzzle": "^6.2|^7.4.5",
        "illuminate/support": "^10.0|^11.0|^12.0|^13.0",
        "nesbot/carbon": "^2.0|^3.0",
        "ext-json": "*"
    },
    "require-dev": {
        "mockery/mockery": "dev-master|^1.3.1",
        "phpunit/phpunit": "~8.5|^9.3|^10.0"
    },
    "minimum-stability": "stable"
}


================================================
FILE: config/mpesa.php
================================================
<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Default Account
    |--------------------------------------------------------------------------
    |
    | This is the default account to be used when none is specified.
    */

    'default' => 'staging',

    /*
    |--------------------------------------------------------------------------
    | Native File Cache Location
    |--------------------------------------------------------------------------
    |
    | When using the Native Cache driver, this will be the relative directory
    | where the cache information will be stored.
    */

    'cache_location' => '../cache',

    /*
    |--------------------------------------------------------------------------
    | Accounts
    |--------------------------------------------------------------------------
    |
    | These are the accounts that can be used with the package. You can configure
    | as many as needed. Two have been setup for you.
    |
    | Sandbox: Determines whether to use the sandbox, Possible values: sandbox | production
    | Initiator: This is the username used to authenticate the transaction request
    | LNMO:
    |    paybill: Your paybill number
    |    shortcode: Your business shortcode
    |    passkey: The passkey for the paybill number
    |    callback: Endpoint that will be be queried on completion or failure of the transaction.
    |
    */

    'accounts' => [
        'staging' => [
            'sandbox' => true,
            'key' => '',
            'secret' => '',
            'initiator' => 'apitest363',
            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            'lnmo' => [
                'paybill' => 174379,
                'shortcode' => 174379,
                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',
                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            ]
        ],

        'production' => [
            'sandbox' => false,
            'key' => '',
            'secret' => '',
            'initiator' => 'apitest363',
            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            'lnmo' => [
                'paybill' => 174379,
                'shortcode' => 174379,
                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',
                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            ]
        ],
    ],
];


================================================
FILE: phpunit.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    backupGlobals="false"
    bootstrap="vendor/autoload.php"
    colors="true"
    processIsolation="false"
    stopOnFailure="false"
>
    <testsuite name="Package Test Suite">
        <directory suffix="Test.php">tests</directory>
    </testsuite>
    <coverage>
        <report>
            <clover outputFile="clover.xml"/>
        </report>
    </coverage>
    <logging>
        <junit outputFile="junit.xml"/>
    </logging>
    <source>
        <include>
            <directory suffix=".php">./src</directory>
        </include>
    </source>
</phpunit>


================================================
FILE: sonar-project.properties
================================================
sonar.projectKey=smodav_m-pesa
sonar.organization=smodav

# This is the name and version displayed in the SonarCloud UI.
#sonar.projectName=SmoDav
#sonar.projectVersion=1.0

# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
sonar.sources=./src
sonar.php.coverage.reportPaths=clover.xml
sonar.php.tests.reportPath=junit.xml

sonar.php.exclusions=**/vendor/**
sonar.sourceEncoding=UTF-8


================================================
FILE: src/Mpesa/Auth/Authenticator.php
================================================
<?php

namespace SmoDav\Mpesa\Auth;

use Carbon\Carbon;
use GuzzleHttp\Exception\RequestException;
use SmoDav\Mpesa\Engine\Core;
use SmoDav\Mpesa\Exceptions\ConfigurationException;
use SmoDav\Mpesa\Exceptions\ErrorException;
use SmoDav\Mpesa\Repositories\Endpoint;

/**
 * Class Authenticator.
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 */
class Authenticator
{
    /**
     * Cache key.
     */
    const AC_TOKEN = 'MP:';

    /**
     * @var Core
     */
    private $core;

    public function __construct(Core $core)
    {
        $this->core = $core;
    }

    /**
     * Remove all the access tokens
     *
     * @return void
     */
    public function flushTokens()
    {
        collect($this->core->configRepository()->config('accounts'))
            ->each(function ($account) {
                $this->core->cache()->pull($this->getCacheKey($account['key'], $account['secret']));
            });
    }

    /**
     * Get the cache key for the given key and secret
     *
     * @param string $key
     * @param string $secret
     *
     * @return void
     */
    protected function getCacheKey($key, $secret)
    {
        return self::AC_TOKEN . "{$key}{$secret}";
    }

    /**
     * Get the access token required to transact.
     *
     * @return mixed
     *
     * @throws ConfigurationException
     */
    public function authenticate()
    {
        $key = $this->core->configRepository()->getAccountKey('key');
        $secret = $this->core->configRepository()->getAccountKey('secret');
        $cacheKey = $this->getCacheKey($key, $secret);

        if ($token = $this->core->cache()->get($cacheKey)) {
            return $token;
        }

        try {
            $response = $this->makeRequest($key, $secret);
            $body = json_decode($response->getBody());
            $this->saveCredentials($cacheKey, $body);

            return $body->access_token;
        } catch (RequestException $exception) {
            $message = $exception->getResponse() ?
               $exception->getResponse()->getReasonPhrase() :
               $exception->getMessage();

            throw $this->generateException($message);
        }
    }

    /**
     * Initiate the authentication request.
     *
     * @return mixed|\Psr\Http\Message\ResponseInterface
     */
    private function makeRequest($key, $secret)
    {
        $credentials = base64_encode($key . ':' . $secret);
        $endpoint = $this->core->configRepository()->url(Endpoint::MPESA_AUTH);

        return $this->core->client()->request('GET', $endpoint, [
            'headers' => [
                'Authorization' => 'Basic ' . $credentials,
                'Content-Type'  => 'application/json',
            ],
        ]);
    }

    /**
     * Store the credentials in the cache.
     *
     * @param $credentials
     */
    private function saveCredentials($key, $credentials)
    {
        $ttlSeconds = (int) $credentials->expires_in;
        $ttl = Carbon::now()->addSeconds($ttlSeconds)->subMinute();

        $this->core->cache()->put($key, $credentials->access_token, $ttl);
    }

    /**
     * Throw a contextual exception.
     *
     * @param $reason
     *
     * @return ErrorException|ConfigurationException
     */
    private function generateException($reason)
    {
        switch (strtolower($reason)) {
            case 'bad request: invalid credentials':
                return new ConfigurationException('Invalid consumer key and secret combination');
            default:
                return new ErrorException($reason);
        }
    }
}


================================================
FILE: src/Mpesa/C2B/Identity.php
================================================
<?php

namespace SmoDav\Mpesa\C2B;

use Carbon\Carbon;
use GuzzleHttp\Exception\RequestException;
use SmoDav\Mpesa\Repositories\Endpoint;
use SmoDav\Mpesa\Traits\UsesCore;
use SmoDav\Mpesa\Traits\Validates;

class Identity
{
    use UsesCore, Validates;

    /**
     * Prepare the number validation request
     *
     * @param int    $number
     * @param string $callback
     *
     * @return mixed
     */
    public function validate($number, $callback = null)
    {
        $this->validateNumber($number);

        $time = Carbon::now()->format('YmdHis');

        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');
        $passkey   = $this->core->configRepository()->getAccountKey('lnmo.passkey');
        $lmnoCallback  = $callback ?: $this->core->configRepository()->getAccountKey('lnmo.callback');

        $defaultCallback = $this->core->configRepository()->getAccountKey('id_validation_callback');
        $initiator = $this->core->configRepository()->getAccountKey('initiator');

        $body = [
            'Initiator'         => $initiator,
            'BusinessShortCode' => $shortCode,
            'Password'          => $this->password($shortCode, $passkey, $time),
            'Timestamp'         => $time,
            'TransactionType'   => 'CheckIdentity',
            'PhoneNumber'       => $number,
            'CallBackURL'       => $lmnoCallback ?: $defaultCallback,
            'TransactionDesc'   => ' '
        ];

        try {
            $response = $this->clientRequest(
                $body,
                $this->core->configRepository()->url(Endpoint::MPESA_ID_CHECK)
            );

            return json_decode($response->getBody());
        } catch (RequestException $exception) {
            return json_decode($exception->getResponse()->getBody());
        }
    }
}


================================================
FILE: src/Mpesa/C2B/Registrar.php
================================================
<?php

namespace SmoDav\Mpesa\C2B;

use Exception;
use GuzzleHttp\Exception\RequestException;
use InvalidArgumentException;
use SmoDav\Mpesa\Repositories\Endpoint;
use SmoDav\Mpesa\Traits\UsesCore;

class Registrar
{
    use UsesCore;

    /**
     * The short code to register callbacks for.
     *
     * @var string
     */
    protected $shortCode;

    /**
     * The validation callback.
     *
     * @var
     */
    protected $validationURL;

    /**
     * The confirmation callback.
     *
     * @var
     */
    protected $confirmationURL;

    /**
     * The status of the request in case a timeout occurs.
     *
     * @var string
     */
    protected $onTimeout = 'Completed';

    /**
     * The account to be used
     *
     * @var string
     */
    protected $account = null;

    /**
     * Submit the short code to be registered.
     *
     * @param $shortCode
     *
     * @return self
     */
    public function register($shortCode)
    {
        $this->shortCode = $shortCode;

        return $this;
    }

    /**
     * Submit the callback to be used for validation.
     *
     * @param $validationURL
     *
     * @return self
     */
    public function onValidation($validationURL)
    {
        $this->validationURL = $validationURL;

        return $this;
    }

    /**
     * Submit the callback to be used for confirmation.
     *
     * @param $confirmationURL
     *
     * @return self
     */
    public function onConfirmation($confirmationURL)
    {
        $this->confirmationURL = $confirmationURL;

        return $this;
    }

    /**
     * Set the transaction status on timeout.
     *
     * @param string $onTimeout
     *
     * @return self
     */
    public function onTimeout($onTimeout = 'Completed')
    {
        if ($onTimeout != 'Completed' && $onTimeout != 'Cancelled') {
            throw new InvalidArgumentException('Invalid timeout argument. Use Completed or Cancelled');
        }

        $this->onTimeout = $onTimeout;

        return $this;
    }

    /**
     * Set the account to be used.
     *
     * @param string $account
     *
     * @return self
     */
    public function usingAccount($account)
    {
        $this->account = $account;

        return $this;
    }

    /**
     * Initiate the registration process.
     *
     * @param string|null $shortCode
     * @param string|null $confirmationURL
     * @param string|null $validationURL
     * @param string|null $onTimeout
     * @param string|null $account
     *
     * @return mixed
     *
     * @throws \Exception
     */
    public function submit($shortCode = null, $confirmationURL = null, $validationURL = null, $onTimeout = null, $account = null)
    {
        if ($onTimeout) {
            $this->onTimeout($onTimeout);
        }

        $this->core->useAccount($account ?: $this->account);

        $body = [
            'ShortCode'       => $shortCode ?: $this->shortCode,
            'ResponseType'    => $this->onTimeout,
            'ConfirmationURL' => $confirmationURL ?: $this->confirmationURL,
            'ValidationURL'   => $validationURL ?: $this->validationURL
        ];

        try {
            $response = $this->clientRequest(
                $body,
                $this->core->configRepository()->url(Endpoint::MPESA_REGISTER)
            );

            return json_decode($response->getBody());
        } catch (RequestException $exception) {
            $message = $exception->getResponse() ?
               $exception->getResponse()->getReasonPhrase() :
               $exception->getMessage();

            throw new Exception($message);
        }
    }
}


================================================
FILE: src/Mpesa/C2B/STK.php
================================================
<?php

namespace SmoDav\Mpesa\C2B;

use Carbon\Carbon;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use SmoDav\Mpesa\Repositories\Endpoint;
use SmoDav\Mpesa\Traits\UsesCore;
use SmoDav\Mpesa\Traits\UsesSTKMethods;
use SmoDav\Mpesa\Traits\Validates;
use stdClass;

class STK
{
    use UsesCore;
    use Validates;
    use Macroable;
    use UsesSTKMethods;

    const CUSTOMER_BUYGOODS_ONLINE = 'CustomerBuyGoodsOnline';

    const CUSTOMER_PAYBILL_ONLINE = 'CustomerPayBillOnline';

    const VALID_COMMANDS = [
        self::CUSTOMER_BUYGOODS_ONLINE,
        self::CUSTOMER_PAYBILL_ONLINE,
    ];



    /**
     * The MPesa callback URL to be used for the request.
     *
     * @var string
     */
    protected $callback = null;

    /**
     * Set the callback on completion.
     *
     * @param string $callback
     *
     * @return self
     */
    public function setCallback(string $callback)
    {
        $this->callback = $callback;

        return $this;
    }

    /**
     * Prepare the STK Push request.
     *
     * @param int|null    $amount
     * @param int|null    $number
     * @param string|null $reference
     * @param string|null $description
     * @param string|null $account
     * @param string|null $command
     *
     * @return mixed
     */
    public function push(
        $amount = null,
        $number = null,
        $reference = null,
        $description = null,
        $account = null,
        $command = null
    ) {
        $this->set($amount, $number, $command);

        $this->core->useAccount($account ?: $this->account);
        $time = Carbon::now()->format('YmdHis');

        $paybill = $this->core->configRepository()->getAccountKey('lnmo.paybill');
        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');
        $passkey = $this->core->configRepository()->getAccountKey('lnmo.passkey');
        $callback = $this->callback ?: $this->core->configRepository()->getAccountKey('lnmo.callback');

        $partyB = $this->command == self::CUSTOMER_PAYBILL_ONLINE ? $shortCode : $paybill;

        $body = [
            'BusinessShortCode' => $shortCode,
            'Password' => $this->password($shortCode, $passkey, $time),
            'Timestamp' => $time,
            'TransactionType' => $this->command,
            'Amount' => $this->amount,
            'PartyA' => $this->number,
            'PartyB' => $partyB,
            'PhoneNumber' => $number ?: $this->number,
            'CallBackURL' => $callback,
            'AccountReference' => $reference ?: $this->reference,
            'TransactionDesc' => $description ?: $this->description,
        ];

        try {
            $response = $this->clientRequest(
                $body,
                $this->core->configRepository()->url(Endpoint::MPESA_LNMO)
            );

            return json_decode($response->getBody());
        } catch (RequestException $exception) {
            return json_decode($exception->getResponse()->getBody());
        }
    }

    /**
     * Validate an initialized transaction.
     *
     * @param string     $checkoutRequestID
     * @param mixed|null $account
     *
     * @return stdClass
     */
    public function validate($checkoutRequestID, $account = null)
    {
        $this->core->useAccount($account ?: $this->account);
        $time = Carbon::now()->format('YmdHis');

        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');
        $passkey = $this->core->configRepository()->getAccountKey('lnmo.passkey');

        $body = [
            'BusinessShortCode' => $shortCode,
            'Password' => $this->password($shortCode, $passkey, $time),
            'Timestamp' => $time,
            'CheckoutRequestID' => $checkoutRequestID,
        ];

        try {
            $response = $this->clientRequest(
                $body,
                $this->core->configRepository()->url(Endpoint::MPESA_LNMO_VALIDATE)
            );

            return json_decode($response->getBody());
        } catch (RequestException $exception) {
            return json_decode($exception->getResponse()->getBody());
        }
    }
}


================================================
FILE: src/Mpesa/C2B/Simulate.php
================================================
<?php

namespace SmoDav\Mpesa\C2B;

use GuzzleHttp\Exception\RequestException;
use InvalidArgumentException;
use SmoDav\Mpesa\Exceptions\ErrorException;
use SmoDav\Mpesa\Repositories\Endpoint;
use SmoDav\Mpesa\Traits\UsesCore;
use SmoDav\Mpesa\Traits\UsesSTKMethods;
use SmoDav\Mpesa\Traits\Validates;

class Simulate
{
    use UsesCore;
    use Validates;
    use UsesSTKMethods;

    /**
     * The transaction command to be used.
     *
     * @var string
     */
    protected $command = STK::CUSTOMER_PAYBILL_ONLINE;

    /**
     * Prepare the transaction simulation request
     *
     * @param int|null    $amount
     * @param int|null    $number
     * @param string|null $reference
     * @param string|null $account
     * @param string|null $command
     *
     * @throws ErrorException
     *
     * @return mixed
     */
    public function push(
        $amount = null,
        $number = null,
        $reference = null,
        $account = null,
        $command = null
    ) {
        $this->set($amount, $number, $command);
        $this->core->useAccount($account ?: $this->account);

        if (!$this->core->configRepository()->getAccountKey('sandbox')) {
            throw new ErrorException('Cannot simulate a transaction in the live environment.');
        }

        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');

        $body = [
            'CommandID'     => $this->command,
            'Amount'        => $this->amount,
            'Msisdn'        => $this->number,
            'ShortCode'     => $shortCode,
            'BillRefNumber' => $reference ?: $this->reference,
        ];

        try {
            $response = $this->clientRequest(
                $body,
                $this->core->configRepository()->url(Endpoint::MPESA_SIMULATE)
            );

            return json_decode($response->getBody());
        } catch (RequestException $exception) {
            return json_decode($exception->getResponse()->getBody());
        }
    }
}


================================================
FILE: src/Mpesa/Contracts/CacheStore.php
================================================
<?php

namespace SmoDav\Mpesa\Contracts;

/**
 * Interface CacheStore
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 */
interface CacheStore
{
    /**
     * Get the cache value from the store or a default value to be supplied.
     *
     * @param $key
     * @param $default
     *
     * @return mixed
     */
    public function get($key, $default = null);

    /**
     * Store an item in the cache.
     *
     * @param string                                     $key
     * @param mixed                                      $value
     * @param \DateTimeInterface|\DateInterval|float|int $seconds
     */
    public function put($key, $value, $seconds = null);

    /**
     * Get the cache or default value from the store and delete it.
     *
     * @param $key
     * @param $default
     *
     * @return mixed
     */
    public function pull($key, $default = null);
}


================================================
FILE: src/Mpesa/Contracts/ConfigurationStore.php
================================================
<?php

namespace SmoDav\Mpesa\Contracts;

/**
 * Interface ConfigurationStore
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 */
interface ConfigurationStore
{
    /**
     * Get the configuration value from the store or a default value to be supplied.
     *
     * @param $key
     * @param $default
     *
     * @return mixed
     */
    public function get($key, $default = null);
}


================================================
FILE: src/Mpesa/Engine/Core.php
================================================
<?php

namespace SmoDav\Mpesa\Engine;

use Exception;
use GuzzleHttp\ClientInterface;
use SmoDav\Mpesa\Auth\Authenticator;
use SmoDav\Mpesa\Contracts\CacheStore;
use SmoDav\Mpesa\Contracts\ConfigurationStore;
use SmoDav\Mpesa\Native\NativeCache;
use SmoDav\Mpesa\Native\NativeConfig;
use SmoDav\Mpesa\Repositories\ConfigurationRepository;

class Core
{
    /**
     * The configuration
     *
     * @var ConfigurationRepository
     */
    private $configRepository;

    /**
     * @var ClientInterface
     */
    private $client;

    /**
     * @var Authenticator
     */
    private $auth;

    /**
     * Core constructor.
     *
     * @param ClientInterface         $client
     * @param ConfigurationStore|null $configStore
     * @param CacheStore|null         $cacheStore
     */
    public function __construct(
        ClientInterface $client,
        ConfigurationStore $configStore = null,
        CacheStore $cacheStore = null
    ) {
        $this->client = $client;
        $this->setupStores($configStore, $cacheStore);
        $this->initialise();
    }

    /**
     * Use the native implementation of the stores.
     *
     * @return void
     */
    protected function setupStores(ConfigurationStore $configStore = null, CacheStore $cacheStore = null)
    {
        $this->configRepository = new ConfigurationRepository($configStore ?: new NativeConfig);
        $this->cache = $cacheStore ?: new NativeCache($this->configRepository->config('cache_location'));
    }

    /**
     * Initialise the Core process.
     */
    private function initialise()
    {
        $this->auth = new Authenticator($this);
    }

    /**
     * Get the configuration repository.
     *
     * @return ConfigurationRepository
     */
    public function configRepository()
    {
        return $this->configRepository;
    }

    /**
     * Get the cache store.
     *
     * @return CacheStore
     */
    public function cache()
    {
        return $this->cache;
    }

    /**
     * Get the client.
     *
     * @return ClientInterface
     */
    public function client()
    {
        return $this->client;
    }

    /**
     * Get the client.
     *
     * @return Authenticator
     */
    public function auth()
    {
        return $this->auth;
    }

    /**
     * Switch the current account
     *
     * @param string|null $account
     *
     * @throws Exception
     * @return self
     */
    public function useAccount($account = null)
    {
        $this->configRepository->useAccount($account);

        return $this;
    }

    /**
     * Switch the client instance.
     *
     * @param string|null $account
     *
     * @return self
     */
    public function useClient(ClientInterface $client)
    {
        $this->client = $client;

        return $this;
    }
}


================================================
FILE: src/Mpesa/Exceptions/ConfigurationException.php
================================================
<?php

namespace SmoDav\Mpesa\Exceptions;

use Exception;

class ConfigurationException extends Exception
{
}


================================================
FILE: src/Mpesa/Exceptions/ErrorException.php
================================================
<?php

namespace SmoDav\Mpesa\Exceptions;

use Exception;

class ErrorException extends Exception
{
}


================================================
FILE: src/Mpesa/Laravel/Facades/Identity.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * Class Identity.
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 *
 * @method static stdClass validate(string $number, callable $callback, string $account = null)
 *
 * @see \SmoDav\Mpesa\C2B\Registrar
 */
class Identity extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'mp_identity';
    }
}


================================================
FILE: src/Mpesa/Laravel/Facades/Registrar.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * Class Registrar.
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 *
 * @method static Registrar onConfirmation(string $confirmationURL)
 * @method static Registrar onTimeout(string $onTimeout)
 * @method static Registrar onValidation(string $validationURL)
 * @method static Registrar register(string $shortCode)
 * @method static stdClass submit(string $shortCode = null, string $confirmationURL = null, string $validationURL = null, string $onTimeout = null, string $account = null)
 * @method static Registrar usingAccount(string $account)
 *
 * @see \SmoDav\Mpesa\C2B\Registrar
 */
class Registrar extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'mp_registrar';
    }
}


================================================
FILE: src/Mpesa/Laravel/Facades/STK.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * Class STK
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 *
 * @method static STK from(string $number)
 * @method static STK request(string $amount)
 * @method static stdClass push(string $amount = null, string $number = null, string $reference = null, string $description = null, string $account = null)
 * @method static STK usingAccount(string $account)
 * @method static STK usingReference(string $reference, string $description)
 * @method static stdClass validate(string $checkoutRequestID, string $account = null)
 *
 * @see \SmoDav\Mpesa\C2B\STK
 */
class STK extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'mp_stk';
    }
}


================================================
FILE: src/Mpesa/Laravel/Facades/Simulate.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * Class Simulate.
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 *
 * @method static Simulate from(string $number)
 * @method static Simulate request(string $amount)
 * @method static stdClass push(string $amount = null, string $number = null, string $reference = null, string $command = null, string $account = null)
 * @method static Simulate setCommand(string $command)
 * @method static Simulate usingAccount(string $account)
 * @method static Simulate usingReference(string $reference)
 * @method static stdClass validate(string $checkoutRequestID, string $account = null)
 *
 * @see \SmoDav\Mpesa\C2B\Simulate
 */
class Simulate extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'mp_simulate';
    }
}


================================================
FILE: src/Mpesa/Laravel/ServiceProvider.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel;

use GuzzleHttp\Client;
use Illuminate\Support\ServiceProvider as RootProvider;
use SmoDav\Mpesa\C2B\Identity;
use SmoDav\Mpesa\C2B\Registrar;
use SmoDav\Mpesa\C2B\Simulate;
use SmoDav\Mpesa\C2B\STK;
use SmoDav\Mpesa\Contracts\CacheStore;
use SmoDav\Mpesa\Contracts\ConfigurationStore;
use SmoDav\Mpesa\Engine\Core;
use SmoDav\Mpesa\Laravel\Stores\LaravelCache;
use SmoDav\Mpesa\Laravel\Stores\LaravelConfig;

class ServiceProvider extends RootProvider
{
    /**
     * Bootstrap the application services.
     */
    public function boot()
    {
        $this->publishes([
            __DIR__ . '/../../../config/mpesa.php' => config_path('mpesa.php')
        ]);
    }

    /**
     * Registrar the application services.
     */
    public function register()
    {
        $this->bindInstances();

        $this->registerFacades();
    }

    /**
     * Bind the MPesa Instances.
     *
     * @return void
     */
    private function bindInstances()
    {
        $this->app->bind(ConfigurationStore::class, LaravelConfig::class);
        $this->app->bind(CacheStore::class, LaravelCache::class);
        $this->app->singleton(Core::class, function ($app) {
            $config = $app->make(ConfigurationStore::class);
            $cache = $app->make(CacheStore::class);

            return new Core(new Client, $config, $cache);
        });
    }

    private function registerFacades()
    {
        $this->app->bind('mp_stk', function () {
            return $this->app->make(STK::class);
        });

        $this->app->bind('mp_registrar', function () {
            return $this->app->make(Registrar::class);
        });

        $this->app->bind('mp_identity', function () {
            return $this->app->make(Identity::class);
        });

        $this->app->bind('mp_simulate', function () {
            return $this->app->make(Simulate::class);
        });
    }
}


================================================
FILE: src/Mpesa/Laravel/Stores/LaravelCache.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel\Stores;

use Illuminate\Cache\Repository;
use SmoDav\Mpesa\Contracts\CacheStore;

class LaravelCache implements CacheStore
{
    /**
     * @var MpesaRepository
     */
    private $repository;

    /**
     * LaravelConfiguration constructor.
     *
     * @param Repository $repository
     */
    public function __construct(Repository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * Get given config value from the configuration store.
     *
     * @param string $key
     * @param null   $default
     *
     * @return mixed
     */
    public function get($key, $default = null)
    {
        return $this->repository->get($key, $default);
    }

    /**
     * Store an item in the cache.
     *
     * @param string                                     $key
     * @param mixed                                      $value
     * @param \DateTimeInterface|\DateInterval|float|int $seconds
     */
    public function put($key, $value, $seconds = null)
    {
        $this->repository->put($key, $value, $seconds);
    }

    /**
     * Get the cache or default value from the store and delete it.
     *
     * @param $key
     * @param $default
     *
     * @return mixed
     */
    public function pull($key, $default = null)
    {
        return $this->repository->pull($key, $default);
    }
}


================================================
FILE: src/Mpesa/Laravel/Stores/LaravelConfig.php
================================================
<?php

namespace SmoDav\Mpesa\Laravel\Stores;

use Illuminate\Config\Repository;
use SmoDav\Mpesa\Contracts\ConfigurationStore;

class LaravelConfig implements ConfigurationStore
{
    /**
     * @var MpesaRepository
     */
    private $repository;

    /**
     * LaravelConfiguration constructor.
     *
     * @param Repository $repository
     */
    public function __construct(Repository $repository)
    {
        $this->repository = $repository;
    }

    /**
     * Get given config value from the configuration store.
     *
     * @param string $key
     * @param null   $default
     *
     * @return mixed
     */
    public function get($key, $default = null)
    {
        return $this->repository->get($key, $default);
    }
}


================================================
FILE: src/Mpesa/Native/NativeCache.php
================================================
<?php

namespace SmoDav\Mpesa\Native;

use Carbon\Carbon;
use DateInterval;
use DateTimeInterface;
use InvalidArgumentException;
use SmoDav\Mpesa\Contracts\CacheStore;
use SmoDav\Mpesa\Repositories\ConfigurationRepository;

/**
 * Class NativeCache
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 */
class NativeCache implements CacheStore
{
    /**
     * @var string
     */
    private $cacheFile;

    /**
     * NativeCache constructor.
     *
     * @param string|null $cacheDirectory
     */
    public function __construct($cacheDirectory = null)
    {
        $this->setUp($cacheDirectory);
    }

    /**
     * Setup the cache file location.
     *
     * @param string $cacheDirectory
     *
     * @return void
     */
    private function setUp($cacheDirectory = null)
    {
        $cacheDirectory = $cacheDirectory
            ?: (new ConfigurationRepository(new NativeConfig))->config('cache_location');

        $cacheDirectory = rtrim($cacheDirectory, '/');

        if (! is_dir($cacheDirectory)) {
            mkdir($cacheDirectory, 0755, true);
        }

        $this->cacheFile = $cacheDirectory . '/.mpc';

        if (!is_file($this->cacheFile)) {
            file_put_contents($this->cacheFile, serialize([]));
        }
    }

    /**
     * Get the cache value.
     *
     * @param      $key
     * @param null $default
     *
     * @return mixed|null
     */
    public function get($key, $default = null)
    {
        $cache = unserialize(file_get_contents($this->cacheFile));
        $cache = $this->cleanCache($cache, $this->cacheFile);

        if (! isset($cache[$key])) {
            return $default;
        }

        return $cache[$key]['v'];
    }

    /**
     * Store an item in the cache.
     *
     * @param string                                     $key
     * @param mixed                                      $value
     * @param \DateTimeInterface|\DateInterval|float|int $seconds
     *
     * @return bool
     */
    public function put($key, $value, $seconds = null)
    {
        $initial = unserialize(file_get_contents($this->cacheFile));
        $initial = $this->cleanCache($initial, $this->cacheFile, false);

        $payload = [$key => ['v' => $value, 't' => $this->formatTimeFromSeconds($seconds)]];
        $payload = serialize(array_merge($initial, $payload));

        return file_put_contents($this->cacheFile, $payload) !== false;
    }

    /**
     * Get the seconds and format it.
     *
     * @param int|null $seconds
     *
     * @return string|null
     */
    private function formatTimeFromSeconds($seconds = null)
    {
        if (!$seconds) {
            return null;
        }

        if ($seconds instanceof DateTimeInterface || $seconds instanceof DateInterval) {
            return Carbon::parse($seconds)->toDateTimeString();
        }

        if (!is_numeric($seconds)) {
            throw new InvalidArgumentException('The seconds argument should be numeric');
        }

        return Carbon::now()->addSeconds($seconds)->toDateTimeString();
    }

    /**
     * Clean out the expired items
     *
     * @param array  $initial
     * @param string $location
     * @param bool   $save
     *
     * @return array
     */
    private function cleanCache($initial, $location, $save = true)
    {
        $initial = array_filter($initial, function ($value) {
            if (! $value['t']) {
                return true;
            }

            if (Carbon::now()->gt(Carbon::parse($value['t']))) {
                return false;
            }

            return true;
        });

        if ($save) {
            file_put_contents($location, serialize($initial));
        }

        return $initial;
    }

    /**
     * Get the cache or default value from the store and delete it.
     *
     * @param $key
     * @param $default
     *
     * @return mixed
     */
    public function pull($key, $default = null)
    {
        $cache = unserialize(file_get_contents($this->cacheFile));
        $cache = $this->cleanCache($cache, $this->cacheFile, false);

        if (! isset($cache[$key])) {
            file_put_contents($this->cacheFile, serialize($cache));

            return $default;
        }

        $value = $cache[$key]['v'];

        unset($cache[$key]);
        file_put_contents($this->cacheFile, serialize($cache));

        return $value;
    }
}


================================================
FILE: src/Mpesa/Native/NativeConfig.php
================================================
<?php

namespace SmoDav\Mpesa\Native;

use SmoDav\Mpesa\Contracts\ConfigurationStore;

/**
 * Class NativeConfig
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 */
class NativeConfig implements ConfigurationStore
{
    /**
     * Mpesa configuration file.
     *
     * @var array
     */
    protected $config;

    /**
     * NativeConfig constructor.
     *
     * @param string|null $configPath
     */
    public function __construct($configPath = null)
    {
        $defaultConfig = require __DIR__ . '/../../../config/mpesa.php';
        $configPath = $configPath ?: __DIR__ . '/../../../../../../config/mpesa.php';
        $custom = [];

        if (is_file($configPath)) {
            $custom = require $configPath;
        }

        $this->config = ['mpesa' => array_merge($defaultConfig, $custom)];
    }

    /**
     * Get the configuration value.
     *
     * @param      $key
     * @param null $default
     *
     * @return mixed|null
     */
    public function get($key, $default = null)
    {
        $pieces = explode('.', $key);
        $config = $this->config;

        foreach ($pieces as $piece) {
            if (!isset($config[$piece])) {
                return $default;
            }

            $config = $config[$piece];
        }

        return $config;
    }
}


================================================
FILE: src/Mpesa/Repositories/ConfigurationRepository.php
================================================
<?php

namespace SmoDav\Mpesa\Repositories;

use Exception;
use SmoDav\Mpesa\Contracts\ConfigurationStore;

/**
 * Class ConfigurationRepository.
 *
 * @category PHP
 *
 * @author   David Mjomba <smodavprivate@gmail.com>
 */
class ConfigurationRepository
{
    const SANDBOX_URL = 'https://sandbox.safaricom.co.ke/';
    const PRODUCTION_URL = 'https://api.safaricom.co.ke/';

    /**
     * @var string
     */
    private $account;

    /**
     * @var ConfigurationStore
     */
    protected $store;

    /**
     * @var array
     */
    protected $config;

    /**
     * Build up a new instance.
     *
     * @param ConfigurationStore $store
     */
    public function __construct(ConfigurationStore $store)
    {
        $this->store = $store;
        $this->config = $this->store->get('mpesa');
        $this->useAccount();
    }

    /**
     * Get the configuration instance.
     */
    public function store()
    {
        return $this->store;
    }

    /**
     * Get the configuration value.
     *
     * @param string $key
     * @param mixed  $default
     *
     * @return mixed
     */
    public function config($key = null, $default = null)
    {
        if (!$key) {
            return $this->config;
        }

        $key = explode('.', $key);
        $value = $this->config;

        foreach ($key as $prop) {
            if (!isset($value[$prop])) {
                return $default;
            }

            $value = $value[$prop];
        }

        return $value;
    }

    /**
     * Set the account to be used when resoving configs.
     *
     * @param string $account
     *
     * @return self
     */
    public function useAccount($account = null)
    {
        $account = $account ?: $this->config('default');

        if (!$this->config("accounts.{$account}")) {
            throw new Exception('Invalid account selected');
        }

        $this->account = $account;

        return $this;
    }

    /**
     * Get a configuration value from the store.
     *
     * @param string $key
     * @param mixed  $default
     * @param string $account
     *
     * @return mixed
     */
    public function getAccountKey($key, $default = null)
    {
        return $this->config("accounts.{$this->account}.{$key}", $default);
    }

    /**
     * Get the endpoint relative to the current
     *
     * @param string $endpoint
     * @param string $account
     *
     * @return string
     */
    public function url($endpoint)
    {
        return $this->resolveUrl(
            $this->getAccountKey('sandbox', true) ? self::SANDBOX_URL : self::PRODUCTION_URL,
            $endpoint
        );
    }

    /**
     * Resolve the provided URL
     *
     * @param string $base
     * @param string $key
     *
     * @return string
     */
    private function resolveUrl($base, $key)
    {
        return $base . trim($key, '/');
    }
}


================================================
FILE: src/Mpesa/Repositories/Endpoint.php
================================================
<?php

namespace SmoDav\Mpesa\Repositories;

class Endpoint
{
    public const MPESA_AUTH = 'oauth/v1/generate?grant_type=client_credentials';

    public const MPESA_ID_CHECK = 'mpesa/checkidentity/v1/processrequest';

    public const MPESA_REGISTER = 'mpesa/c2b/v1/registerurl';

    public const MPESA_SIMULATE = 'mpesa/c2b/v1/simulate';

    public const MPESA_LNMO = 'mpesa/stkpush/v1/processrequest';

    public const MPESA_LNMO_VALIDATE = 'mpesa/stkpushquery/v1/query';

    public const CUSTOMER_PAYBILL_ONLINE = 'CustomerPayBillOnline';

    public const CUSTOMER_BUYGOODS_ONLINE = 'CustomerBuyGoodsOnline';
}


================================================
FILE: src/Mpesa/Support/Installer.php
================================================
<?php

namespace SmoDav\Mpesa\Support;

use Composer\Script\Event;

class Installer
{
    public static function install(Event $event)
    {
        $config    = __DIR__ . '/../../../config/mpesa.php';
        $configDir = self::getConfigDirectory($event);

        if (! is_file($configDir . '/mpesa.php')) {
            copy($config, $configDir . '/mpesa.php');
        }
    }

    public static function getConfigDirectory(Event $event)
    {
        $vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');
        $configDir = $vendorDir . '/../config';

        if (! is_dir($configDir)) {
            mkdir($configDir, 0755, true);
        }

        return $configDir;
    }
}


================================================
FILE: src/Mpesa/Support/helpers.php
================================================
<?php

use SmoDav\Mpesa\Repositories\EndpointsRepository;

function mpesa_endpoint($endpoint)
{
    return EndpointsRepository::build($endpoint);
}


================================================
FILE: src/Mpesa/Traits/UsesCore.php
================================================
<?php

namespace SmoDav\Mpesa\Traits;

use SmoDav\Mpesa\Engine\Core;

trait UsesCore
{
    /**
     * @var Core
     */
    protected $core;

    /**
     * @param Core $core
     */
    public function __construct(Core $core)
    {
        $this->core = $core;
    }

    /**
     * Initiate the request.
     *
     * @param array  $body
     * @param string $endpoint
     *
     * @return mixed|\Psr\Http\Message\ResponseInterface
     */
    private function clientRequest($body, $endpoint)
    {
        return $this->core->client()->request('POST', $endpoint, [
            'headers' => [
                'Authorization' => 'Bearer ' . $this->bearer(),
                'Content-Type'  => 'application/json',
            ],
            'json' => $body,
        ]);
    }

    /**
     * Get the bearer token.
     *
     * @return string
     */
    protected function bearer()
    {
        return $this->core->auth()->authenticate();
    }

    /**
     * Get the password for the
     *
     * @param string $shortCode
     * @param string $passkey
     * @param string $time
     *
     * @return string
     */
    private function password($shortCode, $passkey, $time)
    {
        return base64_encode($shortCode . $passkey . $time);
    }
}


================================================
FILE: src/Mpesa/Traits/UsesSTKMethods.php
================================================
<?php

namespace SmoDav\Mpesa\Traits;

use InvalidArgumentException;
use SmoDav\Mpesa\C2B\STK;

trait UsesSTKMethods
{
    /**
     * The mobile number.
     *
     * @var string
     */
    protected $number;

    /**
     * The amount to request.
     *
     * @var int
     */
    protected $amount;

    /**
     * The transaction reference.
     *
     * @var string
     */
    protected $reference;

    /**
     * The transaction description.
     *
     * @var string
     */
    protected $description;

    /**
     * The MPesa account to be used.
     *
     * @var string
     */
    protected $account = null;

    /**
     * The transaction command to be used.
     *
     * @var string
     */
    protected $command = self::CUSTOMER_PAYBILL_ONLINE;

    /**
     * Set the account to be used.
     *
     * @param string $account
     *
     * @return self
     */
    public function usingAccount($account)
    {
        $this->account = $account;

        return $this;
    }

    /**
     * Set the product reference number to bill the account.
     *
     * @param int    $reference
     * @param string $description
     *
     * @return self
     */
    public function usingReference($reference, $description)
    {
        $this->reference = $reference;
        $this->description = $description;

        return $this;
    }

    /**
     * Set the request amount to be deducted.
     *
     * @param int $amount
     *
     * @throws InvalidArgumentException
     *
     * @return self
     */
    public function request($amount)
    {
        $this->validateAmount($amount);

        $this->amount = $amount;

        return $this;
    }

    /**
     * Set the Mobile Subscriber Number to deduct the amount from.
     * Must be in format 2547XXXXXXXX.
     *
     * @param int $number
     *
     * @throws InvalidArgumentException
     *
     * @return self
     */
    public function from($number)
    {
        $this->validateNumber($number);

        $this->number = $number;

        return $this;
    }

    /**
     * Set the unique command for this transaction type.
     *
     * @param string $command
     *
     * @throws InvalidArgumentException
     *
     * @return self
     */
    public function setCommand($command)
    {
        if (!in_array($command, STK::VALID_COMMANDS)) {
            throw new InvalidArgumentException('Invalid command sent');
        }

        $this->command = $command;

        return $this;
    }

    /**
     * Set the properties that require validation.
     *
     * @param string|null $amount
     * @param string|null $number
     * @param string|null $command
     *
     * @return void
     */
    private function set($amount, $number, $command)
    {
        if ($amount) {
            $this->request($amount);
        }
        if ($number) {
            $this->from($number);
        }
        if ($command) {
            $this->setCommand($command);
        }
    }
}


================================================
FILE: src/Mpesa/Traits/Validates.php
================================================
<?php

namespace SmoDav\Mpesa\Traits;

use InvalidArgumentException;

trait Validates
{
    /**
     * Check if the provided number is valid.
     *
     * @param string $number
     *
     * @throws InvalidArgumentException
     *
     * @return void
     */
    protected function validateNumber($number)
    {
    }

    /**
     * Check if the amount is numeric.
     *
     * @param string|int|float $amount
     *
     * @throws InvalidArgumentException
     *
     * @return void
     */
    protected function validateAmount($amount)
    {
        if (!is_numeric($amount)) {
            throw new InvalidArgumentException('The amount must be numeric');
        }
    }
}


================================================
FILE: tests/TestCase.php
================================================
<?php

namespace SmoDav\Mpesa\Tests;

use GuzzleHttp\ClientInterface;
use PHPUnit\Framework\TestCase as PHPUnit;
use SmoDav\Mpesa\Engine\Core;
use SmoDav\Mpesa\Native\NativeConfig;
use SmoDav\Mpesa\Tests\Unit\NativeImplementationsTest;

class TestCase extends PHPUnit
{
    /**
     * Get the core instance.
     *
     * @param ClientInterface $client
     *
     * @return Core
     */
    protected function core(ClientInterface $client)
    {
        return new Core($client, new NativeConfig(NativeImplementationsTest::CONFIG_FILE));
    }
}


================================================
FILE: tests/Unit/AuthenticatorTest.php
================================================
<?php

namespace SmoDav\Mpesa\Tests\Unit;

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use SmoDav\Mpesa\Exceptions\ErrorException;
use SmoDav\Mpesa\Tests\TestCase as TestCase;

class AuthenticatorTest extends TestCase
{
    /*
     * Test that authenticator works.
     *
     * @test
     **/
    public function testCanAuthenticateUsingRequestAndCached()
    {
        $mock = new MockHandler([
            new Response(202, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),
        ]);

        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));

        $this->assertEquals('access', $core->auth()->authenticate());

        $mock = new MockHandler([
            new Response(403, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),
        ]);

        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));

        $this->assertEquals('access', $core->auth()->authenticate());

        $mock = new MockHandler([
            new Response(403, [], json_encode(['access_token' => 'access', 'expires_in' => 600000])),
        ]);

        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));

        $core->auth()->flushTokens();

        $this->expectException(ErrorException::class);

        $core->auth()->authenticate();
    }
}


================================================
FILE: tests/Unit/ConfigurationRepositoryTest.php
================================================
<?php

namespace SmoDav\Mpesa\Tests\Unit;

use SmoDav\Mpesa\Native\NativeConfig;
use SmoDav\Mpesa\Repositories\ConfigurationRepository;
use SmoDav\Mpesa\Tests\TestCase;

class ConfigurationRepositoryTest extends TestCase
{
    /**
     * Create the native configuration.
     *
     * @return NativeConfig
     */
    protected function nativeConfig()
    {
        return new NativeConfig(NativeImplementationsTest::CONFIG_FILE);
    }

    /**
     * @return void
     */
    public function testCanConstruct()
    {
        $repository = new ConfigurationRepository($this->nativeConfig());
        $this->assertInstanceOf(ConfigurationRepository::class, $repository);
    }

    /**
     * @return void
     */
    public function testCanExtractConfigAndAccountValues()
    {
        $repository = new ConfigurationRepository($this->nativeConfig());

        $this->assertEquals('test', $repository->config('default'));
        $this->assertTrue($repository->useAccount('test')->getAccountKey('sandbox'));
        $this->assertFalse($repository->useAccount('production')->getAccountKey('sandbox'));

        $this->assertEquals('default', $repository->config('fake_config', 'default'));
        $this->assertEquals(
            'default_account',
            $repository->useAccount('production')->getAccountKey('fake_account_config', 'default_account')
        );
    }

    /**
     * @return void
     */
    public function testCanResolveUrl()
    {
        $repository = new ConfigurationRepository($this->nativeConfig());
        $this->assertEquals(ConfigurationRepository::SANDBOX_URL . 'test-url', $repository->url('test-url'));
        $this->assertEquals(
            ConfigurationRepository::PRODUCTION_URL . 'test-url',
            $repository->useAccount('production')->url('test-url')
        );
    }
}


================================================
FILE: tests/Unit/NativeImplementationsTest.php
================================================
<?php

namespace SmoDav\Mpesa\Tests\Unit;

use InvalidArgumentException;
use SmoDav\Mpesa\Native\NativeCache;
use SmoDav\Mpesa\Native\NativeConfig;
use SmoDav\Mpesa\Tests\TestCase;

class NativeImplementationsTest extends TestCase
{
    const CONFIG_FILE = __DIR__ . '/../files/mpesa.php';
    const CACHE_LOCATION = __DIR__ . '/../files/cache';

    /**
     * @return void
     */
    public function testCanUseNativeConfig()
    {
        $config = new NativeConfig();
        $this->assertEquals(true, $config->get('mpesa.accounts.staging.sandbox'));

        $config = new NativeConfig(self::CONFIG_FILE);
        $this->assertEquals(true, $config->get('mpesa.accounts.test.sandbox'));
    }

    /**
     * @return void
     */
    public function testCanGetWholeConfiguration()
    {
        $config = new NativeConfig(self::CONFIG_FILE);

        $this->assertEquals(require(self::CONFIG_FILE), $config->get('mpesa'));
    }

    /**
     * @return void
     */
    public function testCanUseNativeCache()
    {
        $cache = new NativeCache(self::CACHE_LOCATION);

        $cache->put('test', 123, 10);
        $this->assertEquals(123, $cache->get('test'));
    }

    /**
     * @return void
     */
    public function testCanOverwriteCacheItem()
    {
        $cache = new NativeCache(self::CACHE_LOCATION);

        $cache->put('overwrite', 123, 10);
        $this->assertEquals(123, $cache->get('overwrite'));

        $cache->put('overwrite', 456, 10);
        $this->assertEquals(456, $cache->get('overwrite'));
    }

    /**
     * @return void
     */
    public function testCacheExpires()
    {
        $cache = new NativeCache(self::CACHE_LOCATION);

        $cache->put('expire', 123, 1);
        $this->assertEquals(123, $cache->get('expire'));
        sleep(1);
        $this->assertEquals(null, $cache->get('expire'));
    }

    /**
     * @return void
     */
    public function testCanConstructWithoutConfig()
    {
        $cache = new NativeCache;
        $cache->put('without_config', 'YES', 5);
        $this->assertEquals('YES', $cache->get('without_config'));
    }

    /**
     * @return void
     */
    public function testTTLImplementation()
    {
        $cache = new NativeCache(self::CACHE_LOCATION);

        $this->expectException(InvalidArgumentException::class);
        $cache->put('expire', 123, 'fake');
    }

    /**
     * @return void
     */
    public function testShouldPullCacheItem()
    {
        $cache = new NativeCache(self::CACHE_LOCATION);
        $cache->put('to_pull', 'YES', 5);
        $this->assertEquals('YES', $cache->get('to_pull'));
        $this->assertEquals('YES', $cache->pull('to_pull'));
        $this->assertEquals('pulled', $cache->pull('to_pull', 'pulled'));
    }
}


================================================
FILE: tests/Unit/RegistrarTest.php
================================================
<?php

namespace SmoDav\Mpesa\Tests\Unit;

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use SmoDav\Mpesa\C2B\Registrar;
use SmoDav\Mpesa\Tests\TestCase as TestCase;

class RegistrarTest extends TestCase
{
    /*
     * Test that authenticator works.
     *
     * @test
     **/
    public function testRegisterUrls()
    {
        $mock = new MockHandler([
            new Response(200, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),
            new Response(200, [], json_encode([
                'OriginatorConverstionID' => '123',
                'ConversationID' => '500',
                'ResponseDescription' => 'Success',
            ])),
        ]);

        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));
        $core->auth()->flushTokens();

        $registrar = new Registrar($core);

        $response = $registrar->submit(123456, 'http://example.com', 'http://example.com');

        $this->assertEquals('123', $response->OriginatorConverstionID);
        $this->assertEquals('500', $response->ConversationID);
        $this->assertEquals('Success', $response->ResponseDescription);
    }
}


================================================
FILE: tests/Unit/STKTest.php
================================================
<?php

namespace SmoDav\Mpesa\Tests\Unit;

use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use SmoDav\Mpesa\C2B\STK;
use SmoDav\Mpesa\Tests\TestCase as TestCase;

class STKTest extends TestCase
{
    /*
     * Test that authenticator works.
     *
     * @test
     **/
    public function testPushRequest()
    {
        $response = [
            'MerchantRequestID' => '19465-780693-1',
            'CheckoutRequestID' => 'ws_CO_27072017154747416',
            'ResponseCode' => 0,
            'ResponseDescription' => 'Success. Request accepted for processing',
            'CustomerMessage' => 'Success. Request accepted for processing',
        ];

        $mock = new MockHandler([
            new Response(200, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),
            new Response(200, [], json_encode($response)),
        ]);

        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));
        $core->auth()->flushTokens();

        $stk = new STK($core);

        $response = $stk->push(100, 254722000000, 'Test', 'Awesome');

        $this->assertEquals('19465-780693-1', $response->MerchantRequestID);
        $this->assertEquals('ws_CO_27072017154747416', $response->CheckoutRequestID);
        $this->assertEquals(0, $response->ResponseCode);
        $this->assertEquals('Success. Request accepted for processing', $response->ResponseDescription);
        $this->assertEquals('Success. Request accepted for processing', $response->CustomerMessage);
    }

    public function testValidateTransaction()
    {
        $response = [
            'MerchantRequestID' => '19465-780693-1',
            'CheckoutRequestID' => 'ws_CO_27072017154747416',
            'ResponseCode' => 0,
            'ResponseDescription' => 'Success. Request accepted for processing',
            'ResultCode' => 0,
            'ResultDesc' => 'The service request is processed successfully.'
        ];

        $mock = new MockHandler([
            new Response(200, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),
            new Response(200, [], json_encode($response)),
        ]);

        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));
        $core->auth()->flushTokens();

        $stk = new STK($core);

        $response = $stk->validate('ws_CO_27072017154747416');

        $this->assertEquals('19465-780693-1', $response->MerchantRequestID);
        $this->assertEquals('ws_CO_27072017154747416', $response->CheckoutRequestID);
        $this->assertEquals(0, $response->ResponseCode);
        $this->assertEquals('Success. Request accepted for processing', $response->ResponseDescription);
        $this->assertEquals(0, $response->ResultCode);
        $this->assertEquals('The service request is processed successfully.', $response->ResultDesc);
    }
}


================================================
FILE: tests/files/.gitignore
================================================
/cache

================================================
FILE: tests/files/mpesa.php
================================================
<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Default Account
    |--------------------------------------------------------------------------
    |
    | This is the default account to be used when none is specified.
    */

    'default' => 'test',

    /*
    |--------------------------------------------------------------------------
    | Native File Cache Location
    |--------------------------------------------------------------------------
    |
    | When using the Native Cache driver, this will be the relative directory
    | where the cache information will be stored.
    */

    'cache_location' => __DIR__ . '/cache',

    /*
    |--------------------------------------------------------------------------
    | Accounts
    |--------------------------------------------------------------------------
    |
    | These are the accounts that can be used with the package. You can configure
    | as many as needed. Two have been setup for you.
    |
    | Sandbox: Determines whether to use the sandbox, Possible values: sandbox | production
    | Initiator: This is the username used to authenticate the transaction request
    | LNMO:
    |    paybill: Your paybill number
    |    shortcode: Your business shortcode
    |    passkey: The passkey for the paybill number
    |    callback: Endpoint that will be be queried on completion or failure of the transaction.
    |
    */

    'accounts' => [
        'test' => [
            'sandbox' => true,
            'key' => 'key1',
            'secret' => 'secret1',
            'initiator' => 'apitest363',
            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            'lnmo' => [
                'paybill' => 174379,
                'shortcode' => 174379,
                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',
                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            ]
        ],

        'production' => [
            'sandbox' => false,
            'key' => 'key2',
            'secret' => 'secret2',
            'initiator' => 'apitest363',
            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            'lnmo' => [
                'paybill' => 174379,
                'shortcode' => 174379,
                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',
                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',
            ]
        ],
    ],
];
Download .txt
gitextract_nvdhfbhw/

├── .github/
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .php_cs
├── .phpunit.result.cache
├── .travis.yml
├── CHANGELOG.md
├── LICENSE.txt
├── README.md
├── composer.json
├── config/
│   └── mpesa.php
├── phpunit.xml
├── sonar-project.properties
├── src/
│   └── Mpesa/
│       ├── Auth/
│       │   └── Authenticator.php
│       ├── C2B/
│       │   ├── Identity.php
│       │   ├── Registrar.php
│       │   ├── STK.php
│       │   └── Simulate.php
│       ├── Contracts/
│       │   ├── CacheStore.php
│       │   └── ConfigurationStore.php
│       ├── Engine/
│       │   └── Core.php
│       ├── Exceptions/
│       │   ├── ConfigurationException.php
│       │   └── ErrorException.php
│       ├── Laravel/
│       │   ├── Facades/
│       │   │   ├── Identity.php
│       │   │   ├── Registrar.php
│       │   │   ├── STK.php
│       │   │   └── Simulate.php
│       │   ├── ServiceProvider.php
│       │   └── Stores/
│       │       ├── LaravelCache.php
│       │       └── LaravelConfig.php
│       ├── Native/
│       │   ├── NativeCache.php
│       │   └── NativeConfig.php
│       ├── Repositories/
│       │   ├── ConfigurationRepository.php
│       │   └── Endpoint.php
│       ├── Support/
│       │   ├── Installer.php
│       │   └── helpers.php
│       └── Traits/
│           ├── UsesCore.php
│           ├── UsesSTKMethods.php
│           └── Validates.php
└── tests/
    ├── TestCase.php
    ├── Unit/
    │   ├── AuthenticatorTest.php
    │   ├── ConfigurationRepositoryTest.php
    │   ├── NativeImplementationsTest.php
    │   ├── RegistrarTest.php
    │   └── STKTest.php
    └── files/
        ├── .gitignore
        └── mpesa.php
Download .txt
SYMBOL INDEX (124 symbols across 32 files)

FILE: src/Mpesa/Auth/Authenticator.php
  class Authenticator (line 19) | class Authenticator
    method __construct (line 31) | public function __construct(Core $core)
    method flushTokens (line 41) | public function flushTokens()
    method getCacheKey (line 57) | protected function getCacheKey($key, $secret)
    method authenticate (line 69) | public function authenticate()
    method makeRequest (line 99) | private function makeRequest($key, $secret)
    method saveCredentials (line 117) | private function saveCredentials($key, $credentials)
    method generateException (line 132) | private function generateException($reason)

FILE: src/Mpesa/C2B/Identity.php
  class Identity (line 11) | class Identity
    method validate (line 23) | public function validate($number, $callback = null)

FILE: src/Mpesa/C2B/Registrar.php
  class Registrar (line 11) | class Registrar
    method register (line 57) | public function register($shortCode)
    method onValidation (line 71) | public function onValidation($validationURL)
    method onConfirmation (line 85) | public function onConfirmation($confirmationURL)
    method onTimeout (line 99) | public function onTimeout($onTimeout = 'Completed')
    method usingAccount (line 117) | public function usingAccount($account)
    method submit (line 137) | public function submit($shortCode = null, $confirmationURL = null, $va...

FILE: src/Mpesa/C2B/STK.php
  class STK (line 15) | class STK
    method setCallback (line 47) | public function setCallback(string $callback)
    method push (line 66) | public function push(
    method validate (line 120) | public function validate($checkoutRequestID, $account = null)

FILE: src/Mpesa/C2B/Simulate.php
  class Simulate (line 13) | class Simulate
    method push (line 39) | public function push(

FILE: src/Mpesa/Contracts/CacheStore.php
  type CacheStore (line 12) | interface CacheStore
    method get (line 22) | public function get($key, $default = null);
    method put (line 31) | public function put($key, $value, $seconds = null);
    method pull (line 41) | public function pull($key, $default = null);

FILE: src/Mpesa/Contracts/ConfigurationStore.php
  type ConfigurationStore (line 12) | interface ConfigurationStore
    method get (line 22) | public function get($key, $default = null);

FILE: src/Mpesa/Engine/Core.php
  class Core (line 14) | class Core
    method __construct (line 40) | public function __construct(
    method setupStores (line 55) | protected function setupStores(ConfigurationStore $configStore = null,...
    method initialise (line 64) | private function initialise()
    method configRepository (line 74) | public function configRepository()
    method cache (line 84) | public function cache()
    method client (line 94) | public function client()
    method auth (line 104) | public function auth()
    method useAccount (line 117) | public function useAccount($account = null)
    method useClient (line 131) | public function useClient(ClientInterface $client)

FILE: src/Mpesa/Exceptions/ConfigurationException.php
  class ConfigurationException (line 7) | class ConfigurationException extends Exception

FILE: src/Mpesa/Exceptions/ErrorException.php
  class ErrorException (line 7) | class ErrorException extends Exception

FILE: src/Mpesa/Laravel/Facades/Identity.php
  class Identity (line 18) | class Identity extends Facade
    method getFacadeAccessor (line 25) | protected static function getFacadeAccessor()

FILE: src/Mpesa/Laravel/Facades/Registrar.php
  class Registrar (line 23) | class Registrar extends Facade
    method getFacadeAccessor (line 30) | protected static function getFacadeAccessor()

FILE: src/Mpesa/Laravel/Facades/STK.php
  class STK (line 23) | class STK extends Facade
    method getFacadeAccessor (line 30) | protected static function getFacadeAccessor()

FILE: src/Mpesa/Laravel/Facades/Simulate.php
  class Simulate (line 24) | class Simulate extends Facade
    method getFacadeAccessor (line 31) | protected static function getFacadeAccessor()

FILE: src/Mpesa/Laravel/ServiceProvider.php
  class ServiceProvider (line 17) | class ServiceProvider extends RootProvider
    method boot (line 22) | public function boot()
    method register (line 32) | public function register()
    method bindInstances (line 44) | private function bindInstances()
    method registerFacades (line 56) | private function registerFacades()

FILE: src/Mpesa/Laravel/Stores/LaravelCache.php
  class LaravelCache (line 8) | class LaravelCache implements CacheStore
    method __construct (line 20) | public function __construct(Repository $repository)
    method get (line 33) | public function get($key, $default = null)
    method put (line 45) | public function put($key, $value, $seconds = null)
    method pull (line 58) | public function pull($key, $default = null)

FILE: src/Mpesa/Laravel/Stores/LaravelConfig.php
  class LaravelConfig (line 8) | class LaravelConfig implements ConfigurationStore
    method __construct (line 20) | public function __construct(Repository $repository)
    method get (line 33) | public function get($key, $default = null)

FILE: src/Mpesa/Native/NativeCache.php
  class NativeCache (line 19) | class NativeCache implements CacheStore
    method __construct (line 31) | public function __construct($cacheDirectory = null)
    method setUp (line 43) | private function setUp($cacheDirectory = null)
    method get (line 69) | public function get($key, $default = null)
    method put (line 90) | public function put($key, $value, $seconds = null)
    method formatTimeFromSeconds (line 108) | private function formatTimeFromSeconds($seconds = null)
    method cleanCache (line 134) | private function cleanCache($initial, $location, $save = true)
    method pull (line 163) | public function pull($key, $default = null)

FILE: src/Mpesa/Native/NativeConfig.php
  class NativeConfig (line 14) | class NativeConfig implements ConfigurationStore
    method __construct (line 28) | public function __construct($configPath = null)
    method get (line 49) | public function get($key, $default = null)

FILE: src/Mpesa/Repositories/ConfigurationRepository.php
  class ConfigurationRepository (line 15) | class ConfigurationRepository
    method __construct (line 40) | public function __construct(ConfigurationStore $store)
    method store (line 50) | public function store()
    method config (line 63) | public function config($key = null, $default = null)
    method useAccount (line 90) | public function useAccount($account = null)
    method getAccountKey (line 112) | public function getAccountKey($key, $default = null)
    method url (line 125) | public function url($endpoint)
    method resolveUrl (line 141) | private function resolveUrl($base, $key)

FILE: src/Mpesa/Repositories/Endpoint.php
  class Endpoint (line 5) | class Endpoint

FILE: src/Mpesa/Support/Installer.php
  class Installer (line 7) | class Installer
    method install (line 9) | public static function install(Event $event)
    method getConfigDirectory (line 19) | public static function getConfigDirectory(Event $event)

FILE: src/Mpesa/Support/helpers.php
  function mpesa_endpoint (line 5) | function mpesa_endpoint($endpoint)

FILE: src/Mpesa/Traits/UsesCore.php
  type UsesCore (line 7) | trait UsesCore
    method __construct (line 17) | public function __construct(Core $core)
    method clientRequest (line 30) | private function clientRequest($body, $endpoint)
    method bearer (line 46) | protected function bearer()
    method password (line 60) | private function password($shortCode, $passkey, $time)

FILE: src/Mpesa/Traits/UsesSTKMethods.php
  type UsesSTKMethods (line 8) | trait UsesSTKMethods
    method usingAccount (line 59) | public function usingAccount($account)
    method usingReference (line 74) | public function usingReference($reference, $description)
    method request (line 91) | public function request($amount)
    method from (line 110) | public function from($number)
    method setCommand (line 128) | public function setCommand($command)
    method set (line 148) | private function set($amount, $number, $command)

FILE: src/Mpesa/Traits/Validates.php
  type Validates (line 7) | trait Validates
    method validateNumber (line 18) | protected function validateNumber($number)
    method validateAmount (line 31) | protected function validateAmount($amount)

FILE: tests/TestCase.php
  class TestCase (line 11) | class TestCase extends PHPUnit
    method core (line 20) | protected function core(ClientInterface $client)

FILE: tests/Unit/AuthenticatorTest.php
  class AuthenticatorTest (line 12) | class AuthenticatorTest extends TestCase
    method testCanAuthenticateUsingRequestAndCached (line 19) | public function testCanAuthenticateUsingRequestAndCached()

FILE: tests/Unit/ConfigurationRepositoryTest.php
  class ConfigurationRepositoryTest (line 9) | class ConfigurationRepositoryTest extends TestCase
    method nativeConfig (line 16) | protected function nativeConfig()
    method testCanConstruct (line 24) | public function testCanConstruct()
    method testCanExtractConfigAndAccountValues (line 33) | public function testCanExtractConfigAndAccountValues()
    method testCanResolveUrl (line 51) | public function testCanResolveUrl()

FILE: tests/Unit/NativeImplementationsTest.php
  class NativeImplementationsTest (line 10) | class NativeImplementationsTest extends TestCase
    method testCanUseNativeConfig (line 18) | public function testCanUseNativeConfig()
    method testCanGetWholeConfiguration (line 30) | public function testCanGetWholeConfiguration()
    method testCanUseNativeCache (line 40) | public function testCanUseNativeCache()
    method testCanOverwriteCacheItem (line 51) | public function testCanOverwriteCacheItem()
    method testCacheExpires (line 65) | public function testCacheExpires()
    method testCanConstructWithoutConfig (line 78) | public function testCanConstructWithoutConfig()
    method testTTLImplementation (line 88) | public function testTTLImplementation()
    method testShouldPullCacheItem (line 99) | public function testShouldPullCacheItem()

FILE: tests/Unit/RegistrarTest.php
  class RegistrarTest (line 12) | class RegistrarTest extends TestCase
    method testRegisterUrls (line 19) | public function testRegisterUrls()

FILE: tests/Unit/STKTest.php
  class STKTest (line 12) | class STKTest extends TestCase
    method testPushRequest (line 19) | public function testPushRequest()
    method testValidateTransaction (line 48) | public function testValidateTransaction()
Condensed preview — 46 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (91K chars).
[
  {
    "path": ".github/workflows/build.yml",
    "chars": 898,
    "preview": "name: Build\n\nconcurrency:\n  group: production\n  cancel-in-progress: true\n\non:\n  push:\n    branches:\n      - master\n  pul"
  },
  {
    "path": ".gitignore",
    "chars": 112,
    "preview": "/.idea\n/vendor\ncomposer.lock\n.php_cs.cache\nindex.php\n/cache\n/.phpintel\n.php-cs-fixer.cache\nclover.xml\njunit.xml\n"
  },
  {
    "path": ".php_cs",
    "chars": 2667,
    "preview": "<?php\nreturn PhpCsFixer\\Config::create()\n    ->setRiskyAllowed(true)\n    ->setRules(\n        [\n            'array_syntax"
  },
  {
    "path": ".phpunit.result.cache",
    "chars": 2054,
    "preview": "{\"version\":1,\"defects\":{\"SmoDav\\\\Mpesa\\\\Tests\\\\Unit\\\\AuthenticatorTest::testCanAuthenticateUsingRequestAndCached\":8,\"Smo"
  },
  {
    "path": ".travis.yml",
    "chars": 201,
    "preview": "dist: trusty\nlanguage: php\nphp:\n  - '8.2'\n  - '8.3'\n  - '8.4'\n  - '8.5'\n\nbefore_script:\n  - composer self-update\n  - com"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1375,
    "preview": "## CHANGELOG\n\n### 2019-09-26 :: v5.0.0\n\n#### NativeCache\n\n - Takes a custom storage path as first constructor argument. "
  },
  {
    "path": "LICENSE.txt",
    "chars": 1072,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 SmoDav\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "README.md",
    "chars": 15064,
    "preview": "# M-PESA API Package\n[![Build Status](https://travis-ci.org/SmoDav/mpesa.svg?branch=master)](https://travis-ci.org/SmoDa"
  },
  {
    "path": "composer.json",
    "chars": 1484,
    "preview": "{\n    \"name\": \"smodav/mpesa\",\n    \"description\": \"M-Pesa API implementation\",\n    \"type\": \"library\",\n    \"keywords\": [\n "
  },
  {
    "path": "config/mpesa.php",
    "chars": 2610,
    "preview": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Default Acc"
  },
  {
    "path": "phpunit.xml",
    "chars": 610,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    backupGlobals=\"false\"\n    bootstrap=\"vendor/autoload.php\"\n    colors"
  },
  {
    "path": "sonar-project.properties",
    "chars": 423,
    "preview": "sonar.projectKey=smodav_m-pesa\nsonar.organization=smodav\n\n# This is the name and version displayed in the SonarCloud UI."
  },
  {
    "path": "src/Mpesa/Auth/Authenticator.php",
    "chars": 3600,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Auth;\n\nuse Carbon\\Carbon;\nuse GuzzleHttp\\Exception\\RequestException;\nuse SmoDav\\Mpesa\\Engi"
  },
  {
    "path": "src/Mpesa/C2B/Identity.php",
    "chars": 1844,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse Carbon\\Carbon;\nuse GuzzleHttp\\Exception\\RequestException;\nuse SmoDav\\Mpesa\\Repos"
  },
  {
    "path": "src/Mpesa/C2B/Registrar.php",
    "chars": 3640,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse Exception;\nuse GuzzleHttp\\Exception\\RequestException;\nuse InvalidArgumentExcepti"
  },
  {
    "path": "src/Mpesa/C2B/STK.php",
    "chars": 4222,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse Carbon\\Carbon;\nuse GuzzleHttp\\Exception\\RequestException;\nuse Illuminate\\Support"
  },
  {
    "path": "src/Mpesa/C2B/Simulate.php",
    "chars": 2016,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse GuzzleHttp\\Exception\\RequestException;\nuse InvalidArgumentException;\nuse SmoDav\\"
  },
  {
    "path": "src/Mpesa/Contracts/CacheStore.php",
    "chars": 910,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Contracts;\n\n/**\n * Interface CacheStore\n *\n * @category PHP\n *\n * @author   David Mjomba <"
  },
  {
    "path": "src/Mpesa/Contracts/ConfigurationStore.php",
    "chars": 416,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Contracts;\n\n/**\n * Interface ConfigurationStore\n *\n * @category PHP\n *\n * @author   David "
  },
  {
    "path": "src/Mpesa/Engine/Core.php",
    "chars": 2802,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Engine;\n\nuse Exception;\nuse GuzzleHttp\\ClientInterface;\nuse SmoDav\\Mpesa\\Auth\\Authenticato"
  },
  {
    "path": "src/Mpesa/Exceptions/ConfigurationException.php",
    "chars": 110,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Exceptions;\n\nuse Exception;\n\nclass ConfigurationException extends Exception\n{\n}\n"
  },
  {
    "path": "src/Mpesa/Exceptions/ErrorException.php",
    "chars": 102,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Exceptions;\n\nuse Exception;\n\nclass ErrorException extends Exception\n{\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/Identity.php",
    "chars": 548,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class Identity.\n *\n * @ca"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/Registrar.php",
    "chars": 930,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class Registrar.\n *\n * @c"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/STK.php",
    "chars": 891,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class STK\n *\n * @category"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/Simulate.php",
    "chars": 962,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class Simulate.\n *\n * @ca"
  },
  {
    "path": "src/Mpesa/Laravel/ServiceProvider.php",
    "chars": 1921,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel;\n\nuse GuzzleHttp\\Client;\nuse Illuminate\\Support\\ServiceProvider as RootProvider;\nu"
  },
  {
    "path": "src/Mpesa/Laravel/Stores/LaravelCache.php",
    "chars": 1379,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Stores;\n\nuse Illuminate\\Cache\\Repository;\nuse SmoDav\\Mpesa\\Contracts\\CacheStore;\n\n"
  },
  {
    "path": "src/Mpesa/Laravel/Stores/LaravelConfig.php",
    "chars": 745,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Stores;\n\nuse Illuminate\\Config\\Repository;\nuse SmoDav\\Mpesa\\Contracts\\Configuratio"
  },
  {
    "path": "src/Mpesa/Native/NativeCache.php",
    "chars": 4394,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Native;\n\nuse Carbon\\Carbon;\nuse DateInterval;\nuse DateTimeInterface;\nuse InvalidArgumentEx"
  },
  {
    "path": "src/Mpesa/Native/NativeConfig.php",
    "chars": 1327,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Native;\n\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\n\n/**\n * Class NativeConfig\n *\n * @"
  },
  {
    "path": "src/Mpesa/Repositories/ConfigurationRepository.php",
    "chars": 2884,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Repositories;\n\nuse Exception;\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\n\n/**\n * Class"
  },
  {
    "path": "src/Mpesa/Repositories/Endpoint.php",
    "chars": 621,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Repositories;\n\nclass Endpoint\n{\n    public const MPESA_AUTH = 'oauth/v1/generate?grant_typ"
  },
  {
    "path": "src/Mpesa/Support/Installer.php",
    "chars": 697,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Support;\n\nuse Composer\\Script\\Event;\n\nclass Installer\n{\n    public static function install"
  },
  {
    "path": "src/Mpesa/Support/helpers.php",
    "chars": 148,
    "preview": "<?php\n\nuse SmoDav\\Mpesa\\Repositories\\EndpointsRepository;\n\nfunction mpesa_endpoint($endpoint)\n{\n    return EndpointsRepo"
  },
  {
    "path": "src/Mpesa/Traits/UsesCore.php",
    "chars": 1256,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Traits;\n\nuse SmoDav\\Mpesa\\Engine\\Core;\n\ntrait UsesCore\n{\n    /**\n     * @var Core\n     */\n"
  },
  {
    "path": "src/Mpesa/Traits/UsesSTKMethods.php",
    "chars": 2959,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Traits;\n\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\C2B\\STK;\n\ntrait UsesSTKMethods\n{\n "
  },
  {
    "path": "src/Mpesa/Traits/Validates.php",
    "chars": 680,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Traits;\n\nuse InvalidArgumentException;\n\ntrait Validates\n{\n    /**\n     * Check if the prov"
  },
  {
    "path": "tests/TestCase.php",
    "chars": 547,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests;\n\nuse GuzzleHttp\\ClientInterface;\nuse PHPUnit\\Framework\\TestCase as PHPUnit;\nuse Smo"
  },
  {
    "path": "tests/Unit/AuthenticatorTest.php",
    "chars": 1422,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\Han"
  },
  {
    "path": "tests/Unit/ConfigurationRepositoryTest.php",
    "chars": 1822,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse SmoDav\\Mpesa\\Native\\NativeConfig;\nuse SmoDav\\Mpesa\\Repositories\\Configura"
  },
  {
    "path": "tests/Unit/NativeImplementationsTest.php",
    "chars": 2754,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\Native\\NativeCache;\nuse SmoDav"
  },
  {
    "path": "tests/Unit/RegistrarTest.php",
    "chars": 1232,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\Han"
  },
  {
    "path": "tests/Unit/STKTest.php",
    "chars": 2929,
    "preview": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\Han"
  },
  {
    "path": "tests/files/.gitignore",
    "chars": 6,
    "preview": "/cache"
  },
  {
    "path": "tests/files/mpesa.php",
    "chars": 2634,
    "preview": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Default Acc"
  }
]

About this extraction

This page contains the full source code of the SmoDav/mpesa GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 46 files (82.0 KB), approximately 21.8k tokens, and a symbol index with 124 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!