├── .DS_Store ├── src ├── .DS_Store ├── assets │ └── .DS_Store ├── Exceptions │ ├── SplitPaymentException.php │ └── IntegrationTypeException.php ├── Models │ └── InterswitchPayment.php ├── Facades │ └── Interswitch.php ├── views │ ├── interswitch │ │ └── email.blade.php │ ├── pay.blade.php │ └── logs.blade.php ├── routes │ └── web.php ├── Mail │ └── InterswitchMailable.php ├── InterswitchServiceProvider.php ├── database │ └── migrations │ │ └── 2020_11_26_092007_interwitch_payments.php ├── Http │ └── Controllers │ │ └── InterswitchController.php ├── config │ └── interswitch.php └── Interswitch.php ├── vendor ├── autoload.php └── composer │ ├── autoload_classmap.php │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_static.php │ ├── LICENSE │ ├── autoload_real.php │ └── ClassLoader.php ├── composer.json ├── LICENSE.md └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toyosi12/laravel-interswitch/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toyosi12/laravel-interswitch/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toyosi12/laravel-interswitch/HEAD/src/assets/.DS_Store -------------------------------------------------------------------------------- /src/Exceptions/SplitPaymentException.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src'), 10 | ); 11 | -------------------------------------------------------------------------------- /src/views/interswitch/email.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # {{$message['responseDescription']}} 3 |
4 |

Dear {{$message['customerName']}},
5 | Your payment of ₦ {{number_format(($message['amount']) / 100, 2)}} was successful.
6 | Payment Reference - {{$message['paymentReference']}} 7 |

