[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\nconcurrency:\n  group: production\n  cancel-in-progress: true\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    types: [opened, synchronize, reopened]\njobs:\n  tests:\n    name: Tests and SonarCloud\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: shivammathur/setup-php@v2\n        with:\n          php-version: \"8.4\"\n          coverage: \"xdebug\"\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - name: Install composer dependencies\n        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist\n      - name: Static Checks & Test\n        run: ./vendor/bin/phpunit\n      - name: SonarCloud Scan\n        uses: SonarSource/sonarqube-scan-action@v7.0.0\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}  # Needed to get PR information, if any\n          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/.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",
    "content": "<?php\nreturn PhpCsFixer\\Config::create()\n    ->setRiskyAllowed(true)\n    ->setRules(\n        [\n            'array_syntax' => ['syntax' => 'short'],\n            'binary_operator_spaces' => [\n                'align_double_arrow' => true,\n                'align_equals' => true\n            ],\n            'blank_line_after_namespace' => true,\n            'blank_line_before_return' => true,\n            'braces' => true,\n            'cast_spaces' => true,\n            'concat_space' => ['spacing' => 'one'],\n            'elseif' => true,\n            'encoding' => true,\n            'full_opening_tag' => true,\n            'function_declaration' => true,\n            'indentation_type' => true,\n            'line_ending' => true,\n            'lowercase_constants' => true,\n            'lowercase_keywords' => true,\n            'method_argument_space' => true,\n            'native_function_invocation' => true,\n            'no_alias_functions' => true,\n            'no_blank_lines_after_class_opening' => true,\n            'no_blank_lines_after_phpdoc' => true,\n            'no_closing_tag' => true,\n            'no_empty_phpdoc' => true,\n            'no_empty_statement' => true,\n            'no_extra_consecutive_blank_lines' => true,\n            'no_leading_namespace_whitespace' => true,\n            'no_singleline_whitespace_before_semicolons' => true,\n            'no_spaces_after_function_name' => true,\n            'no_spaces_inside_parenthesis' => true,\n            'no_trailing_comma_in_list_call' => true,\n            'no_trailing_whitespace' => true,\n            'no_unused_imports' => true,\n            'no_whitespace_in_blank_line' => true,\n            'phpdoc_align' => true,\n            'phpdoc_indent' => true,\n            'phpdoc_no_access' => true,\n            'phpdoc_no_empty_return' => true,\n            'phpdoc_no_package' => true,\n            'phpdoc_scalar' => true,\n            'phpdoc_separation' => true,\n            'phpdoc_to_comment' => true,\n            'phpdoc_trim' => true,\n            'phpdoc_types' => true,\n            'phpdoc_var_without_name' => true,\n            'self_accessor' => true,\n            'simplified_null_return' => true,\n            'single_blank_line_at_eof' => true,\n            'single_import_per_statement' => true,\n            'single_line_after_imports' => true,\n            'single_quote' => true,\n            'ternary_operator_spaces' => true,\n            'trim_array_spaces' => true,\n            'visibility_required' => true,\n        ]\n    )\n    ->setFinder(\n        PhpCsFixer\\Finder::create()\n        ->files()\n        ->in(__DIR__ . '/src')\n        ->in(__DIR__ . '/tests')\n        ->name('*.php')\n    );\n"
  },
  {
    "path": ".phpunit.result.cache",
    "content": "{\"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}}"
  },
  {
    "path": ".travis.yml",
    "content": "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  - composer install --prefer-source --no-interaction --dev\n\nscript: vendor/bin/phpunit\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## CHANGELOG\n\n### 2019-09-26 :: v5.0.0\n\n#### NativeCache\n\n - 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.\n - Uses seconds instead of minutes as ttl to conform to PSR-16 spec.\n - Added a new method `pull` to extract the key and delete it from the cache if present.\n\n#### NativeConfig\n\n - Takes the path of the custom configuration file location as the first argument. If none is provided it uses the default configuration file location.\n - Moved the extracting of configuration values to the `ConfigurationRepository`.\n\n#### Core\n\n - 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.\n - The account to be used is now set via the `Core` method `useAccount`.\n\n#### Authenticator\n\n - Removed all static methods.\n - Added a `flushTokens` method to remove all authentication tokens from the store. `$core->auth()->flushTokens();` or `app(Core::class)->auth()->flushTokens();`\n - \n\n#### Authenticator\n - `Identity`, `Registrar`, `Simulate` and `STK` all receive the `Core` instance as the first constructor argument."
  },
  {
    "path": "LICENSE.txt",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 SmoDav\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# M-PESA API Package\n[![Build Status](https://travis-ci.org/SmoDav/mpesa.svg?branch=master)](https://travis-ci.org/SmoDav/mpesa)\n[![Total Downloads](https://poser.pugx.org/smodav/mpesa/d/total.svg)](https://packagist.org/packages/smodav/mpesa)\n[![Latest Stable Version](https://poser.pugx.org/smodav/mpesa/v/stable.svg)](https://packagist.org/packages/smodav/mpesa)\n[![Latest Unstable Version](https://poser.pugx.org/smodav/mpesa/v/unstable.svg)](https://packagist.org/packages/smodav/mpesa)\n[![License](https://poser.pugx.org/smodav/mpesa/license.svg)](https://packagist.org/packages/smodav/mpesa)\n\nThis is a PHP package for the Safaricom's M-Pesa API. \nThe API allows a merchant to initiate C2B online checkout (paybill via web) transactions.\nThe merchant submits authentication details, transaction details, callback url and callback method. \n\nAfter request submission, the merchant receives instant feedback with validity status of their requests. \nThe C2B API handles customer validation and authentication via USSD push. \nThe 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.\n\nIf you enjoy using this package, please take a moment and [buy me some coffee.](https://rave.flutterwave.com/donate/fiqyumudlt6t)\n\n\n\n## Installation\n\nPull in the package through Composer.\n\n### Native Addon\n\nWhen using vanilla PHP, modify your `composer.json` file to include:\n\n```json\n  \"scripts\": {\n    \"post-update-cmd\": [\n        \"SmoDav\\\\Mpesa\\\\Support\\\\Installer::install\"\n    ]\n  },\n```\n\nThis script will copy the default configuration file to a config folder in the root directory of your project.\nNow proceed to require the package.\n\n### General Install\n\nRun `composer require smodav/mpesa` to get the latest stable version of the package.\n\n## Migration from previous versions\n\nv5 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).\n\nv4 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.\n\n### Laravel\n\nWhen using Laravel 5.5+, the package will automatically register. For laravel 5.4 and below,\ninclude the service provider and its alias within your `config/app.php`.\n\n```php\n'providers' => [\n    SmoDav\\Mpesa\\Laravel\\ServiceProvider::class,\n],\n\n'aliases' => [\n    'STK'       => SmoDav\\Mpesa\\Laravel\\Facades\\STK::class,\n    'Simulate'  => SmoDav\\Mpesa\\Laravel\\Facades\\Simulate::class,\n    'Registrar' => SmoDav\\Mpesa\\Laravel\\Facades\\Registrar::class,\n    'Identity'  => SmoDav\\Mpesa\\Laravel\\Facades\\Identity::class,\n],\n```\n\nPublish the package specific config using:\n```bash\nphp artisan vendor:publish\n```\n\n### Other Frameworks\n\nTo 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.\n\n### Configuration\n\nThe package allows you to have multiple accounts. Each account will have its specific credentials and endpoints that are independent of the rest.\nYou 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\nwith two default accounts that you can modify.\n\n```\n/*\n|--------------------------------------------------------------------------\n| Default Account\n|--------------------------------------------------------------------------\n|\n| This is the default account to be used when none is specified.\n*/\n\n'default' => 'staging',\n\n/*\n|--------------------------------------------------------------------------\n| File Cache Location\n|--------------------------------------------------------------------------\n|\n| When using the Native Cache driver, this will be the relative directory\n| where the cache information will be stored.\n*/\n\n'cache_location' => '../cache',\n\n/*\n|--------------------------------------------------------------------------\n| Accounts\n|--------------------------------------------------------------------------\n|\n| These are the accounts that can be used with the package. You can configure\n| as many as needed. Two have been setup for you.\n|\n| Sandbox: Determines whether to use the sandbox, Possible values: sandbox | production\n| Initiator: This is the username used to authenticate the transaction request\n| LNMO:\n|    shortcode: The till number\n|    passkey: The passkey for the till number\n|    callback: Endpoint that will be be queried on completion or failure of the transaction.\n|\n*/\n\n'accounts' => [\n    'staging' => [\n        'sandbox' => true,\n        'key' => 'your development consumer key',\n        'secret' => 'your development consumer secret',\n        'initiator' => 'your development username',\n        'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n        'lnmo' => [\n            'paybill' => 'your development paybill number',\n            'shortcode' => 'your development business code',\n            'passkey' => 'your development passkey',\n            'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n        ]\n    ],\n\n    'paybill_1' => [\n        'sandbox' => false,\n        'key' => 'your production consumer key',\n        'secret' => 'your production consumer secret',\n        'initiator' => 'your production username',\n        'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n        'lnmo' => [\n            'paybill' => 'your production paybill number',\n            'shortcode' => 'your production business code',\n            'passkey' => 'your production passkey',\n            'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n        ]\n    ],\n\n    'paybill_2' => [\n        'sandbox' => false,\n        'key' => 'your production consumer key',\n        'secret' => 'your production consumer secret',\n        'initiator' => 'your production username',\n        'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n        'lnmo' => [\n            'paybill' => 'your production paybill number',\n            'shortcode' => 'your production business code',\n            'passkey' => 'your production passkey',\n            'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n        ]\n    ],\n],\n```\n\nYou can add as many accounts as required and switch the connection using the method `usingAccount` on `STK`, `Register` and `Simulate` as shown below.\n\nAlso, 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.\n\n\n## Usage\n\nFor 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`.\n\nThe `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.\n\nThe `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.\n\n```php\nuse GuzzleHttp\\Client;\nuse SmoDav\\Mpesa\\Engine\\Core;\nuse SmoDav\\Mpesa\\Native\\NativeCache;\nuse SmoDav\\Mpesa\\Native\\NativeConfig;\n\nrequire \"vendor/autoload.php\";\n\n$config = new NativeConfig();\n$cache = new NativeCache($config->get('cache_location'));\n// or\n$cache = new NativeCache(__DIR__ . '/../some/awesome/directory');\n\n$core = new Core(new Client, $config, $cache);\n\n```\n\n### URL Registration\n#### submit(shortCode = null, confirmationURL = null, validationURL = null, onTimeout = 'Completed|Cancelled', account = null)\n\nRegister callback URLs\n\n##### Vanilla\n\n```php\nuse SmoDav\\Mpesa\\C2B\\Registrar;\n\n$conf = 'http://example.com/mpesa/confirm?secret=some_secret_hash_key';\n$val = 'http://example.com/mpesa/validate?secret=some_secret_hash_key';\n\n\n$response = (new Registrar($core))->register(600000)\n    ->onConfirmation($conf)\n    ->onValidation($val)\n    ->submit();\n\n/****** OR ********/\n$response = (new Registrar($core))->submit(600000, $conf, $val);\n\n```\n\nWhen having multiple accounts, switch using the `usingAccount` method. We currently have `staging`, `paybill_1` and `paybill_2` with `staging` as the default:\n\n```php\n$response = (new Registrar($core))\n    ->register(600000)\n    ->usingAccount('paybill_1')\n    ->onConfirmation($conf)\n    ->onValidation($val)\n    ->submit();\n\n/****** OR ********/\n$response = (new Registrar($core))->submit(600000, $conf, $val, null, 'paybill_1');\n```\n\n##### Laravel\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\Registrar;\n\n$conf = 'http://example.com/mpesa/confirm?secret=some_secret_hash_key';\n$val = 'http://example.com/mpesa/validate?secret=some_secret_hash_key';\n\n$response = Registrar::register(600000)\n    ->onConfirmation($conf)\n    ->onValidation($val)\n    ->submit();\n\n/****** OR ********/\n$response = Registrar::submit(600000, $conf, $val);\n```\n\nUsing the `paybill_1` account:\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\Registrar;\n\n$response = Registrar::register(600000)\n    ->usingAccount('paybill_1')\n    ->onConfirmation($conf)\n    ->onValidation($val)\n    ->submit();\n\n/****** OR ********/\n$response = Registrar::submit(600000, $conf, $val, null, 'paybill_1');\n```\n\n### Simulate Transaction\n#### push(amount = null, number = null, reference = null, account = null, command = null)\n\nInitiate a C2B simulation transaction request.\n\nNote that when initiating a C2B simulation, setting the command type is optional and by default `CustomerPaybillOnline`\nwill be used.\n\n##### Vanilla\n\n```php\nuse SmoDav\\Mpesa\\C2B\\Simulate;\n\n$simulate = new Simulate($core)\n\n$response = $simulate->request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference')\n    ->push();\n\n/****** OR ********/\n$response = $simulate->push(10, 254722000000, 'Some Reference');\n```\n\nUsing the `paybill_1` account:\n\n```php\n$response = $simulate->request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference')\n    ->usingAccount('paybill_1')\n    ->push();\n\n/****** OR ********/\n$response = $simulate->push(10, 254722000000, 'Some Reference', 'paybill_1');\n```\n\nUsing the `CustomerBuyGoodsOnline` command:\n\n```php\n$response = $simulate->request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference')\n    ->setCommand(CUSTOMER_BUYGOODS_ONLINE)\n    ->push();\n\n/****** OR ********/\n$response = $simulate->push(10, 254722000000, 'Some Reference', null, CUSTOMER_BUYGOODS_ONLINE);\n```\n\n\n##### Laravel\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\Simulate;\n\n$response = Simulate::request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference')\n    ->push();\n\n/****** OR ********/\n$response = Simulate::push(10, 254722000000, 'Some Reference');\n```\n\nUsing the `paybill_1` account:\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\Simulate;\n\n$response = Simulate::request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference')\n    ->usingAccount('paybill_1')\n    ->push();\n\n/****** OR ********/\n$response = Simulate::push(10, 254722000000, 'Some Reference', 'paybill_1');\n```\n\nUsing the `CustomerBuyGoodsOnline` command:\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\Simulate;\n\n$response = Simulate::request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference')\n    ->setCommand(CUSTOMER_BUYGOODS_ONLINE) \n    ->push();\n\n/****** OR ********/\n$response = Simulate::push(10, 254722000000, 'Some Reference', null, CUSTOMER_BUYGOODS_ONLINE);\n```\n\n### STK PUSH\n#### push(amount = null, number = null, reference = null, description = null, account = null, command = null)\n\nInitiate a C2B STK Push request.\n\nNote that when initiating an STK Push, setting the command type is optional and by default `CustomerPaybillOnline`\nwill be used.\n\n##### Vanilla\n\n```php\nuse SmoDav\\Mpesa\\C2B\\STK;\n\n$stk = new STK($core);\n\n$response = $stk->request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference', 'Test Payment')\n    ->push();\n\n/****** OR ********/\n$response = $stk->push(10, 254722000000, 'Some Reference', 'Test Payment');\n```\n\nUsing the `paybill_2` account:\n\n```php\n\n$response = $stk->request(10)\n    ->from(254722000000)\n    ->usingAccount('paybill_2')\n    ->usingReference('Some Reference', 'Test Payment')\n    ->push();\n\n/****** OR ********/\n$response = $stk->push(10, 254722000000, 'Some Reference', 'Test Payment', 'paybill_2');\n```\n\nUsing `CustomerBuyGoodsOnline` command:\n\n```php\n\n$response = $stk->request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference', 'Test Payment')\n    ->setCommand(CUSTOMER_BUYGOODS_ONLINE)\n    ->push();\n\n/****** OR ********/\n$response = $stk->push(10, 254722000000, 'Some Reference', 'Test Payment', null, CUSTOMER_BUYGOODS_ONLINE);\n```\n\n\n##### Laravel\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\STK;\n\n$response = STK::request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference', 'Test Payment')\n    ->push();\n\n/****** OR ********/\n$response = STK::push(10, 254722000000, 'Some Reference', 'Test Payment');\n```\n\nUsing the `paybill_2` account:\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\STK;\n\n$response = STK::request(10)\n    ->from(254722000000)\n    ->usingAccount('paybill_2')\n    ->usingReference('Some Reference', 'Test Payment')\n    ->push();\n\n$response = STK::push(10, 254722000000, 'Some Reference', 'Test Payment', 'paybill_2');\n```\n\nUsing the `CustomerGoodsOnline` command:\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\STK;\n\n$response = STK::request(10)\n    ->from(254722000000)\n    ->usingReference('Some Reference', 'Test Payment')\n    ->setCommand(CUSTOMER_BUYGOODS_ONLINE) \n    ->push();\n\n$response = STK::push(10, 254722000000, 'Some Reference', 'Test Payment', null, CUSTOMER_BUYGOODS_ONLINE);\n```\n\n### STK PUSH Transaction Validation\n#### validate(merchantReferenceId, account = null)\n\nValidate a C2B STK Push transaction.\n\n##### Vanilla\n\n```php\nuse SmoDav\\Mpesa\\C2B\\STK;\n\n$stk = new STK($core);\n    \n$response = $stk->validate('ws_CO_16022018125');\n```\n\nUsing the `paybill_2` account:\n\n```php\n$response = $stk->validate('ws_CO_16022018125', 'paybill_2');\n```\n\n##### Laravel\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\STK;\n\n$response = STK::validate('ws_CO_16022018125');\n```\n\nUsing the `paybill_1` account:\n\n```php\nuse SmoDav\\Mpesa\\Laravel\\Facades\\STK;\n\n$response = STK::validate('ws_CO_16022018125', 'paybill_2');\n```\n\n##### When going live, you should change the `default` value of the config file to the production account.\n\n## License\n\nThe M-Pesa Package is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT).\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"smodav/mpesa\",\n    \"description\": \"M-Pesa API implementation\",\n    \"type\": \"library\",\n    \"keywords\": [\n        \"mpesa\",\n        \"safaricom\",\n        \"laravel\",\n        \"transactions\",\n        \"api\"\n    ],\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"SmoDav\",\n            \"email\": \"smodavprivate@gmail.com\"\n        }\n    ],\n    \"autoload\": {\n        \"files\": [\n            \"src/Mpesa/Support/helpers.php\"\n        ],\n        \"psr-4\": {\n            \"SmoDav\\\\Mpesa\\\\\": \"src/Mpesa/\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"SmoDav\\\\Mpesa\\\\Tests\\\\\": \"tests/\"\n        }\n    },\n    \"extra\": {\n        \"laravel\": {\n            \"providers\": [\n                \"SmoDav\\\\Mpesa\\\\Laravel\\\\ServiceProvider\"\n            ],\n            \"aliases\": {\n                \"STK\": \"SmoDav\\\\Mpesa\\\\Laravel\\\\Facades\\\\STK\",\n                \"Simulate\": \"SmoDav\\\\Mpesa\\\\Laravel\\\\Facades\\\\Simulate\",\n                \"Registrar\": \"SmoDav\\\\Mpesa\\\\Laravel\\\\Facades\\\\Registrar\",\n                \"Identity\": \"SmoDav\\\\Mpesa\\\\Laravel\\\\Facades\\\\Identity\"\n            }\n        }\n    },\n    \"require\": {\n        \"php\": \">=8.2\",\n        \"guzzlehttp/guzzle\": \"^6.2|^7.4.5\",\n        \"illuminate/support\": \"^10.0|^11.0|^12.0|^13.0\",\n        \"nesbot/carbon\": \"^2.0|^3.0\",\n        \"ext-json\": \"*\"\n    },\n    \"require-dev\": {\n        \"mockery/mockery\": \"dev-master|^1.3.1\",\n        \"phpunit/phpunit\": \"~8.5|^9.3|^10.0\"\n    },\n    \"minimum-stability\": \"stable\"\n}\n"
  },
  {
    "path": "config/mpesa.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Default Account\n    |--------------------------------------------------------------------------\n    |\n    | This is the default account to be used when none is specified.\n    */\n\n    'default' => 'staging',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Native File Cache Location\n    |--------------------------------------------------------------------------\n    |\n    | When using the Native Cache driver, this will be the relative directory\n    | where the cache information will be stored.\n    */\n\n    'cache_location' => '../cache',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Accounts\n    |--------------------------------------------------------------------------\n    |\n    | These are the accounts that can be used with the package. You can configure\n    | as many as needed. Two have been setup for you.\n    |\n    | Sandbox: Determines whether to use the sandbox, Possible values: sandbox | production\n    | Initiator: This is the username used to authenticate the transaction request\n    | LNMO:\n    |    paybill: Your paybill number\n    |    shortcode: Your business shortcode\n    |    passkey: The passkey for the paybill number\n    |    callback: Endpoint that will be be queried on completion or failure of the transaction.\n    |\n    */\n\n    'accounts' => [\n        'staging' => [\n            'sandbox' => true,\n            'key' => '',\n            'secret' => '',\n            'initiator' => 'apitest363',\n            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            'lnmo' => [\n                'paybill' => 174379,\n                'shortcode' => 174379,\n                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',\n                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            ]\n        ],\n\n        'production' => [\n            'sandbox' => false,\n            'key' => '',\n            'secret' => '',\n            'initiator' => 'apitest363',\n            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            'lnmo' => [\n                'paybill' => 174379,\n                'shortcode' => 174379,\n                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',\n                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            ]\n        ],\n    ],\n];\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    backupGlobals=\"false\"\n    bootstrap=\"vendor/autoload.php\"\n    colors=\"true\"\n    processIsolation=\"false\"\n    stopOnFailure=\"false\"\n>\n    <testsuite name=\"Package Test Suite\">\n        <directory suffix=\"Test.php\">tests</directory>\n    </testsuite>\n    <coverage>\n        <report>\n            <clover outputFile=\"clover.xml\"/>\n        </report>\n    </coverage>\n    <logging>\n        <junit outputFile=\"junit.xml\"/>\n    </logging>\n    <source>\n        <include>\n            <directory suffix=\".php\">./src</directory>\n        </include>\n    </source>\n</phpunit>\n"
  },
  {
    "path": "sonar-project.properties",
    "content": "sonar.projectKey=smodav_m-pesa\nsonar.organization=smodav\n\n# This is the name and version displayed in the SonarCloud UI.\n#sonar.projectName=SmoDav\n#sonar.projectVersion=1.0\n\n# Path is relative to the sonar-project.properties file. Replace \"\\\" by \"/\" on Windows.\nsonar.sources=./src\nsonar.php.coverage.reportPaths=clover.xml\nsonar.php.tests.reportPath=junit.xml\n\nsonar.php.exclusions=**/vendor/**\nsonar.sourceEncoding=UTF-8\n"
  },
  {
    "path": "src/Mpesa/Auth/Authenticator.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Auth;\n\nuse Carbon\\Carbon;\nuse GuzzleHttp\\Exception\\RequestException;\nuse SmoDav\\Mpesa\\Engine\\Core;\nuse SmoDav\\Mpesa\\Exceptions\\ConfigurationException;\nuse SmoDav\\Mpesa\\Exceptions\\ErrorException;\nuse SmoDav\\Mpesa\\Repositories\\Endpoint;\n\n/**\n * Class Authenticator.\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n */\nclass Authenticator\n{\n    /**\n     * Cache key.\n     */\n    const AC_TOKEN = 'MP:';\n\n    /**\n     * @var Core\n     */\n    private $core;\n\n    public function __construct(Core $core)\n    {\n        $this->core = $core;\n    }\n\n    /**\n     * Remove all the access tokens\n     *\n     * @return void\n     */\n    public function flushTokens()\n    {\n        collect($this->core->configRepository()->config('accounts'))\n            ->each(function ($account) {\n                $this->core->cache()->pull($this->getCacheKey($account['key'], $account['secret']));\n            });\n    }\n\n    /**\n     * Get the cache key for the given key and secret\n     *\n     * @param string $key\n     * @param string $secret\n     *\n     * @return void\n     */\n    protected function getCacheKey($key, $secret)\n    {\n        return self::AC_TOKEN . \"{$key}{$secret}\";\n    }\n\n    /**\n     * Get the access token required to transact.\n     *\n     * @return mixed\n     *\n     * @throws ConfigurationException\n     */\n    public function authenticate()\n    {\n        $key = $this->core->configRepository()->getAccountKey('key');\n        $secret = $this->core->configRepository()->getAccountKey('secret');\n        $cacheKey = $this->getCacheKey($key, $secret);\n\n        if ($token = $this->core->cache()->get($cacheKey)) {\n            return $token;\n        }\n\n        try {\n            $response = $this->makeRequest($key, $secret);\n            $body = json_decode($response->getBody());\n            $this->saveCredentials($cacheKey, $body);\n\n            return $body->access_token;\n        } catch (RequestException $exception) {\n            $message = $exception->getResponse() ?\n               $exception->getResponse()->getReasonPhrase() :\n               $exception->getMessage();\n\n            throw $this->generateException($message);\n        }\n    }\n\n    /**\n     * Initiate the authentication request.\n     *\n     * @return mixed|\\Psr\\Http\\Message\\ResponseInterface\n     */\n    private function makeRequest($key, $secret)\n    {\n        $credentials = base64_encode($key . ':' . $secret);\n        $endpoint = $this->core->configRepository()->url(Endpoint::MPESA_AUTH);\n\n        return $this->core->client()->request('GET', $endpoint, [\n            'headers' => [\n                'Authorization' => 'Basic ' . $credentials,\n                'Content-Type'  => 'application/json',\n            ],\n        ]);\n    }\n\n    /**\n     * Store the credentials in the cache.\n     *\n     * @param $credentials\n     */\n    private function saveCredentials($key, $credentials)\n    {\n        $ttlSeconds = (int) $credentials->expires_in;\n        $ttl = Carbon::now()->addSeconds($ttlSeconds)->subMinute();\n\n        $this->core->cache()->put($key, $credentials->access_token, $ttl);\n    }\n\n    /**\n     * Throw a contextual exception.\n     *\n     * @param $reason\n     *\n     * @return ErrorException|ConfigurationException\n     */\n    private function generateException($reason)\n    {\n        switch (strtolower($reason)) {\n            case 'bad request: invalid credentials':\n                return new ConfigurationException('Invalid consumer key and secret combination');\n            default:\n                return new ErrorException($reason);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/C2B/Identity.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse Carbon\\Carbon;\nuse GuzzleHttp\\Exception\\RequestException;\nuse SmoDav\\Mpesa\\Repositories\\Endpoint;\nuse SmoDav\\Mpesa\\Traits\\UsesCore;\nuse SmoDav\\Mpesa\\Traits\\Validates;\n\nclass Identity\n{\n    use UsesCore, Validates;\n\n    /**\n     * Prepare the number validation request\n     *\n     * @param int    $number\n     * @param string $callback\n     *\n     * @return mixed\n     */\n    public function validate($number, $callback = null)\n    {\n        $this->validateNumber($number);\n\n        $time = Carbon::now()->format('YmdHis');\n\n        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');\n        $passkey   = $this->core->configRepository()->getAccountKey('lnmo.passkey');\n        $lmnoCallback  = $callback ?: $this->core->configRepository()->getAccountKey('lnmo.callback');\n\n        $defaultCallback = $this->core->configRepository()->getAccountKey('id_validation_callback');\n        $initiator = $this->core->configRepository()->getAccountKey('initiator');\n\n        $body = [\n            'Initiator'         => $initiator,\n            'BusinessShortCode' => $shortCode,\n            'Password'          => $this->password($shortCode, $passkey, $time),\n            'Timestamp'         => $time,\n            'TransactionType'   => 'CheckIdentity',\n            'PhoneNumber'       => $number,\n            'CallBackURL'       => $lmnoCallback ?: $defaultCallback,\n            'TransactionDesc'   => ' '\n        ];\n\n        try {\n            $response = $this->clientRequest(\n                $body,\n                $this->core->configRepository()->url(Endpoint::MPESA_ID_CHECK)\n            );\n\n            return json_decode($response->getBody());\n        } catch (RequestException $exception) {\n            return json_decode($exception->getResponse()->getBody());\n        }\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/C2B/Registrar.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse Exception;\nuse GuzzleHttp\\Exception\\RequestException;\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\Repositories\\Endpoint;\nuse SmoDav\\Mpesa\\Traits\\UsesCore;\n\nclass Registrar\n{\n    use UsesCore;\n\n    /**\n     * The short code to register callbacks for.\n     *\n     * @var string\n     */\n    protected $shortCode;\n\n    /**\n     * The validation callback.\n     *\n     * @var\n     */\n    protected $validationURL;\n\n    /**\n     * The confirmation callback.\n     *\n     * @var\n     */\n    protected $confirmationURL;\n\n    /**\n     * The status of the request in case a timeout occurs.\n     *\n     * @var string\n     */\n    protected $onTimeout = 'Completed';\n\n    /**\n     * The account to be used\n     *\n     * @var string\n     */\n    protected $account = null;\n\n    /**\n     * Submit the short code to be registered.\n     *\n     * @param $shortCode\n     *\n     * @return self\n     */\n    public function register($shortCode)\n    {\n        $this->shortCode = $shortCode;\n\n        return $this;\n    }\n\n    /**\n     * Submit the callback to be used for validation.\n     *\n     * @param $validationURL\n     *\n     * @return self\n     */\n    public function onValidation($validationURL)\n    {\n        $this->validationURL = $validationURL;\n\n        return $this;\n    }\n\n    /**\n     * Submit the callback to be used for confirmation.\n     *\n     * @param $confirmationURL\n     *\n     * @return self\n     */\n    public function onConfirmation($confirmationURL)\n    {\n        $this->confirmationURL = $confirmationURL;\n\n        return $this;\n    }\n\n    /**\n     * Set the transaction status on timeout.\n     *\n     * @param string $onTimeout\n     *\n     * @return self\n     */\n    public function onTimeout($onTimeout = 'Completed')\n    {\n        if ($onTimeout != 'Completed' && $onTimeout != 'Cancelled') {\n            throw new InvalidArgumentException('Invalid timeout argument. Use Completed or Cancelled');\n        }\n\n        $this->onTimeout = $onTimeout;\n\n        return $this;\n    }\n\n    /**\n     * Set the account to be used.\n     *\n     * @param string $account\n     *\n     * @return self\n     */\n    public function usingAccount($account)\n    {\n        $this->account = $account;\n\n        return $this;\n    }\n\n    /**\n     * Initiate the registration process.\n     *\n     * @param string|null $shortCode\n     * @param string|null $confirmationURL\n     * @param string|null $validationURL\n     * @param string|null $onTimeout\n     * @param string|null $account\n     *\n     * @return mixed\n     *\n     * @throws \\Exception\n     */\n    public function submit($shortCode = null, $confirmationURL = null, $validationURL = null, $onTimeout = null, $account = null)\n    {\n        if ($onTimeout) {\n            $this->onTimeout($onTimeout);\n        }\n\n        $this->core->useAccount($account ?: $this->account);\n\n        $body = [\n            'ShortCode'       => $shortCode ?: $this->shortCode,\n            'ResponseType'    => $this->onTimeout,\n            'ConfirmationURL' => $confirmationURL ?: $this->confirmationURL,\n            'ValidationURL'   => $validationURL ?: $this->validationURL\n        ];\n\n        try {\n            $response = $this->clientRequest(\n                $body,\n                $this->core->configRepository()->url(Endpoint::MPESA_REGISTER)\n            );\n\n            return json_decode($response->getBody());\n        } catch (RequestException $exception) {\n            $message = $exception->getResponse() ?\n               $exception->getResponse()->getReasonPhrase() :\n               $exception->getMessage();\n\n            throw new Exception($message);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/C2B/STK.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse Carbon\\Carbon;\nuse GuzzleHttp\\Exception\\RequestException;\nuse Illuminate\\Support\\Traits\\Macroable;\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\Repositories\\Endpoint;\nuse SmoDav\\Mpesa\\Traits\\UsesCore;\nuse SmoDav\\Mpesa\\Traits\\UsesSTKMethods;\nuse SmoDav\\Mpesa\\Traits\\Validates;\nuse stdClass;\n\nclass STK\n{\n    use UsesCore;\n    use Validates;\n    use Macroable;\n    use UsesSTKMethods;\n\n    const CUSTOMER_BUYGOODS_ONLINE = 'CustomerBuyGoodsOnline';\n\n    const CUSTOMER_PAYBILL_ONLINE = 'CustomerPayBillOnline';\n\n    const VALID_COMMANDS = [\n        self::CUSTOMER_BUYGOODS_ONLINE,\n        self::CUSTOMER_PAYBILL_ONLINE,\n    ];\n\n\n\n    /**\n     * The MPesa callback URL to be used for the request.\n     *\n     * @var string\n     */\n    protected $callback = null;\n\n    /**\n     * Set the callback on completion.\n     *\n     * @param string $callback\n     *\n     * @return self\n     */\n    public function setCallback(string $callback)\n    {\n        $this->callback = $callback;\n\n        return $this;\n    }\n\n    /**\n     * Prepare the STK Push request.\n     *\n     * @param int|null    $amount\n     * @param int|null    $number\n     * @param string|null $reference\n     * @param string|null $description\n     * @param string|null $account\n     * @param string|null $command\n     *\n     * @return mixed\n     */\n    public function push(\n        $amount = null,\n        $number = null,\n        $reference = null,\n        $description = null,\n        $account = null,\n        $command = null\n    ) {\n        $this->set($amount, $number, $command);\n\n        $this->core->useAccount($account ?: $this->account);\n        $time = Carbon::now()->format('YmdHis');\n\n        $paybill = $this->core->configRepository()->getAccountKey('lnmo.paybill');\n        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');\n        $passkey = $this->core->configRepository()->getAccountKey('lnmo.passkey');\n        $callback = $this->callback ?: $this->core->configRepository()->getAccountKey('lnmo.callback');\n\n        $partyB = $this->command == self::CUSTOMER_PAYBILL_ONLINE ? $shortCode : $paybill;\n\n        $body = [\n            'BusinessShortCode' => $shortCode,\n            'Password' => $this->password($shortCode, $passkey, $time),\n            'Timestamp' => $time,\n            'TransactionType' => $this->command,\n            'Amount' => $this->amount,\n            'PartyA' => $this->number,\n            'PartyB' => $partyB,\n            'PhoneNumber' => $number ?: $this->number,\n            'CallBackURL' => $callback,\n            'AccountReference' => $reference ?: $this->reference,\n            'TransactionDesc' => $description ?: $this->description,\n        ];\n\n        try {\n            $response = $this->clientRequest(\n                $body,\n                $this->core->configRepository()->url(Endpoint::MPESA_LNMO)\n            );\n\n            return json_decode($response->getBody());\n        } catch (RequestException $exception) {\n            return json_decode($exception->getResponse()->getBody());\n        }\n    }\n\n    /**\n     * Validate an initialized transaction.\n     *\n     * @param string     $checkoutRequestID\n     * @param mixed|null $account\n     *\n     * @return stdClass\n     */\n    public function validate($checkoutRequestID, $account = null)\n    {\n        $this->core->useAccount($account ?: $this->account);\n        $time = Carbon::now()->format('YmdHis');\n\n        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');\n        $passkey = $this->core->configRepository()->getAccountKey('lnmo.passkey');\n\n        $body = [\n            'BusinessShortCode' => $shortCode,\n            'Password' => $this->password($shortCode, $passkey, $time),\n            'Timestamp' => $time,\n            'CheckoutRequestID' => $checkoutRequestID,\n        ];\n\n        try {\n            $response = $this->clientRequest(\n                $body,\n                $this->core->configRepository()->url(Endpoint::MPESA_LNMO_VALIDATE)\n            );\n\n            return json_decode($response->getBody());\n        } catch (RequestException $exception) {\n            return json_decode($exception->getResponse()->getBody());\n        }\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/C2B/Simulate.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\C2B;\n\nuse GuzzleHttp\\Exception\\RequestException;\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\Exceptions\\ErrorException;\nuse SmoDav\\Mpesa\\Repositories\\Endpoint;\nuse SmoDav\\Mpesa\\Traits\\UsesCore;\nuse SmoDav\\Mpesa\\Traits\\UsesSTKMethods;\nuse SmoDav\\Mpesa\\Traits\\Validates;\n\nclass Simulate\n{\n    use UsesCore;\n    use Validates;\n    use UsesSTKMethods;\n\n    /**\n     * The transaction command to be used.\n     *\n     * @var string\n     */\n    protected $command = STK::CUSTOMER_PAYBILL_ONLINE;\n\n    /**\n     * Prepare the transaction simulation request\n     *\n     * @param int|null    $amount\n     * @param int|null    $number\n     * @param string|null $reference\n     * @param string|null $account\n     * @param string|null $command\n     *\n     * @throws ErrorException\n     *\n     * @return mixed\n     */\n    public function push(\n        $amount = null,\n        $number = null,\n        $reference = null,\n        $account = null,\n        $command = null\n    ) {\n        $this->set($amount, $number, $command);\n        $this->core->useAccount($account ?: $this->account);\n\n        if (!$this->core->configRepository()->getAccountKey('sandbox')) {\n            throw new ErrorException('Cannot simulate a transaction in the live environment.');\n        }\n\n        $shortCode = $this->core->configRepository()->getAccountKey('lnmo.shortcode');\n\n        $body = [\n            'CommandID'     => $this->command,\n            'Amount'        => $this->amount,\n            'Msisdn'        => $this->number,\n            'ShortCode'     => $shortCode,\n            'BillRefNumber' => $reference ?: $this->reference,\n        ];\n\n        try {\n            $response = $this->clientRequest(\n                $body,\n                $this->core->configRepository()->url(Endpoint::MPESA_SIMULATE)\n            );\n\n            return json_decode($response->getBody());\n        } catch (RequestException $exception) {\n            return json_decode($exception->getResponse()->getBody());\n        }\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Contracts/CacheStore.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Contracts;\n\n/**\n * Interface CacheStore\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n */\ninterface CacheStore\n{\n    /**\n     * Get the cache value from the store or a default value to be supplied.\n     *\n     * @param $key\n     * @param $default\n     *\n     * @return mixed\n     */\n    public function get($key, $default = null);\n\n    /**\n     * Store an item in the cache.\n     *\n     * @param string                                     $key\n     * @param mixed                                      $value\n     * @param \\DateTimeInterface|\\DateInterval|float|int $seconds\n     */\n    public function put($key, $value, $seconds = null);\n\n    /**\n     * Get the cache or default value from the store and delete it.\n     *\n     * @param $key\n     * @param $default\n     *\n     * @return mixed\n     */\n    public function pull($key, $default = null);\n}\n"
  },
  {
    "path": "src/Mpesa/Contracts/ConfigurationStore.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Contracts;\n\n/**\n * Interface ConfigurationStore\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n */\ninterface ConfigurationStore\n{\n    /**\n     * Get the configuration value from the store or a default value to be supplied.\n     *\n     * @param $key\n     * @param $default\n     *\n     * @return mixed\n     */\n    public function get($key, $default = null);\n}\n"
  },
  {
    "path": "src/Mpesa/Engine/Core.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Engine;\n\nuse Exception;\nuse GuzzleHttp\\ClientInterface;\nuse SmoDav\\Mpesa\\Auth\\Authenticator;\nuse SmoDav\\Mpesa\\Contracts\\CacheStore;\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\nuse SmoDav\\Mpesa\\Native\\NativeCache;\nuse SmoDav\\Mpesa\\Native\\NativeConfig;\nuse SmoDav\\Mpesa\\Repositories\\ConfigurationRepository;\n\nclass Core\n{\n    /**\n     * The configuration\n     *\n     * @var ConfigurationRepository\n     */\n    private $configRepository;\n\n    /**\n     * @var ClientInterface\n     */\n    private $client;\n\n    /**\n     * @var Authenticator\n     */\n    private $auth;\n\n    /**\n     * Core constructor.\n     *\n     * @param ClientInterface         $client\n     * @param ConfigurationStore|null $configStore\n     * @param CacheStore|null         $cacheStore\n     */\n    public function __construct(\n        ClientInterface $client,\n        ConfigurationStore $configStore = null,\n        CacheStore $cacheStore = null\n    ) {\n        $this->client = $client;\n        $this->setupStores($configStore, $cacheStore);\n        $this->initialise();\n    }\n\n    /**\n     * Use the native implementation of the stores.\n     *\n     * @return void\n     */\n    protected function setupStores(ConfigurationStore $configStore = null, CacheStore $cacheStore = null)\n    {\n        $this->configRepository = new ConfigurationRepository($configStore ?: new NativeConfig);\n        $this->cache = $cacheStore ?: new NativeCache($this->configRepository->config('cache_location'));\n    }\n\n    /**\n     * Initialise the Core process.\n     */\n    private function initialise()\n    {\n        $this->auth = new Authenticator($this);\n    }\n\n    /**\n     * Get the configuration repository.\n     *\n     * @return ConfigurationRepository\n     */\n    public function configRepository()\n    {\n        return $this->configRepository;\n    }\n\n    /**\n     * Get the cache store.\n     *\n     * @return CacheStore\n     */\n    public function cache()\n    {\n        return $this->cache;\n    }\n\n    /**\n     * Get the client.\n     *\n     * @return ClientInterface\n     */\n    public function client()\n    {\n        return $this->client;\n    }\n\n    /**\n     * Get the client.\n     *\n     * @return Authenticator\n     */\n    public function auth()\n    {\n        return $this->auth;\n    }\n\n    /**\n     * Switch the current account\n     *\n     * @param string|null $account\n     *\n     * @throws Exception\n     * @return self\n     */\n    public function useAccount($account = null)\n    {\n        $this->configRepository->useAccount($account);\n\n        return $this;\n    }\n\n    /**\n     * Switch the client instance.\n     *\n     * @param string|null $account\n     *\n     * @return self\n     */\n    public function useClient(ClientInterface $client)\n    {\n        $this->client = $client;\n\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Exceptions/ConfigurationException.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Exceptions;\n\nuse Exception;\n\nclass ConfigurationException extends Exception\n{\n}\n"
  },
  {
    "path": "src/Mpesa/Exceptions/ErrorException.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Exceptions;\n\nuse Exception;\n\nclass ErrorException extends Exception\n{\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/Identity.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class Identity.\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n *\n * @method static stdClass validate(string $number, callable $callback, string $account = null)\n *\n * @see \\SmoDav\\Mpesa\\C2B\\Registrar\n */\nclass Identity extends Facade\n{\n    /**\n     * Get the registered name of the component.\n     *\n     * @return string\n     */\n    protected static function getFacadeAccessor()\n    {\n        return 'mp_identity';\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/Registrar.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class Registrar.\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n *\n * @method static Registrar onConfirmation(string $confirmationURL)\n * @method static Registrar onTimeout(string $onTimeout)\n * @method static Registrar onValidation(string $validationURL)\n * @method static Registrar register(string $shortCode)\n * @method static stdClass submit(string $shortCode = null, string $confirmationURL = null, string $validationURL = null, string $onTimeout = null, string $account = null)\n * @method static Registrar usingAccount(string $account)\n *\n * @see \\SmoDav\\Mpesa\\C2B\\Registrar\n */\nclass Registrar extends Facade\n{\n    /**\n     * Get the registered name of the component.\n     *\n     * @return string\n     */\n    protected static function getFacadeAccessor()\n    {\n        return 'mp_registrar';\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/STK.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class STK\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n *\n * @method static STK from(string $number)\n * @method static STK request(string $amount)\n * @method static stdClass push(string $amount = null, string $number = null, string $reference = null, string $description = null, string $account = null)\n * @method static STK usingAccount(string $account)\n * @method static STK usingReference(string $reference, string $description)\n * @method static stdClass validate(string $checkoutRequestID, string $account = null)\n *\n * @see \\SmoDav\\Mpesa\\C2B\\STK\n */\nclass STK extends Facade\n{\n    /**\n     * Get the registered name of the component.\n     *\n     * @return string\n     */\n    protected static function getFacadeAccessor()\n    {\n        return 'mp_stk';\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Facades/Simulate.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Facades;\n\nuse Illuminate\\Support\\Facades\\Facade;\n\n/**\n * Class Simulate.\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n *\n * @method static Simulate from(string $number)\n * @method static Simulate request(string $amount)\n * @method static stdClass push(string $amount = null, string $number = null, string $reference = null, string $command = null, string $account = null)\n * @method static Simulate setCommand(string $command)\n * @method static Simulate usingAccount(string $account)\n * @method static Simulate usingReference(string $reference)\n * @method static stdClass validate(string $checkoutRequestID, string $account = null)\n *\n * @see \\SmoDav\\Mpesa\\C2B\\Simulate\n */\nclass Simulate extends Facade\n{\n    /**\n     * Get the registered name of the component.\n     *\n     * @return string\n     */\n    protected static function getFacadeAccessor()\n    {\n        return 'mp_simulate';\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/ServiceProvider.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel;\n\nuse GuzzleHttp\\Client;\nuse Illuminate\\Support\\ServiceProvider as RootProvider;\nuse SmoDav\\Mpesa\\C2B\\Identity;\nuse SmoDav\\Mpesa\\C2B\\Registrar;\nuse SmoDav\\Mpesa\\C2B\\Simulate;\nuse SmoDav\\Mpesa\\C2B\\STK;\nuse SmoDav\\Mpesa\\Contracts\\CacheStore;\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\nuse SmoDav\\Mpesa\\Engine\\Core;\nuse SmoDav\\Mpesa\\Laravel\\Stores\\LaravelCache;\nuse SmoDav\\Mpesa\\Laravel\\Stores\\LaravelConfig;\n\nclass ServiceProvider extends RootProvider\n{\n    /**\n     * Bootstrap the application services.\n     */\n    public function boot()\n    {\n        $this->publishes([\n            __DIR__ . '/../../../config/mpesa.php' => config_path('mpesa.php')\n        ]);\n    }\n\n    /**\n     * Registrar the application services.\n     */\n    public function register()\n    {\n        $this->bindInstances();\n\n        $this->registerFacades();\n    }\n\n    /**\n     * Bind the MPesa Instances.\n     *\n     * @return void\n     */\n    private function bindInstances()\n    {\n        $this->app->bind(ConfigurationStore::class, LaravelConfig::class);\n        $this->app->bind(CacheStore::class, LaravelCache::class);\n        $this->app->singleton(Core::class, function ($app) {\n            $config = $app->make(ConfigurationStore::class);\n            $cache = $app->make(CacheStore::class);\n\n            return new Core(new Client, $config, $cache);\n        });\n    }\n\n    private function registerFacades()\n    {\n        $this->app->bind('mp_stk', function () {\n            return $this->app->make(STK::class);\n        });\n\n        $this->app->bind('mp_registrar', function () {\n            return $this->app->make(Registrar::class);\n        });\n\n        $this->app->bind('mp_identity', function () {\n            return $this->app->make(Identity::class);\n        });\n\n        $this->app->bind('mp_simulate', function () {\n            return $this->app->make(Simulate::class);\n        });\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Stores/LaravelCache.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Stores;\n\nuse Illuminate\\Cache\\Repository;\nuse SmoDav\\Mpesa\\Contracts\\CacheStore;\n\nclass LaravelCache implements CacheStore\n{\n    /**\n     * @var MpesaRepository\n     */\n    private $repository;\n\n    /**\n     * LaravelConfiguration constructor.\n     *\n     * @param Repository $repository\n     */\n    public function __construct(Repository $repository)\n    {\n        $this->repository = $repository;\n    }\n\n    /**\n     * Get given config value from the configuration store.\n     *\n     * @param string $key\n     * @param null   $default\n     *\n     * @return mixed\n     */\n    public function get($key, $default = null)\n    {\n        return $this->repository->get($key, $default);\n    }\n\n    /**\n     * Store an item in the cache.\n     *\n     * @param string                                     $key\n     * @param mixed                                      $value\n     * @param \\DateTimeInterface|\\DateInterval|float|int $seconds\n     */\n    public function put($key, $value, $seconds = null)\n    {\n        $this->repository->put($key, $value, $seconds);\n    }\n\n    /**\n     * Get the cache or default value from the store and delete it.\n     *\n     * @param $key\n     * @param $default\n     *\n     * @return mixed\n     */\n    public function pull($key, $default = null)\n    {\n        return $this->repository->pull($key, $default);\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Laravel/Stores/LaravelConfig.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Laravel\\Stores;\n\nuse Illuminate\\Config\\Repository;\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\n\nclass LaravelConfig implements ConfigurationStore\n{\n    /**\n     * @var MpesaRepository\n     */\n    private $repository;\n\n    /**\n     * LaravelConfiguration constructor.\n     *\n     * @param Repository $repository\n     */\n    public function __construct(Repository $repository)\n    {\n        $this->repository = $repository;\n    }\n\n    /**\n     * Get given config value from the configuration store.\n     *\n     * @param string $key\n     * @param null   $default\n     *\n     * @return mixed\n     */\n    public function get($key, $default = null)\n    {\n        return $this->repository->get($key, $default);\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Native/NativeCache.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Native;\n\nuse Carbon\\Carbon;\nuse DateInterval;\nuse DateTimeInterface;\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\Contracts\\CacheStore;\nuse SmoDav\\Mpesa\\Repositories\\ConfigurationRepository;\n\n/**\n * Class NativeCache\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n */\nclass NativeCache implements CacheStore\n{\n    /**\n     * @var string\n     */\n    private $cacheFile;\n\n    /**\n     * NativeCache constructor.\n     *\n     * @param string|null $cacheDirectory\n     */\n    public function __construct($cacheDirectory = null)\n    {\n        $this->setUp($cacheDirectory);\n    }\n\n    /**\n     * Setup the cache file location.\n     *\n     * @param string $cacheDirectory\n     *\n     * @return void\n     */\n    private function setUp($cacheDirectory = null)\n    {\n        $cacheDirectory = $cacheDirectory\n            ?: (new ConfigurationRepository(new NativeConfig))->config('cache_location');\n\n        $cacheDirectory = rtrim($cacheDirectory, '/');\n\n        if (! is_dir($cacheDirectory)) {\n            mkdir($cacheDirectory, 0755, true);\n        }\n\n        $this->cacheFile = $cacheDirectory . '/.mpc';\n\n        if (!is_file($this->cacheFile)) {\n            file_put_contents($this->cacheFile, serialize([]));\n        }\n    }\n\n    /**\n     * Get the cache value.\n     *\n     * @param      $key\n     * @param null $default\n     *\n     * @return mixed|null\n     */\n    public function get($key, $default = null)\n    {\n        $cache = unserialize(file_get_contents($this->cacheFile));\n        $cache = $this->cleanCache($cache, $this->cacheFile);\n\n        if (! isset($cache[$key])) {\n            return $default;\n        }\n\n        return $cache[$key]['v'];\n    }\n\n    /**\n     * Store an item in the cache.\n     *\n     * @param string                                     $key\n     * @param mixed                                      $value\n     * @param \\DateTimeInterface|\\DateInterval|float|int $seconds\n     *\n     * @return bool\n     */\n    public function put($key, $value, $seconds = null)\n    {\n        $initial = unserialize(file_get_contents($this->cacheFile));\n        $initial = $this->cleanCache($initial, $this->cacheFile, false);\n\n        $payload = [$key => ['v' => $value, 't' => $this->formatTimeFromSeconds($seconds)]];\n        $payload = serialize(array_merge($initial, $payload));\n\n        return file_put_contents($this->cacheFile, $payload) !== false;\n    }\n\n    /**\n     * Get the seconds and format it.\n     *\n     * @param int|null $seconds\n     *\n     * @return string|null\n     */\n    private function formatTimeFromSeconds($seconds = null)\n    {\n        if (!$seconds) {\n            return null;\n        }\n\n        if ($seconds instanceof DateTimeInterface || $seconds instanceof DateInterval) {\n            return Carbon::parse($seconds)->toDateTimeString();\n        }\n\n        if (!is_numeric($seconds)) {\n            throw new InvalidArgumentException('The seconds argument should be numeric');\n        }\n\n        return Carbon::now()->addSeconds($seconds)->toDateTimeString();\n    }\n\n    /**\n     * Clean out the expired items\n     *\n     * @param array  $initial\n     * @param string $location\n     * @param bool   $save\n     *\n     * @return array\n     */\n    private function cleanCache($initial, $location, $save = true)\n    {\n        $initial = array_filter($initial, function ($value) {\n            if (! $value['t']) {\n                return true;\n            }\n\n            if (Carbon::now()->gt(Carbon::parse($value['t']))) {\n                return false;\n            }\n\n            return true;\n        });\n\n        if ($save) {\n            file_put_contents($location, serialize($initial));\n        }\n\n        return $initial;\n    }\n\n    /**\n     * Get the cache or default value from the store and delete it.\n     *\n     * @param $key\n     * @param $default\n     *\n     * @return mixed\n     */\n    public function pull($key, $default = null)\n    {\n        $cache = unserialize(file_get_contents($this->cacheFile));\n        $cache = $this->cleanCache($cache, $this->cacheFile, false);\n\n        if (! isset($cache[$key])) {\n            file_put_contents($this->cacheFile, serialize($cache));\n\n            return $default;\n        }\n\n        $value = $cache[$key]['v'];\n\n        unset($cache[$key]);\n        file_put_contents($this->cacheFile, serialize($cache));\n\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Native/NativeConfig.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Native;\n\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\n\n/**\n * Class NativeConfig\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n */\nclass NativeConfig implements ConfigurationStore\n{\n    /**\n     * Mpesa configuration file.\n     *\n     * @var array\n     */\n    protected $config;\n\n    /**\n     * NativeConfig constructor.\n     *\n     * @param string|null $configPath\n     */\n    public function __construct($configPath = null)\n    {\n        $defaultConfig = require __DIR__ . '/../../../config/mpesa.php';\n        $configPath = $configPath ?: __DIR__ . '/../../../../../../config/mpesa.php';\n        $custom = [];\n\n        if (is_file($configPath)) {\n            $custom = require $configPath;\n        }\n\n        $this->config = ['mpesa' => array_merge($defaultConfig, $custom)];\n    }\n\n    /**\n     * Get the configuration value.\n     *\n     * @param      $key\n     * @param null $default\n     *\n     * @return mixed|null\n     */\n    public function get($key, $default = null)\n    {\n        $pieces = explode('.', $key);\n        $config = $this->config;\n\n        foreach ($pieces as $piece) {\n            if (!isset($config[$piece])) {\n                return $default;\n            }\n\n            $config = $config[$piece];\n        }\n\n        return $config;\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Repositories/ConfigurationRepository.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Repositories;\n\nuse Exception;\nuse SmoDav\\Mpesa\\Contracts\\ConfigurationStore;\n\n/**\n * Class ConfigurationRepository.\n *\n * @category PHP\n *\n * @author   David Mjomba <smodavprivate@gmail.com>\n */\nclass ConfigurationRepository\n{\n    const SANDBOX_URL = 'https://sandbox.safaricom.co.ke/';\n    const PRODUCTION_URL = 'https://api.safaricom.co.ke/';\n\n    /**\n     * @var string\n     */\n    private $account;\n\n    /**\n     * @var ConfigurationStore\n     */\n    protected $store;\n\n    /**\n     * @var array\n     */\n    protected $config;\n\n    /**\n     * Build up a new instance.\n     *\n     * @param ConfigurationStore $store\n     */\n    public function __construct(ConfigurationStore $store)\n    {\n        $this->store = $store;\n        $this->config = $this->store->get('mpesa');\n        $this->useAccount();\n    }\n\n    /**\n     * Get the configuration instance.\n     */\n    public function store()\n    {\n        return $this->store;\n    }\n\n    /**\n     * Get the configuration value.\n     *\n     * @param string $key\n     * @param mixed  $default\n     *\n     * @return mixed\n     */\n    public function config($key = null, $default = null)\n    {\n        if (!$key) {\n            return $this->config;\n        }\n\n        $key = explode('.', $key);\n        $value = $this->config;\n\n        foreach ($key as $prop) {\n            if (!isset($value[$prop])) {\n                return $default;\n            }\n\n            $value = $value[$prop];\n        }\n\n        return $value;\n    }\n\n    /**\n     * Set the account to be used when resoving configs.\n     *\n     * @param string $account\n     *\n     * @return self\n     */\n    public function useAccount($account = null)\n    {\n        $account = $account ?: $this->config('default');\n\n        if (!$this->config(\"accounts.{$account}\")) {\n            throw new Exception('Invalid account selected');\n        }\n\n        $this->account = $account;\n\n        return $this;\n    }\n\n    /**\n     * Get a configuration value from the store.\n     *\n     * @param string $key\n     * @param mixed  $default\n     * @param string $account\n     *\n     * @return mixed\n     */\n    public function getAccountKey($key, $default = null)\n    {\n        return $this->config(\"accounts.{$this->account}.{$key}\", $default);\n    }\n\n    /**\n     * Get the endpoint relative to the current\n     *\n     * @param string $endpoint\n     * @param string $account\n     *\n     * @return string\n     */\n    public function url($endpoint)\n    {\n        return $this->resolveUrl(\n            $this->getAccountKey('sandbox', true) ? self::SANDBOX_URL : self::PRODUCTION_URL,\n            $endpoint\n        );\n    }\n\n    /**\n     * Resolve the provided URL\n     *\n     * @param string $base\n     * @param string $key\n     *\n     * @return string\n     */\n    private function resolveUrl($base, $key)\n    {\n        return $base . trim($key, '/');\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Repositories/Endpoint.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Repositories;\n\nclass Endpoint\n{\n    public const MPESA_AUTH = 'oauth/v1/generate?grant_type=client_credentials';\n\n    public const MPESA_ID_CHECK = 'mpesa/checkidentity/v1/processrequest';\n\n    public const MPESA_REGISTER = 'mpesa/c2b/v1/registerurl';\n\n    public const MPESA_SIMULATE = 'mpesa/c2b/v1/simulate';\n\n    public const MPESA_LNMO = 'mpesa/stkpush/v1/processrequest';\n\n    public const MPESA_LNMO_VALIDATE = 'mpesa/stkpushquery/v1/query';\n\n    public const CUSTOMER_PAYBILL_ONLINE = 'CustomerPayBillOnline';\n\n    public const CUSTOMER_BUYGOODS_ONLINE = 'CustomerBuyGoodsOnline';\n}\n"
  },
  {
    "path": "src/Mpesa/Support/Installer.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Support;\n\nuse Composer\\Script\\Event;\n\nclass Installer\n{\n    public static function install(Event $event)\n    {\n        $config    = __DIR__ . '/../../../config/mpesa.php';\n        $configDir = self::getConfigDirectory($event);\n\n        if (! is_file($configDir . '/mpesa.php')) {\n            copy($config, $configDir . '/mpesa.php');\n        }\n    }\n\n    public static function getConfigDirectory(Event $event)\n    {\n        $vendorDir = $event->getComposer()->getConfig()->get('vendor-dir');\n        $configDir = $vendorDir . '/../config';\n\n        if (! is_dir($configDir)) {\n            mkdir($configDir, 0755, true);\n        }\n\n        return $configDir;\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Support/helpers.php",
    "content": "<?php\n\nuse SmoDav\\Mpesa\\Repositories\\EndpointsRepository;\n\nfunction mpesa_endpoint($endpoint)\n{\n    return EndpointsRepository::build($endpoint);\n}\n"
  },
  {
    "path": "src/Mpesa/Traits/UsesCore.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Traits;\n\nuse SmoDav\\Mpesa\\Engine\\Core;\n\ntrait UsesCore\n{\n    /**\n     * @var Core\n     */\n    protected $core;\n\n    /**\n     * @param Core $core\n     */\n    public function __construct(Core $core)\n    {\n        $this->core = $core;\n    }\n\n    /**\n     * Initiate the request.\n     *\n     * @param array  $body\n     * @param string $endpoint\n     *\n     * @return mixed|\\Psr\\Http\\Message\\ResponseInterface\n     */\n    private function clientRequest($body, $endpoint)\n    {\n        return $this->core->client()->request('POST', $endpoint, [\n            'headers' => [\n                'Authorization' => 'Bearer ' . $this->bearer(),\n                'Content-Type'  => 'application/json',\n            ],\n            'json' => $body,\n        ]);\n    }\n\n    /**\n     * Get the bearer token.\n     *\n     * @return string\n     */\n    protected function bearer()\n    {\n        return $this->core->auth()->authenticate();\n    }\n\n    /**\n     * Get the password for the\n     *\n     * @param string $shortCode\n     * @param string $passkey\n     * @param string $time\n     *\n     * @return string\n     */\n    private function password($shortCode, $passkey, $time)\n    {\n        return base64_encode($shortCode . $passkey . $time);\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Traits/UsesSTKMethods.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Traits;\n\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\C2B\\STK;\n\ntrait UsesSTKMethods\n{\n    /**\n     * The mobile number.\n     *\n     * @var string\n     */\n    protected $number;\n\n    /**\n     * The amount to request.\n     *\n     * @var int\n     */\n    protected $amount;\n\n    /**\n     * The transaction reference.\n     *\n     * @var string\n     */\n    protected $reference;\n\n    /**\n     * The transaction description.\n     *\n     * @var string\n     */\n    protected $description;\n\n    /**\n     * The MPesa account to be used.\n     *\n     * @var string\n     */\n    protected $account = null;\n\n    /**\n     * The transaction command to be used.\n     *\n     * @var string\n     */\n    protected $command = self::CUSTOMER_PAYBILL_ONLINE;\n\n    /**\n     * Set the account to be used.\n     *\n     * @param string $account\n     *\n     * @return self\n     */\n    public function usingAccount($account)\n    {\n        $this->account = $account;\n\n        return $this;\n    }\n\n    /**\n     * Set the product reference number to bill the account.\n     *\n     * @param int    $reference\n     * @param string $description\n     *\n     * @return self\n     */\n    public function usingReference($reference, $description)\n    {\n        $this->reference = $reference;\n        $this->description = $description;\n\n        return $this;\n    }\n\n    /**\n     * Set the request amount to be deducted.\n     *\n     * @param int $amount\n     *\n     * @throws InvalidArgumentException\n     *\n     * @return self\n     */\n    public function request($amount)\n    {\n        $this->validateAmount($amount);\n\n        $this->amount = $amount;\n\n        return $this;\n    }\n\n    /**\n     * Set the Mobile Subscriber Number to deduct the amount from.\n     * Must be in format 2547XXXXXXXX.\n     *\n     * @param int $number\n     *\n     * @throws InvalidArgumentException\n     *\n     * @return self\n     */\n    public function from($number)\n    {\n        $this->validateNumber($number);\n\n        $this->number = $number;\n\n        return $this;\n    }\n\n    /**\n     * Set the unique command for this transaction type.\n     *\n     * @param string $command\n     *\n     * @throws InvalidArgumentException\n     *\n     * @return self\n     */\n    public function setCommand($command)\n    {\n        if (!in_array($command, STK::VALID_COMMANDS)) {\n            throw new InvalidArgumentException('Invalid command sent');\n        }\n\n        $this->command = $command;\n\n        return $this;\n    }\n\n    /**\n     * Set the properties that require validation.\n     *\n     * @param string|null $amount\n     * @param string|null $number\n     * @param string|null $command\n     *\n     * @return void\n     */\n    private function set($amount, $number, $command)\n    {\n        if ($amount) {\n            $this->request($amount);\n        }\n        if ($number) {\n            $this->from($number);\n        }\n        if ($command) {\n            $this->setCommand($command);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Mpesa/Traits/Validates.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Traits;\n\nuse InvalidArgumentException;\n\ntrait Validates\n{\n    /**\n     * Check if the provided number is valid.\n     *\n     * @param string $number\n     *\n     * @throws InvalidArgumentException\n     *\n     * @return void\n     */\n    protected function validateNumber($number)\n    {\n    }\n\n    /**\n     * Check if the amount is numeric.\n     *\n     * @param string|int|float $amount\n     *\n     * @throws InvalidArgumentException\n     *\n     * @return void\n     */\n    protected function validateAmount($amount)\n    {\n        if (!is_numeric($amount)) {\n            throw new InvalidArgumentException('The amount must be numeric');\n        }\n    }\n}\n"
  },
  {
    "path": "tests/TestCase.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests;\n\nuse GuzzleHttp\\ClientInterface;\nuse PHPUnit\\Framework\\TestCase as PHPUnit;\nuse SmoDav\\Mpesa\\Engine\\Core;\nuse SmoDav\\Mpesa\\Native\\NativeConfig;\nuse SmoDav\\Mpesa\\Tests\\Unit\\NativeImplementationsTest;\n\nclass TestCase extends PHPUnit\n{\n    /**\n     * Get the core instance.\n     *\n     * @param ClientInterface $client\n     *\n     * @return Core\n     */\n    protected function core(ClientInterface $client)\n    {\n        return new Core($client, new NativeConfig(NativeImplementationsTest::CONFIG_FILE));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/AuthenticatorTest.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\HandlerStack;\nuse GuzzleHttp\\Psr7\\Response;\nuse SmoDav\\Mpesa\\Exceptions\\ErrorException;\nuse SmoDav\\Mpesa\\Tests\\TestCase as TestCase;\n\nclass AuthenticatorTest extends TestCase\n{\n    /*\n     * Test that authenticator works.\n     *\n     * @test\n     **/\n    public function testCanAuthenticateUsingRequestAndCached()\n    {\n        $mock = new MockHandler([\n            new Response(202, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),\n        ]);\n\n        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));\n\n        $this->assertEquals('access', $core->auth()->authenticate());\n\n        $mock = new MockHandler([\n            new Response(403, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),\n        ]);\n\n        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));\n\n        $this->assertEquals('access', $core->auth()->authenticate());\n\n        $mock = new MockHandler([\n            new Response(403, [], json_encode(['access_token' => 'access', 'expires_in' => 600000])),\n        ]);\n\n        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));\n\n        $core->auth()->flushTokens();\n\n        $this->expectException(ErrorException::class);\n\n        $core->auth()->authenticate();\n    }\n}\n"
  },
  {
    "path": "tests/Unit/ConfigurationRepositoryTest.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse SmoDav\\Mpesa\\Native\\NativeConfig;\nuse SmoDav\\Mpesa\\Repositories\\ConfigurationRepository;\nuse SmoDav\\Mpesa\\Tests\\TestCase;\n\nclass ConfigurationRepositoryTest extends TestCase\n{\n    /**\n     * Create the native configuration.\n     *\n     * @return NativeConfig\n     */\n    protected function nativeConfig()\n    {\n        return new NativeConfig(NativeImplementationsTest::CONFIG_FILE);\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanConstruct()\n    {\n        $repository = new ConfigurationRepository($this->nativeConfig());\n        $this->assertInstanceOf(ConfigurationRepository::class, $repository);\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanExtractConfigAndAccountValues()\n    {\n        $repository = new ConfigurationRepository($this->nativeConfig());\n\n        $this->assertEquals('test', $repository->config('default'));\n        $this->assertTrue($repository->useAccount('test')->getAccountKey('sandbox'));\n        $this->assertFalse($repository->useAccount('production')->getAccountKey('sandbox'));\n\n        $this->assertEquals('default', $repository->config('fake_config', 'default'));\n        $this->assertEquals(\n            'default_account',\n            $repository->useAccount('production')->getAccountKey('fake_account_config', 'default_account')\n        );\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanResolveUrl()\n    {\n        $repository = new ConfigurationRepository($this->nativeConfig());\n        $this->assertEquals(ConfigurationRepository::SANDBOX_URL . 'test-url', $repository->url('test-url'));\n        $this->assertEquals(\n            ConfigurationRepository::PRODUCTION_URL . 'test-url',\n            $repository->useAccount('production')->url('test-url')\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Unit/NativeImplementationsTest.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse InvalidArgumentException;\nuse SmoDav\\Mpesa\\Native\\NativeCache;\nuse SmoDav\\Mpesa\\Native\\NativeConfig;\nuse SmoDav\\Mpesa\\Tests\\TestCase;\n\nclass NativeImplementationsTest extends TestCase\n{\n    const CONFIG_FILE = __DIR__ . '/../files/mpesa.php';\n    const CACHE_LOCATION = __DIR__ . '/../files/cache';\n\n    /**\n     * @return void\n     */\n    public function testCanUseNativeConfig()\n    {\n        $config = new NativeConfig();\n        $this->assertEquals(true, $config->get('mpesa.accounts.staging.sandbox'));\n\n        $config = new NativeConfig(self::CONFIG_FILE);\n        $this->assertEquals(true, $config->get('mpesa.accounts.test.sandbox'));\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanGetWholeConfiguration()\n    {\n        $config = new NativeConfig(self::CONFIG_FILE);\n\n        $this->assertEquals(require(self::CONFIG_FILE), $config->get('mpesa'));\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanUseNativeCache()\n    {\n        $cache = new NativeCache(self::CACHE_LOCATION);\n\n        $cache->put('test', 123, 10);\n        $this->assertEquals(123, $cache->get('test'));\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanOverwriteCacheItem()\n    {\n        $cache = new NativeCache(self::CACHE_LOCATION);\n\n        $cache->put('overwrite', 123, 10);\n        $this->assertEquals(123, $cache->get('overwrite'));\n\n        $cache->put('overwrite', 456, 10);\n        $this->assertEquals(456, $cache->get('overwrite'));\n    }\n\n    /**\n     * @return void\n     */\n    public function testCacheExpires()\n    {\n        $cache = new NativeCache(self::CACHE_LOCATION);\n\n        $cache->put('expire', 123, 1);\n        $this->assertEquals(123, $cache->get('expire'));\n        sleep(1);\n        $this->assertEquals(null, $cache->get('expire'));\n    }\n\n    /**\n     * @return void\n     */\n    public function testCanConstructWithoutConfig()\n    {\n        $cache = new NativeCache;\n        $cache->put('without_config', 'YES', 5);\n        $this->assertEquals('YES', $cache->get('without_config'));\n    }\n\n    /**\n     * @return void\n     */\n    public function testTTLImplementation()\n    {\n        $cache = new NativeCache(self::CACHE_LOCATION);\n\n        $this->expectException(InvalidArgumentException::class);\n        $cache->put('expire', 123, 'fake');\n    }\n\n    /**\n     * @return void\n     */\n    public function testShouldPullCacheItem()\n    {\n        $cache = new NativeCache(self::CACHE_LOCATION);\n        $cache->put('to_pull', 'YES', 5);\n        $this->assertEquals('YES', $cache->get('to_pull'));\n        $this->assertEquals('YES', $cache->pull('to_pull'));\n        $this->assertEquals('pulled', $cache->pull('to_pull', 'pulled'));\n    }\n}\n"
  },
  {
    "path": "tests/Unit/RegistrarTest.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\HandlerStack;\nuse GuzzleHttp\\Psr7\\Response;\nuse SmoDav\\Mpesa\\C2B\\Registrar;\nuse SmoDav\\Mpesa\\Tests\\TestCase as TestCase;\n\nclass RegistrarTest extends TestCase\n{\n    /*\n     * Test that authenticator works.\n     *\n     * @test\n     **/\n    public function testRegisterUrls()\n    {\n        $mock = new MockHandler([\n            new Response(200, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),\n            new Response(200, [], json_encode([\n                'OriginatorConverstionID' => '123',\n                'ConversationID' => '500',\n                'ResponseDescription' => 'Success',\n            ])),\n        ]);\n\n        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));\n        $core->auth()->flushTokens();\n\n        $registrar = new Registrar($core);\n\n        $response = $registrar->submit(123456, 'http://example.com', 'http://example.com');\n\n        $this->assertEquals('123', $response->OriginatorConverstionID);\n        $this->assertEquals('500', $response->ConversationID);\n        $this->assertEquals('Success', $response->ResponseDescription);\n    }\n}\n"
  },
  {
    "path": "tests/Unit/STKTest.php",
    "content": "<?php\n\nnamespace SmoDav\\Mpesa\\Tests\\Unit;\n\nuse GuzzleHttp\\Client;\nuse GuzzleHttp\\Handler\\MockHandler;\nuse GuzzleHttp\\HandlerStack;\nuse GuzzleHttp\\Psr7\\Response;\nuse SmoDav\\Mpesa\\C2B\\STK;\nuse SmoDav\\Mpesa\\Tests\\TestCase as TestCase;\n\nclass STKTest extends TestCase\n{\n    /*\n     * Test that authenticator works.\n     *\n     * @test\n     **/\n    public function testPushRequest()\n    {\n        $response = [\n            'MerchantRequestID' => '19465-780693-1',\n            'CheckoutRequestID' => 'ws_CO_27072017154747416',\n            'ResponseCode' => 0,\n            'ResponseDescription' => 'Success. Request accepted for processing',\n            'CustomerMessage' => 'Success. Request accepted for processing',\n        ];\n\n        $mock = new MockHandler([\n            new Response(200, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),\n            new Response(200, [], json_encode($response)),\n        ]);\n\n        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));\n        $core->auth()->flushTokens();\n\n        $stk = new STK($core);\n\n        $response = $stk->push(100, 254722000000, 'Test', 'Awesome');\n\n        $this->assertEquals('19465-780693-1', $response->MerchantRequestID);\n        $this->assertEquals('ws_CO_27072017154747416', $response->CheckoutRequestID);\n        $this->assertEquals(0, $response->ResponseCode);\n        $this->assertEquals('Success. Request accepted for processing', $response->ResponseDescription);\n        $this->assertEquals('Success. Request accepted for processing', $response->CustomerMessage);\n    }\n\n    public function testValidateTransaction()\n    {\n        $response = [\n            'MerchantRequestID' => '19465-780693-1',\n            'CheckoutRequestID' => 'ws_CO_27072017154747416',\n            'ResponseCode' => 0,\n            'ResponseDescription' => 'Success. Request accepted for processing',\n            'ResultCode' => 0,\n            'ResultDesc' => 'The service request is processed successfully.'\n        ];\n\n        $mock = new MockHandler([\n            new Response(200, [], json_encode(['access_token' => 'access', 'expires_in' => 3599])),\n            new Response(200, [], json_encode($response)),\n        ]);\n\n        $core = $this->core(new Client(['handler' => HandlerStack::create($mock)]));\n        $core->auth()->flushTokens();\n\n        $stk = new STK($core);\n\n        $response = $stk->validate('ws_CO_27072017154747416');\n\n        $this->assertEquals('19465-780693-1', $response->MerchantRequestID);\n        $this->assertEquals('ws_CO_27072017154747416', $response->CheckoutRequestID);\n        $this->assertEquals(0, $response->ResponseCode);\n        $this->assertEquals('Success. Request accepted for processing', $response->ResponseDescription);\n        $this->assertEquals(0, $response->ResultCode);\n        $this->assertEquals('The service request is processed successfully.', $response->ResultDesc);\n    }\n}\n"
  },
  {
    "path": "tests/files/.gitignore",
    "content": "/cache"
  },
  {
    "path": "tests/files/mpesa.php",
    "content": "<?php\n\nreturn [\n    /*\n    |--------------------------------------------------------------------------\n    | Default Account\n    |--------------------------------------------------------------------------\n    |\n    | This is the default account to be used when none is specified.\n    */\n\n    'default' => 'test',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Native File Cache Location\n    |--------------------------------------------------------------------------\n    |\n    | When using the Native Cache driver, this will be the relative directory\n    | where the cache information will be stored.\n    */\n\n    'cache_location' => __DIR__ . '/cache',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Accounts\n    |--------------------------------------------------------------------------\n    |\n    | These are the accounts that can be used with the package. You can configure\n    | as many as needed. Two have been setup for you.\n    |\n    | Sandbox: Determines whether to use the sandbox, Possible values: sandbox | production\n    | Initiator: This is the username used to authenticate the transaction request\n    | LNMO:\n    |    paybill: Your paybill number\n    |    shortcode: Your business shortcode\n    |    passkey: The passkey for the paybill number\n    |    callback: Endpoint that will be be queried on completion or failure of the transaction.\n    |\n    */\n\n    'accounts' => [\n        'test' => [\n            'sandbox' => true,\n            'key' => 'key1',\n            'secret' => 'secret1',\n            'initiator' => 'apitest363',\n            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            'lnmo' => [\n                'paybill' => 174379,\n                'shortcode' => 174379,\n                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',\n                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            ]\n        ],\n\n        'production' => [\n            'sandbox' => false,\n            'key' => 'key2',\n            'secret' => 'secret2',\n            'initiator' => 'apitest363',\n            'id_validation_callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            'lnmo' => [\n                'paybill' => 174379,\n                'shortcode' => 174379,\n                'passkey' => 'bfb279f9aa9bdbcf158e97dd71a467cd2e0c893059b10f78e6b72ada1ed2c919',\n                'callback' => 'http://example.com/callback?secret=some_secret_hash_key',\n            ]\n        ],\n    ],\n];\n"
  }
]