8 |
9 | 10 | Thanks,
11 | {{ config('app.name') }} 12 | @endcomponent 13 | -------------------------------------------------------------------------------- /src/routes/web.php: -------------------------------------------------------------------------------- 1 | 'Toyosi\Interswitch\Http\Controllers'], function(){ 3 | Route::post('interswitch-pay', 'InterswitchController@pay'); 4 | 5 | /** 6 | * The redirect url after transaction attempt. 7 | * Do not change this 8 | */ 9 | Route::post('interswitch-redirect', 'InterswitchController@redirect'); 10 | 11 | /** 12 | * Log of all transactions. Implement route guards as necessary 13 | */ 14 | Route::get('interswitch-logs', 'InterswitchController@logs'); 15 | 16 | 17 | /** 18 | * Requery incomplete transactions 19 | */ 20 | Route::post('interswitch-requery', 'InterswitchController@requeryTransaction'); 21 | 22 | 23 | 24 | 25 | 26 | 27 | }); -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toyosi/laravel-interswitch", 3 | "description": "This package simplifies interswitch payment integration in laravel", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Toyosi Oyelayo", 8 | "email": "toyosioyelayo@gmail.com" 9 | } 10 | ], 11 | "minimum-stability": "dev", 12 | "require": {}, 13 | "autoload": { 14 | "psr-4": { 15 | "Toyosi\\Interswitch\\": "src/" 16 | } 17 | }, 18 | "extra": { 19 | "laravel": { 20 | "providers": [ 21 | "Toyosi\\Interswitch\\InterswitchServiceProvider" 22 | ], 23 | "aliases": { 24 | "Interswitch": "Toyosi\\Interswitch\\Facade\\Interswitch" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Mail/InterswitchMailable.php: -------------------------------------------------------------------------------- 1 | message = $message; 22 | } 23 | 24 | /** 25 | * Build the message. 26 | * 27 | * @return $this 28 | */ 29 | public function build() 30 | { 31 | return $this->markdown('interswitch::interswitch.email')->with(['message' => $this->message]) 32 | ->subject('Interswitch Payment'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Toyosi\\Interswitch\\' => 19, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'Toyosi\\Interswitch\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/src', 20 | ), 21 | ); 22 | 23 | public static function getInitializer(ClassLoader $loader) 24 | { 25 | return \Closure::bind(function () use ($loader) { 26 | $loader->prefixLengthsPsr4 = ComposerStaticInitf7a05e23b584abc0228a301c87211676::$prefixLengthsPsr4; 27 | $loader->prefixDirsPsr4 = ComposerStaticInitf7a05e23b584abc0228a301c87211676::$prefixDirsPsr4; 28 | 29 | }, null, ClassLoader::class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Toyosi Oyelayo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/InterswitchServiceProvider.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | namespace Toyosi\Interswitch; 9 | 10 | use Illuminate\Support\ServiceProvider; 11 | 12 | 13 | class InterswitchServiceProvider extends ServiceProvider{ 14 | public function boot(){ 15 | $this->loadRoutesFrom(__DIR__ . '/routes/web.php'); 16 | $this->loadViewsFrom(__DIR__ . '/views', 'interswitch'); 17 | $this->mergeConfigFrom(__DIR__ . '/config/interswitch.php', 'interswitch'); 18 | $this->loadMigrationsFrom(__DIR__ . '/database/migrations'); 19 | 20 | $this->publishes([ 21 | __DIR__ . '/config/interswitch.php' => config_path('interswitch.php'), 22 | __DIR__ . '/views' =>resource_path('views/vendor/interswitch') 23 | ]); 24 | } 25 | 26 | 27 | public function register(){ 28 | $this->app->bind('laravel-interswitch', function(){ 29 | return new Interswitch; 30 | }); 31 | } 32 | 33 | public function provides(){ 34 | return ['laravel-interswitch']; 35 | } 36 | } -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /src/database/migrations/2020_11_26_092007_interwitch_payments.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('customer_id'); 19 | $table->string('customer_name'); 20 | $table->string('customer_email'); 21 | $table->string('transaction_reference');//This reference is generated by this package 22 | $table->string('payment_reference')->nullable();//This reference is generated by interswitch after successful payment 23 | $table->string('retrieval_reference_number')->nullable(); 24 | $table->string('environment'); 25 | $table->bigInteger('amount_in_kobo'); 26 | $table->string('confirmed')->default('N');//This column is actually useless 27 | $table->string('response_code')->nullable(); 28 | $table->string('response_text')->nullable(); 29 | $table->timestamps(); 30 | }); 31 | } 32 | 33 | /** 34 | * Reverse the migrations. 35 | * 36 | * @return void 37 | */ 38 | public function down() 39 | { 40 | // 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/views/pay.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Payment Page 7 | 8 | 9 |

Loading...

10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | @if(config('interswitch.split')) 22 | 23 | 24 | @endif 25 | 26 |
27 | 28 | 34 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 30 | if ($useStaticLoader) { 31 | require_once __DIR__ . '/autoload_static.php'; 32 | 33 | call_user_func(\Composer\Autoload\ComposerStaticInitf7a05e23b584abc0228a301c87211676::getInitializer($loader)); 34 | } else { 35 | $map = require __DIR__ . '/autoload_namespaces.php'; 36 | foreach ($map as $namespace => $path) { 37 | $loader->set($namespace, $path); 38 | } 39 | 40 | $map = require __DIR__ . '/autoload_psr4.php'; 41 | foreach ($map as $namespace => $path) { 42 | $loader->setPsr4($namespace, $path); 43 | } 44 | 45 | $classMap = require __DIR__ . '/autoload_classmap.php'; 46 | if ($classMap) { 47 | $loader->addClassMap($classMap); 48 | } 49 | } 50 | 51 | $loader->register(true); 52 | 53 | return $loader; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Http/Controllers/InterswitchController.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | namespace Toyosi\Interswitch\Http\Controllers; 9 | 10 | use App\Http\Controllers\Controller; 11 | use Illuminate\Http\Request; 12 | use Illuminate\Support\Facades\Validator; 13 | use Illuminate\Support\Facades\Redirect; 14 | use Toyosi\Interswitch\Facades\Interswitch; 15 | use Toyosi\Interswitch\Models\InterswitchPayment; 16 | use Toyosi\Interswitch\Exceptions\IntegrationTypeException; 17 | 18 | class InterswitchController extends Controller{ 19 | 20 | public function pay(Request $request){ 21 | /** 22 | * Verify that a valid integration is used 23 | */ 24 | Interswitch::verifyGateway(); 25 | 26 | /** 27 | * customerId, customerName, customerEmail and amount are 28 | * all expected to be passed throught the form. 29 | * They are all required. 30 | * 31 | * Amount must be greater than 0 and must be a number 32 | */ 33 | $validator = Validator::make($request->all(), [ 34 | 'customerID' => 'required|numeric', 35 | 'customerName' => 'required|string', 36 | 'customerEmail' => 'required|email', 37 | 'amount' => 'required|gt:0|numeric' 38 | ]); 39 | 40 | /** 41 | * If validation fails, return error 42 | */ 43 | 44 | if($validator->fails()){ 45 | return $validator->errors(); 46 | } 47 | 48 | 49 | /** 50 | * if all validations are passed, 51 | * send request data to Interswitch class to initialize transaction. 52 | * This is the beginning of the phase where the user is redirected to 53 | * the payment page provided by interswitch 54 | */ 55 | $transactionData = Interswitch::initializeTransaction($request->all()); 56 | 57 | /** 58 | * Return to hidden forms (with the required data) 59 | * This sends a post request to interswitch servers 60 | * which causes the user to be redirected to the payment page(where they'd enter their card details) 61 | */ 62 | return view('interswitch::pay', compact('transactionData')); 63 | } 64 | 65 | /** 66 | * Redirect the user after payment attempt 67 | */ 68 | public function redirect(Request $request){ 69 | $response = Interswitch::getTransactionStatus($request->all()); 70 | 71 | 72 | $rebuiltResponse = [ 73 | 'paymentReference' => $response['PaymentReference'], 74 | 'responseCode' => $response['ResponseCode'], 75 | 'responseDescription' => $response['ResponseDescription'], 76 | 'amount' => $response['Amount'], 77 | 'transactionDate' => $response['TransactionDate'], 78 | 'customerEmail' => $response['customerEmail'], 79 | 'customerName' => $response['customerName'] 80 | ]; 81 | 82 | 83 | /** 84 | * Send email to user on successful transaction if email notification is enabled 85 | */ 86 | if(in_array($rebuiltResponse['responseCode'], ['00', '10', '11'])){ 87 | Interswitch::sendTransactionMail($rebuiltResponse); 88 | } 89 | 90 | $redirectURL = Interswitch::attachQueryString($rebuiltResponse); 91 | 92 | return redirect()->to($redirectURL); 93 | } 94 | 95 | public function requeryTransaction(Request $request){ 96 | $validator = Validator::make($request->all(), [ 97 | 'txnref' => 'required' 98 | ]); 99 | 100 | if($validator->fails()){ 101 | return response()->json([ 102 | 'success' => false, 103 | 'message' => "Requery Failed", 104 | 'data' => $validator->errors() 105 | ]); 106 | } 107 | 108 | $response = Interswitch::getTransactionStatus($request->all()); 109 | 110 | return response()->json([ 111 | 'success' => true, 112 | 'message' => "Requery Successful", 113 | 'data' => $response 114 | ]); 115 | 116 | } 117 | 118 | public function logs(){ 119 | $logs = InterswitchPayment::all()->sortByDesc("created_at");; 120 | return view('interswitch::logs', compact('logs')); 121 | } 122 | 123 | 124 | } -------------------------------------------------------------------------------- /src/views/logs.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Interswitch Transaction Logs 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 |
24 |

Transaction Logs

25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | @foreach($logs as $key => $log) 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | @endforeach 63 | 64 | 65 |
S/NCustomer IDCustomer NameCustomer EmailTransaction ReferencePayment ReferenceAmountResponse CodeResponse TextEnvironmentDateAction
{{ $key + 1 }}{{ $log['customer_id'] }}{{ $log['customer_name'] }}{{ $log['customer_email'] }}{{ $log['transaction_reference'] }}{{ $log['payment_reference'] }} ₦ {{ number_format(($log['amount_in_kobo'] / 100), 2) }}{{ $log['response_code'] }}{{ $log['response_text'] }}{{ $log['environment'] }}{{ $log['created_at'] }}
66 | 67 |
68 |
69 |
70 |
71 |
72 | 104 | 105 | -------------------------------------------------------------------------------- /src/config/interswitch.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | 8 | return [ 9 | /** 10 | * Integration method. Could be WEBPAY, PAYDIRECT or COLLEGEPAY. Default is WEBPAY 11 | */ 12 | 'gatewayType' => env('INTERSWITCH_GATEWAY_TYPE', 'WEBPAY'), 13 | 14 | /** 15 | * Currency, Naira is default 16 | */ 17 | 'currency' => env('INTERSWITCH_CURRENCY', 566), 18 | 19 | /** 20 | * Site redirection url as defined by the user 21 | */ 22 | 'siteRedirectURL' => env('INTERSWITCH_SITE_REDIRECT_URL'), 23 | 24 | /** 25 | * Site redirection path that works internally. Do not change 26 | */ 27 | 'fixedRedirectURL' => 'interswitch-redirect', 28 | 29 | /** 30 | * current environment (TEST or LIVE) 31 | */ 32 | 'env' => env('INTERSWITCH_ENV', 'TEST'), 33 | 34 | /** 35 | * Split payment or not 36 | */ 37 | 'split' => env('INTERSWITCH_SPLIT', false), 38 | 39 | /** 40 | * Name of Institution(CollegePay split payment only) 41 | */ 42 | 'college' => env('INTERSWITCH_COLLEGE', 'Unnamed'), 43 | 44 | /** 45 | * send mail to user on successfull completion of transaction 46 | */ 47 | 'send_mail' => env('INTERSWITCH_SEND_MAIL', false), 48 | 49 | /** 50 | * Split payment configurations 51 | * WARNING: Total percentage allocation for all items must sum up to 100. 52 | * WARNING: IF YOU ARE NOT USING SPLIT PAYMENT, LEAVE splitDetails KEY AS AN EMPTY ARRAY, DO NOT REMOVE COMPLETELY 53 | */ 54 | 'splitDetails' => [ 55 | [ 56 | 'itemName' => 'item1', 57 | 'bankID' => 7, 58 | 'accountNumber' => 1234567890, 59 | 'percentageAllocation' => 50 60 | 61 | ], 62 | [ 63 | 'itemName' => 'item2', 64 | 'bankID' => 10, 65 | 'accountNumber' => 4564567890, 66 | 'percentageAllocation' => 50 67 | ] 68 | ], 69 | 70 | /** 71 | * Test environment parameters 72 | */ 73 | 'test' => [ 74 | /** 75 | * Parameters for non-split payments 76 | */ 77 | 'noSplit' => [ 78 | 'webPay' => [ 79 | 'macKey' => 'D3D1D05AFE42AD50818167EAC73C109168A0F108F32645C8B59E897FA930DA44F9230910DAC9E20641823799A107A02068F7BC0F4CC41D2952E249552255710F', 80 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 81 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 82 | 'productID' => 6205, 83 | 'payItemID' => 101 84 | ], 85 | 'payDirect' => [ 86 | 'macKey' => 'E187B1191265B18338B5DEBAF9F38FEC37B170FF582D4666DAB1F098304D5EE7F3BE15540461FE92F1D40332FDBBA34579034EE2AC78B1A1B8D9A321974025C4', 87 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 88 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 89 | 'productID' => 6204, 90 | 'payItemID' => 103 91 | ], 92 | 'collegePay' => [ 93 | 'macKey' => 'CEF793CBBE838AA0CBB29B74D571113B4EA6586D3BA77E7CFA0B95E278364EFC4526ED7BD255A366CDDE11F1F607F0F844B09D93B16F7CFE87563B2272007AB3', 94 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 95 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 96 | 'productID' => 6207, 97 | 'payItemID' => 103 98 | ] 99 | ], 100 | 101 | /** 102 | * Paramters for split payments 103 | */ 104 | 'split' => [ 105 | 'webPay' => [ 106 | 'macKey' => 'D3D1D05AFE42AD50818167EAC73C109168A0F108F32645C8B59E897FA930DA44F9230910DAC9E20641823799A107A02068F7BC0F4CC41D2952E249552255710F', 107 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 108 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 109 | 'productID' => 6205, 110 | 'payItemID' => 101 111 | ], 112 | 'payDirect' => [ 113 | 'macKey' => 'E187B1191265B18338B5DEBAF9F38FEC37B170FF582D4666DAB1F098304D5EE7F3BE15540461FE92F1D40332FDBBA34579034EE2AC78B1A1B8D9A321974025C4', 114 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 115 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 116 | 'productID' => 6204, 117 | 'payItemID' => 101 118 | ], 119 | 'collegePay' => [ 120 | 'macKey' => 'CEF793CBBE838AA0CBB29B74D571113B4EA6586D3BA77E7CFA0B95E278364EFC4526ED7BD255A366CDDE11F1F607F0F844B09D93B16F7CFE87563B2272007AB3', 121 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 122 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 123 | 'productID' => 6207, 124 | 'payItemID' => 101 125 | ] 126 | ] 127 | ], 128 | 129 | /** 130 | * Live environment parameters 131 | */ 132 | 'live' => [ 133 | 'macKey' => env('INTERSWITCH_MAC_KEY'), 134 | 'initializationURL' => 'https://sandbox.interswitchng.com/webpay/pay', 135 | 'transactionStatusURL' => 'https://sandbox.interswitchng.com/webpay/api/v1/gettransaction.json', 136 | 'productID' => env('INTERSWITCH_PRODUCT_ID'), 137 | 'payItemID' => env('INTERSWITCH_PAY_ITEM_ID') 138 | ] 139 | ]; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-interswitch 2 | 3 | [![Issues]( https://img.shields.io/github/issues/toyosi12/laravel-interswitch)](https://github.com/toyosi12/laravel-interswitch/issues) 4 | [![Forks]( https://img.shields.io/github/forks/toyosi12/laravel-interswitch)](https://github.com/toyosi12/laravel-interswitch/network/members) 5 | [![Stars]( https://img.shields.io/github/stars/toyosi12/laravel-interswitch)](https://github.com/toyosi12/laravel-interswitch/stargazers) 6 | 7 | > A laravel package to easily integrate interswitch 8 | 9 | ## Installation 10 | 11 | [PHP](https://php.net) 7.2+ and [Composer](https://getcomposer.org) are required. 12 | 13 | To get the latest version of Laravel Interswitch, simply require it 14 | 15 | ```bash 16 | composer require toyosi/laravel-interswitch 17 | ``` 18 | Once installed, the package automatically registers its service provider and facade. 19 | 20 | Next, run migration to get the database table that logs all transactions: 21 | 22 | ```bash 23 | php artisan migrate 24 | ``` 25 | 26 | ## Configuration 27 | You can publish the configuration file using this command: 28 | ```bash 29 | php artisan vendor:publish --provider="Toyosi\Interswitch\InterswitchServiceProvider" 30 | ``` 31 | A configuration file 'interswitch.php' with some defaults is placed in your config directory. 32 | 33 | With this package, you can easily integrate the three major payment types of interswitch which are webpay, paydirect or collegepay. Webpay is the default type. 34 | 35 | ## Payment Flow 36 | The payment flow described below applies to interswitch and many other payment gateways 37 | 38 | 1. User clicks a button to make payment, the user is redirected to the payment provider's site, usually by submitting a form with hidden fields. A hash is generated from these fields. 39 | 2. On the payment provider's site, card details are entered. 40 | 3. The user is redirected back with details of the transaction indicating a successful or failed transaction. 41 | 42 | ## Usage 43 | 44 | ### Test Environment 45 | 46 | ### 1. Open .env and add: 47 | ```php 48 | INTERSWITCH_SITE_REDIRECT_URL="${APP_URL}/response" 49 | ``` 50 | This is the only variable in the test environment that is required. 'response' as indicated above could be anything. The specified value indicates the url the user is redirected to after every transaction. 51 | Don't forget to add this route in your project. In this case, it will be: 52 | ```php 53 | Route::get('response', function(){ 54 | return $_GET; 55 | }); 56 | ``` 57 | Note: please ensure APP_URL is correctly defined. 58 | 59 | ### 2. Create payment route and view 60 | Create your payment route in web.php. Something like: 61 | ```php 62 | Route::get('pay', function(){ 63 | return view('payment'); 64 | }); 65 | ``` 66 | Then create the view. In this case, 'payment.blade.php'. The view can be like so: 67 | ```html 68 |
69 | 70 | 71 | 72 | 73 | 75 |
76 | ``` 77 | **Note: 'amount' field must be in kobo** 78 | 79 | Navigate to your newly created route, click the 'Pay Now' button and follow the required steps. 80 | Note that the form is submitted to route 'interswitch-pay', this is predefined in the package. 81 | All the fields are required. On clicking the 'Pay Now' button, the user is redirected to interswitch's payment page, where card details are entered. The user is then redirected back to your website as indicated by 'INTERSWITCH_SITE_REDIRECT_URL'. 82 | This url will return the result of the transaction. Sample response will be like so: 83 | ```php 84 | { 85 | "paymentReference": "FBN|WEB|CDEM|10-12-2020|383104", 86 | "responseCode": "00", 87 | "responseDescription": "Approved Successful", 88 | "amount": "12000", 89 | "transactionDate": "2020-12-10T15:59:37.827", 90 | "customerEmail": "toyosioyelayo@gmail.com", 91 | "customerName": "Toyosi Oyelayo" 92 | } 93 | ``` 94 | A list of test cards [can be found here](https://sandbox.interswitchng.com/docbase/docs/webpay/test-cards). 95 | 96 | 97 | ### Live Environment 98 | The same processes described in the test environment above also applies to the live environment. Do note that Interswitch does certain checks on your website before it can be approved to recieve live payments: 99 | 1. You must have the interswitch logo on your website 100 | 2. You must have filled and submitted the User Acceptance Test Form which has to be approved by interswitch. You can [download the form here](https://sandbox.interswitchng.com/docbase/docs/webpay/merchant-user-acceptance-testing) 101 | 3. After this, you are given your unique Product ID, MAC ID and Pay Item ID. These have to be included in your .env file like so: 102 | 103 | ```php 104 | INTERSWITCH_PRODUCT_ID= 105 | INTERSWITCH_MAC_KEY= 106 | INTERSWITCH_PAY_ITEM_ID= 107 | ``` 108 | 109 | You also have to change the environment to live like so: 110 | ```php 111 | INTERSWITCH_ENV=LIVE 112 | ``` 113 | 114 | To change the integration type, use: 115 | ```php 116 | INTERSWITCH_GATEWAY_TYPE= 117 | ``` 118 | The values could be 'WEBPAY', 'PAYDIRECT' or 'COLLEGEPAY'. 'WEBPAY' is the default. 119 | 120 | ## Split Payment 121 | With split payment, you can divide money recieved on your site into multiple accounts. This is only available on COLLEGEPAY. Split implementation uses XML which I have handled in the package. You can setup split payments in two easy steps: 122 | ### 1. Enable split payments in .env like so: 123 | ```php 124 | INTERSWITCH_SPLIT=true 125 | ``` 126 | ### 2. Configure accounts and percentage allocation. 127 | You need to specify the account numbers to be credited and percentage of the total amount to be credited into each account. To do this, open 'config/interswitch.php' and edit the key 'splitDetails'. Do note that this key already exists, you only need to edit it: 128 | ```php 129 | 'splitDetails' => [ 130 | [ 131 | 'itemName' => 'item1', 132 | 'bankID' => 7, 133 | 'accountNumber' => 1234567890, 134 | 'percentageAllocation' => 50 135 | 136 | ], 137 | [ 138 | 'itemName' => 'item2', 139 | 'bankID' => 10, 140 | 'accountNumber' => 4564567890, 141 | 'percentageAllocation' => 50 142 | ] 143 | ], 144 | ``` 145 | In the above example, two bank accounts are indicated and the total amount is split into two equal parts (50% each) as indicated with 'percentageAllocation'. In the test environment, 'accountNumber' can be any 10 digit number. Don't forget to change to valid account numbers in the live environment. The package handles the conversion into XML and other necessary stuffs. 146 | Note: You can find the [list of bank IDs here.](https://sandbox.interswitchng.com/docbase/docs/collegepay-web/xml-split-bank-codes) 147 | 148 | ## Transaction Logs 149 | You can find all transaction logs at the 'interswitch-logs' route. Don't forget to protect this route. You don't want just any user to have access to it. 150 | 151 | ### Requerying Transactions 152 | Sometimes, things might go wrong while a user is making a payment. It could be power failure or flaky internet connectivity. To complete an already started payment process, you can click the 'requery' button in 'interswitch logs'. This updates the transaction as necessary. 153 | 154 | ## Further Info 155 | Interswitch requires that the user gets a mail after every successful transaction. This has already been added to the package. You only need to add the following to your .env file: 156 | ```php 157 | INTERSWITCH_SEND_MAIL=true 158 | ``` 159 | Ensure that your mail variables are properly set in .env. 160 | 161 | ## Contributing 162 | Do feel free to fork this repo and contribute by submitting a pull request. Let's make it better. 163 | 164 | ## Star 165 | I'd love you star this repo. Also [follow me on twitter](https://twitter.com/dev_toyosi) 166 | 167 | ## License 168 | 169 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /src/Interswitch.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | namespace Toyosi\Interswitch; 8 | use Toyosi\Interswitch\Models\InterswitchPayment; 9 | use Toyosi\Interswitch\Exceptions\SplitPaymentException; 10 | use Toyosi\Interswitch\Exceptions\IntegrationTypeException; 11 | use Illuminate\Support\Facades\Mail; 12 | use Toyosi\Interswitch\Mail\InterswitchMailable; 13 | 14 | 15 | class Interswitch{ 16 | /** 17 | * The current envirionment(test or live). 18 | */ 19 | private $env; 20 | 21 | /** 22 | * The url for redirection after payment(used internally by the system). 23 | */ 24 | private $fixedRedirectURL; 25 | 26 | /** 27 | * User defined redirect url 28 | */ 29 | private $siteRedirectURL; 30 | 31 | /** 32 | * The gateway type - webpay, paydirect or collegepay. 33 | */ 34 | private $gatewayType; 35 | 36 | /** 37 | * Unique reference for transaction 38 | */ 39 | private $transactionReference; 40 | 41 | /** 42 | * The currency being used, Naira is the default. 43 | */ 44 | private $currency; 45 | 46 | /** 47 | * mac key as provided by interswitch 48 | */ 49 | private $macKey; 50 | 51 | /** 52 | * product ID as provided by interswitch 53 | */ 54 | private $productID; 55 | 56 | /** 57 | * pay item ID as provided by interswitch 58 | */ 59 | private $payItemID; 60 | 61 | /** 62 | * URL where user is directed to to make payment 63 | */ 64 | private $initializationURL; 65 | 66 | /** 67 | * URL to check the status of transaction 68 | */ 69 | private $transactionStatusURL; 70 | 71 | /** 72 | * split or not 73 | */ 74 | private $split; 75 | 76 | /** 77 | * Configurations for split payment 78 | */ 79 | private array $splitDetails; 80 | 81 | /** 82 | * Name of institution (College pay split payment) 83 | */ 84 | private $college; 85 | 86 | private $amountInKobo; 87 | 88 | /** 89 | * Send mail after successful transaction or not 90 | */ 91 | private $sendMail; 92 | 93 | const GATEWAY_TYPES = ['WEBPAY', 'COLLEGEPAY', 'PAYDIRECT']; 94 | 95 | public function __construct(){ 96 | $this->env = config('interswitch.env'); 97 | $this->fixedRedirectURL = env('APP_URL') . '/' .config('interswitch.fixedRedirectURL'); 98 | $this->siteRedirectURL = config('interswitch.siteRedirectURL'); 99 | $this->gatewayType = config('interswitch.gatewayType'); 100 | $this->currency = config('interswitch.currency'); 101 | $this->split = config('interswitch.split'); 102 | $this->college = config('interswitch.college'); 103 | $this->transactionReference = $this->generateTransactionReference(); 104 | $this->sendMail = config('interswitch.send_mail'); 105 | $this->environmentSelector(); 106 | $this->splitDetails = config('interswitch.splitDetails'); 107 | } 108 | 109 | /** 110 | * Verify that a valid integration is used 111 | */ 112 | public function verifyGateway(){ 113 | if(!in_array($this->gatewayType, self::GATEWAY_TYPES)){ 114 | throw new IntegrationTypeException("Unrecongnized Integration Type"); 115 | } 116 | } 117 | 118 | 119 | /** 120 | * This method gets all the required data to be supplied to 121 | * the interswitch payment page 122 | */ 123 | public function initializeTransaction($request){ 124 | $this->amountInKobo = $request['amount']; 125 | $hash = $this->generateTransactionHash($this->amountInKobo, $this->transactionReference); 126 | 127 | /** 128 | * save payment data into the database 129 | */ 130 | InterswitchPayment::create([ 131 | 'customer_id' => $request['customerID'], 132 | 'customer_name' => $request['customerName'], 133 | 'customer_email' => $request['customerEmail'], 134 | 'transaction_reference' => $this->transactionReference, 135 | 'environment' => $this->env, 136 | 'response_code' => -1, 137 | 'response_text' => 'Pending', 138 | 'amount_in_kobo' => $this->amountInKobo, 139 | ]); 140 | 141 | 142 | /** 143 | * Data supplied to the interswitch interface 144 | */ 145 | $computedData = [ 146 | 'transactionReference' => $this->transactionReference, 147 | 'productID' => $this->productID, 148 | 'payItemID' => $this->payItemID, 149 | 'amount' => $this->amountInKobo, 150 | 'siteRedirectURL' => $this->fixedRedirectURL, 151 | 'macKey' => $this->macKey, 152 | 'currency' => $this->currency, 153 | 'customerID' => $request['customerID'], 154 | 'customerName' => $request['customerName'], 155 | 'hash' => $hash, 156 | 'initializationURL' => $this->initializationURL, 157 | 'splitData' => $this->generateXMLForSplitPayments() 158 | ]; 159 | 160 | 161 | return $computedData; 162 | } 163 | 164 | 165 | /** 166 | * Get the status of a transaction 167 | */ 168 | public function getTransactionStatus($request){ 169 | /** 170 | * Generate required hash using SHA512 algorithm 171 | */ 172 | $hash = hash('SHA512', $this->productID . $request['txnref'] . $this->macKey); 173 | $transactionDetails = InterswitchPayment::where('transaction_reference', $request['txnref'])->first(); 174 | $this->amountInKobo = $transactionDetails['amount_in_kobo']; 175 | $queryString = '?productId=' . $this->productID . "&transactionreference=" . $request['txnref'] . "&amount=". $this->amountInKobo; 176 | $verificationURL = $this->transactionStatusURL . $queryString; 177 | 178 | $curl = curl_init(); 179 | curl_setopt_array($curl, array( 180 | CURLOPT_URL => $verificationURL, 181 | CURLOPT_RETURNTRANSFER => true, 182 | CURLOPT_CUSTOMREQUEST => "GET", 183 | CURLOPT_SSL_VERIFYHOST => 2, 184 | CURLOPT_SSL_VERIFYPEER => false, 185 | CURLOPT_TIMEOUT => 60, 186 | CURLOPT_POST => false, 187 | CURLOPT_HTTPHEADER => [ 188 | "content-type: application/json", 189 | "cache-control: no-cache", 190 | "Connection: keep-alive", 191 | "hash: " . $hash 192 | ], 193 | )); 194 | 195 | $response = json_decode(curl_exec($curl), true); 196 | $response['customerEmail'] = $transactionDetails['customer_email']; 197 | $response['customerName'] = $transactionDetails['customer_name']; 198 | 199 | /** 200 | * Update database with transaction status 201 | */ 202 | InterswitchPayment::where('transaction_reference', $request['txnref']) 203 | ->update([ 204 | 'payment_reference' => $response['PaymentReference'], 205 | 'retrieval_reference_number' => $response['RetrievalReferenceNumber'], 206 | 'response_code' => $response['ResponseCode'], 207 | 'response_text' => $response['ResponseDescription'] 208 | ]); 209 | return $response; 210 | 211 | } 212 | 213 | /** 214 | * This method generates a unique reference per transaction. 215 | * It concatenates the current timestamp, to ensure it is unique 216 | */ 217 | private function generateTransactionReference(){ 218 | $length = 6; 219 | $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 220 | $charactersLength = strlen($characters); 221 | $randomString = ''; 222 | for ($i = 0; $i < $length; $i++) { 223 | $randomString .= $characters[rand(0, $charactersLength - 1)]; 224 | } 225 | return $randomString . time(); 226 | } 227 | 228 | /** 229 | * Interswitch requires a hash to be generated which using the SHA512 algorithm. 230 | * The parameters to be hashed are transaction reference, product ID, pay item ID, 231 | * amount, site redirect url and mac key in that order 232 | * 233 | */ 234 | private function generateTransactionHash($_amountInKobo, $_transactionReference){ 235 | $paramters = $_transactionReference . $this->productID . $this->payItemID . $_amountInKobo . $this->fixedRedirectURL . $this->macKey; 236 | $hash = hash('SHA512', $paramters); 237 | return $hash; 238 | } 239 | 240 | /** 241 | * This method is used by environmentSelector() to detect whether split payment 242 | * is enabled or not. 243 | */ 244 | private function splitDetector(){ 245 | if($this->split){ 246 | return 'split'; 247 | }else{ 248 | return 'noSplit'; 249 | } 250 | } 251 | 252 | /** 253 | * This method converts the array of split payment details 254 | * defined in config/interswitch.php and converts to XML 255 | */ 256 | public function generateXMLForSplitPayments(){ 257 | if(!$this->split) return; 258 | 259 | /** 260 | * Prevent split if integration is not collegepay 261 | */ 262 | if($this->gatewayType != 'COLLEGEPAY'){ 263 | throw new SplitPaymentException("Split payment only works with college pay"); 264 | } 265 | $XMLString = ''; 266 | $XMLDataItems = ''; 267 | $totalPercentageAllocation = 0; 268 | 269 | /** 270 | * Verify that the total percentage allocation is exactly 100 271 | */ 272 | foreach($this->splitDetails as $splitDetail){ 273 | $totalPercentageAllocation += $splitDetail['percentageAllocation']; 274 | } 275 | if($totalPercentageAllocation !== 100){ 276 | throw new SplitPaymentException("Total percentage allocation is expected to be 100"); 277 | } 278 | 279 | /** 280 | * convert to XML 281 | */ 282 | foreach($this->splitDetails as $index => $splitDetail){ 283 | $itemID = $index + 1; 284 | 285 | $splitDetail = (object) $splitDetail; 286 | 287 | /** 288 | * In split payment, The total amount to be disbursed to banks must be total amount lef 289 | * after deducting the N300 charge 290 | */ 291 | $itemAmount = ($this->amountInKobo - 30000) * ($splitDetail->percentageAllocation / $totalPercentageAllocation); 292 | 293 | 294 | $XMLDataItems .= " 295 | 302 | "; 303 | $XMLString = " 304 | 305 | 306 | {$XMLDataItems} 307 | 308 | 309 | "; 310 | } 311 | return $XMLString; 312 | } 313 | 314 | /** 315 | * changes configurations based on the current environment(live or test) 316 | */ 317 | private function environmentSelector(){ 318 | if($this->env === 'TEST'){ 319 | if($this->gatewayType === 'WEBPAY'){ 320 | $this->productID = config("interswitch.test.{$this->splitDetector()}.webPay.productID"); 321 | $this->payItemID = config("interswitch.test.{$this->splitDetector()}.webPay.payItemID"); 322 | $this->macKey = config("interswitch.test.{$this->splitDetector()}.webPay.macKey"); 323 | $this->transactionStatusURL = config("interswitch.test.{$this->splitDetector()}.webPay.transactionStatusURL"); 324 | $this->initializationURL = config("interswitch.test.{$this->splitDetector()}.webPay.initializationURL"); 325 | }else if($this->gatewayType === 'PAYDIRECT'){ 326 | $this->productID = config("interswitch.test.{$this->splitDetector()}.payDirect.productID"); 327 | $this->payItemID = config("interswitch.test.{$this->splitDetector()}.payDirect.payItemID"); 328 | $this->macKey = config("interswitch.test.{$this->splitDetector()}.payDirect.macKey"); 329 | $this->transactionStatusURL = config("interswitch.test.{$this->splitDetector()}.payDirect.transactionStatusURL"); 330 | $this->initializationURL = config("interswitch.test.{$this->splitDetector()}.payDirect.initializationURL"); 331 | }else if($this->gatewayType === 'COLLEGEPAY'){ 332 | $this->productID = config("interswitch.test.{$this->splitDetector()}.collegePay.productID"); 333 | $this->payItemID = config("interswitch.test.{$this->splitDetector()}.collegePay.payItemID"); 334 | $this->macKey = config("interswitch.test.{$this->splitDetector()}.collegePay.macKey"); 335 | $this->transactionStatusURL = config("interswitch.test.{$this->splitDetector()}.collegePay.transactionStatusURL"); 336 | $this->initializationURL = config("interswitch.test.{$this->splitDetector()}.collegePay.initializationURL"); 337 | } 338 | }else if($this->env === 'LIVE'){ 339 | $this->productID = config('interswitch.live.productID'); 340 | $this->payItemID = config('interswitch.live.payItemID'); 341 | $this->macKey = config('interswitch.live.macKey'); 342 | $this->transactionStatusURL = config('interswitch.live.transactionStatusURL'); 343 | $this->initializationURL = config('interswitch.live.initializationURL'); 344 | } 345 | 346 | } 347 | 348 | public function attachQueryString($rebuiltResponse){ 349 | $queryString = '/?'; 350 | foreach($rebuiltResponse as $key => $response){ 351 | $queryString .= $key . '=' . $response . '&'; 352 | } 353 | 354 | /** 355 | * Form the complete url and remove the last character which is '&' 356 | */ 357 | return substr($this->siteRedirectURL . $queryString, 0, -1); 358 | } 359 | 360 | public function sendTransactionMail($rebuiltResponse){ 361 | if($this->sendMail){ 362 | Mail::to($rebuiltResponse['customerEmail'])->send(new InterswitchMailable($rebuiltResponse)); 363 | } 364 | } 365 | } -------------------------------------------------------------------------------- /vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath . '\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 383 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 384 | if (file_exists($file = $dir . $pathEnd)) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | --------------------------------------------------------------------------------