diff --git a/readme.md b/readme.md index d4f5ce1..d7c014f 100644 --- a/readme.md +++ b/readme.md @@ -1,121 +1,175 @@ -**Introduction** +# Introduction -This package seeks to help php developers implement the various Mpesa APIs without much hustle. It is based on the REST API whose documentation is available on http://developer.safaricom.co.ke. - - **Installation using composer**
- `composer require safaricom/mpesa`
- +This package seeks to help php developers implement the various Mpesa APIs without much hustle. It is based on the REST API whose documentation is available on [Safaricom Developer](http://developer.safaricom.co.ke). + +## Installation using composer + + ``` + composer require safaricom/mpesa + ``` + +## Configuration + +At your project root, create a .env file and in it set the consumer key and consumer secret as follows +```.dotenv +MPESA_CONSUMER_KEY=[consumer key] +MPESA_CONSUMER_SECRET=[consumer secret] +MPESA_ENV=[live or sandbox] +``` - **Configuration**
- At your project root, create a .env file and in it set the consumer key and consumer secret as follows - `MPESA_CONSUMER_KEY= [consumer key]`
- `MPESA_CONSUMER_SECRET=[consumer secret]`
- `MPESA_ENV=[live or sandbox]`
- For Laravel users, open the Config/App.php file and add `\Safaricom\Mpesa\MpesaServiceProvider::class` under providers and ` 'Mpesa'=> \Safaricom\Mpesa\MpesaServiceProvider::class` under aliases. +For Laravel users, open the Config/App.php file and add `\Safaricom\Mpesa\MpesaServiceProvider::class` under providers and ` 'Mpesa'=> \Safaricom\Mpesa\MpesaServiceProvider::class` under aliases. _Remember to edit the consumer_key and consumer_secret values appropriately when switching between sandbox and live_ - - **Usage** - - **Confirmation and validation urls** -**B2C Payment Request** - - This creates transaction between an M-Pesa short code to a phone number registered on M-Pesa. - -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +In order to transact from multiple paybill numbers or till numbers , you can change the configs at runtime using the config helper. -`$b2cTransaction=$mpesa->b2c($InitiatorName, $SecurityCredential, $CommandID, $Amount, $PartyA, $PartyB, $Remarks, $QueueTimeOutURL, $ResultURL, $Occasion);` +So we need to add the following to the services config file (config/services.php) +```php +return = [ +// More configs here -**Account Balance Request** - -This is used to enquire the balance on an M-Pesa BuyGoods (Till Number) +'mpesa' => [ + 'MPESA_CONSUMER_KEY' => env('MPESA_CONSUMER_KEY'), + 'MPESA_CONSUMER_SECRET' => env('MPESA_CONSUMER_SECRET'), + 'MPESA_ENV' => env('MPESA_ENV') + ] +]; +``` -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +## Usage -`$balanceInquiry=$mpesa->accountBalance($CommandID, $Initiator, $SecurityCredential, $PartyA, $IdentifierType, $Remarks, $QueueTimeOutURL, $ResultURL);` +### Confirmation and validation urls +### B2C Payment Request +This creates transaction between an M-Pesa short code to a phone number registered on M-Pesa. -**Transaction Status Request** -This is used to check the status of transaction. +```php +$mpesa= new \Safaricom\Mpesa\Mpesa(); -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +$b2cTransaction=$mpesa->b2c($InitiatorName, $SecurityCredential, $CommandID, $Amount, $PartyA, $PartyB, $Remarks, $QueueTimeOutURL, $ResultURL, $Occasion); +``` -`$trasactionStatus=$mpesa->transactionStatus($Initiator, $SecurityCredential, $CommandID, $TransactionID, $PartyA, $IdentifierType, $ResultURL, $QueueTimeOutURL, $Remarks, $Occasion);` +### Account Balance Request +This is used to enquire the balance on an M-Pesa BuyGoods (Till Number) +```php +$mpesa= new \Safaricom\Mpesa\Mpesa(); -**B2B Payment Request** +$balanceInquiry = $mpesa->accountBalance($CommandID, $Initiator, $SecurityCredential, $PartyA, $IdentifierType, $Remarks, $QueueTimeOutURL, $ResultURL); +``` -This is used to transfer funds between two companies. +### Transaction Status Request -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +This is used to check the status of transaction. -`$b2bTransaction=$mpesa->b2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRefNumber );` +```php +$mpesa = new \Safaricom\Mpesa\Mpesa();` +$trasactionStatus = $mpesa->transactionStatus($Initiator, $SecurityCredential, $CommandID, $TransactionID, $PartyA, $IdentifierType, $ResultURL, $QueueTimeOutURL, $Remarks, $Occasion); +``` +### B2B Payment Request -**C2B Payment Request** +This is used to transfer funds between two companies. -This is used to Simulate transfer of funds between a customer and business. +```php +$mpesa = new \Safaricom\Mpesa\Mpesa(); +$b2bTransaction = $mpesa->b2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRefNumber ); +``` -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +### C2B Payment Request -`$b2bTransaction=$mpesa->c2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRefNumber );` +This is used to Simulate transfer of funds between a customer and business. -_Also important to note is that you should have registered validation and confirmation urls where the callback responses will be sent._ +```php +$mpesa = new \Safaricom\Mpesa\Mpesa(); +$b2bTransaction = $mpesa->c2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRefNumber ); +``` +_Also important to note is that you should have registered validation and confirmation urls where the callback responses will be sent._ -**STK Push Simulation** +### STK Push Simulation This is used to initiate online payment on behalf of a customer. -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +```php +$mpesa = new \Safaricom\Mpesa\Mpesa(); -`$stkPushSimulation=$mpesa->STKPushSimulation($BusinessShortCode, $LipaNaMpesaPasskey, $TransactionType, $Amount, $PartyA, $PartyB, $PhoneNumber, $CallBackURL, $AccountReference, $TransactionDesc, $Remarks);` +$stkPushSimulation = $mpesa->STKPushSimulation($BusinessShortCode, $LipaNaMpesaPasskey, $TransactionType, $Amount, $PartyA, $PartyB, $PhoneNumber, $CallBackURL, $AccountReference, $TransactionDesc, $Remarks); +``` +### STK Push Status Query + This is used to check the status of a Lipa Na M-Pesa Online Payment. -**STK Push Status Query** + ```php +$mpesa = new \Safaricom\Mpesa\Mpesa();` - This is used to check the status of a Lipa Na M-Pesa Online Payment. - -`$mpesa= new \Safaricom\Mpesa\Mpesa();` +$STKPushRequestStatus = $mpesa->STKPushQuery($checkoutRequestID,$businessShortCode,$password,$timestamp); +``` -`$STKPushRequestStatus=$mpesa->STKPushQuery($checkoutRequestID,$businessShortCode,$password,$timestamp);` +### Callback Routes +M-Pesa APIs are asynchronous. When a valid M-Pesa API request is received by the API Gateway, it is sent to M-Pesa where it is added to a queue. M-Pesa then processes the requests in the queue and sends a response to the API Gateway which then forwards the response to the URL registered in the CallBackURL or ResultURL request parameter. Whenever M-Pesa receives more requests than the queue can handle, M-Pesa responds by rejecting any more requests and the API Gateway sends a queue timeout response to the URL registered in the QueueTimeOutURL request parameter. +### Obtaining post data from callbacks +This is used to get post data from callback in json format. The data can be decoded and stored in a database. -**Callback Routes** -M-Pesa APIs are asynchronous. When a valid M-Pesa API request is received by the API Gateway, it is sent to M-Pesa where it is added to a queue. M-Pesa then processes the requests in the queue and sends a response to the API Gateway which then forwards the response to the URL registered in the CallBackURL or ResultURL request parameter. Whenever M-Pesa receives more requests than the queue can handle, M-Pesa responds by rejecting any more requests and the API Gateway sends a queue timeout response to the URL registered in the QueueTimeOutURL request parameter. + ```php + $mpesa= new \Safaricom\Mpesa\Mpesa(); -**Obtaining post data from callbacks** - This is used to get post data from callback in json format. The data can be decoded and stored in a database. - - `$mpesa= new \Safaricom\Mpesa\Mpesa();` - - `$callbackData=$mpesa->getDataFromCallback();` + $callbackData = $mpesa->getDataFromCallback(); + ``` - **Finishing a transaction** +### Finishing a transaction + After obtaining the Post data from the callbacks, use this at the end of your callback routes to complete the transaction - `$mpesa= new \Safaricom\Mpesa\Mpesa();` + ```php + $mpesa = new \Safaricom\Mpesa\Mpesa(); - `$callbackData=$mpesa->finishTransaction();` - + $callbackData = $mpesa->finishTransaction(); + ``` If validation fails, pass `false` to `finishTransaction()` - `$mpesa= new \Safaricom\Mpesa\Mpesa();` + ```php + $mpesa = new \Safaricom\Mpesa\Mpesa(); - `$callbackData=$mpesa->finishTransaction(false);` + $callbackData = $mpesa->finishTransaction(false); + ``` + +## Multitenancy support + +When handling transactions you do as follows + +```php +/** Get client preferably from a database somewhere. + * You can use a dynamic url or even an api key to identify which client the transaction belongs to. + * This can be done using a middleware implementation triggered before the transaction is processed. + * (Ignore this part if you don't want Multitenancy support as it will default to the values in the .env file) + */ +$clientA = [ + 'MPESA_CONSUMER_KEY' => 'MPESA_CONSUMER_KEY_HERE', + 'MPESA_CONSUMER_SECRET' => 'MPESA_CONSUMER_SECRET_HERE', + 'MPESA_ENV' => 'MPESA_ENV_HERE' +]; + +// change the configs depending on the client +config(['services.mpesa' => $clientA ]); +// Instanciate the mpesa class ONLY after changing the configs +$mpesa = new \Safaricom\Mpesa\Mpesa(); +// Do your business logic here +$b2bTransaction=$mpesa->b2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRefNumber ); +``` \ No newline at end of file diff --git a/src/Mpesa.php b/src/Mpesa.php index cb37a32..b3943c2 100644 --- a/src/Mpesa.php +++ b/src/Mpesa.php @@ -19,8 +19,8 @@ class Mpesa * @return mixed */ public static function generateLiveToken(){ - $consumer_key=env("MPESA_CONSUMER_KEY"); - $consumer_secret=env("MPESA_CONSUMER_SECRET"); + $consumer_key = self::getConfig("services.mpesa.MPESA_CONSUMER_KEY"); + $consumer_secret = self::getConfig("services.mpesa.MPESA_CONSUMER_SECRET"); if(!isset($consumer_key)||!isset($consumer_secret)){ die("please declare the consumer key and consumer secret as defined in the documentation"); @@ -48,8 +48,8 @@ public static function generateLiveToken(){ * @return mixed */ public static function generateSandBoxToken(){ - $consumer_key= env("MPESA_CONSUMER_KEY"); - $consumer_secret= env("MPESA_CONSUMER_SECRET"); + $consumer_key = self::getConfig("services.mpesa.MPESA_CONSUMER_KEY"); + $consumer_secret= self::getConfig("services.mpesa.MPESA_CONSUMER_SECRET"); if(!isset($consumer_key)||!isset($consumer_secret)){ die("please declare the consumer key and consumer secret as defined in the documentation"); } @@ -84,7 +84,7 @@ public static function generateSandBoxToken(){ * @return mixed|string */ public static function reversal($CommandID, $Initiator, $SecurityCredential, $TransactionID, $Amount, $ReceiverParty, $RecieverIdentifierType, $ResultURL, $QueueTimeOutURL, $Remarks, $Occasion){ - $environment=env("MPESA_ENV"); + $environment = self::getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ $url = 'https://api.safaricom.co.ke/mpesa/reversal/v1/request'; @@ -93,7 +93,7 @@ public static function reversal($CommandID, $Initiator, $SecurityCredential, $Tr $url = 'https://sandbox.safaricom.co.ke/mpesa/reversal/v1/request'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } @@ -142,16 +142,16 @@ public static function reversal($CommandID, $Initiator, $SecurityCredential, $Tr * @return string */ public static function b2c($InitiatorName, $SecurityCredential, $CommandID, $Amount, $PartyA, $PartyB, $Remarks, $QueueTimeOutURL, $ResultURL, $Occasion){ - $environment=env("MPESA_ENV"); + $environment = self::getConfig("services.mpesa.MPESA_ENV"); - if( $environment =="live"){ + if( $environment == "live"){ $url = 'https://api.safaricom.co.ke/mpesa/b2c/v1/paymentrequest'; $token=self::generateLiveToken(); }elseif ($environment=="sandbox"){ $url = 'https://sandbox.safaricom.co.ke/mpesa/b2c/v1/paymentrequest'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } @@ -194,7 +194,7 @@ public static function b2c($InitiatorName, $SecurityCredential, $CommandID, $Amo * @return mixed|string */ public static function c2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRefNumber ){ - $environment=env("MPESA_ENV"); + $environment = self::getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ $url = 'https://api.safaricom.co.ke/mpesa/c2b/v1/simulate'; @@ -203,7 +203,7 @@ public static function c2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRef $url = 'https://sandbox.safaricom.co.ke/mpesa/c2b/v1/simulate'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } @@ -245,7 +245,7 @@ public static function c2b($ShortCode, $CommandID, $Amount, $Msisdn, $BillRef * @return mixed|string */ public static function accountBalance($CommandID, $Initiator, $SecurityCredential, $PartyA, $IdentifierType, $Remarks, $QueueTimeOutURL, $ResultURL){ - $environment=env("MPESA_ENV"); + $environment = self::getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ @@ -255,7 +255,7 @@ public static function accountBalance($CommandID, $Initiator, $SecurityCredentia $url = 'https://sandbox.safaricom.co.ke/mpesa/accountbalance/v1/query'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } @@ -300,7 +300,7 @@ public static function accountBalance($CommandID, $Initiator, $SecurityCredentia * @return mixed|string */ public function transactionStatus($Initiator, $SecurityCredential, $CommandID, $TransactionID, $PartyA, $IdentifierType, $ResultURL, $QueueTimeOutURL, $Remarks, $Occasion){ - $environment=env("MPESA_ENV"); + $environment = $this->getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ $url = 'https://api.safaricom.co.ke/mpesa/transactionstatus/v1/query'; @@ -309,7 +309,7 @@ public function transactionStatus($Initiator, $SecurityCredential, $CommandID, $ $url = 'https://sandbox.safaricom.co.ke/mpesa/transactionstatus/v1/query'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } $curl = curl_init(); @@ -361,7 +361,7 @@ public function transactionStatus($Initiator, $SecurityCredential, $CommandID, $ * @return mixed|string */ public function b2b($Initiator, $SecurityCredential, $Amount, $PartyA, $PartyB, $Remarks, $QueueTimeOutURL, $ResultURL, $AccountReference, $commandID, $SenderIdentifierType, $RecieverIdentifierType){ - $environment=env("MPESA_ENV"); + $environment = $this->getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ $url = 'https://api.safaricom.co.ke/mpesa/b2b/v1/paymentrequest'; @@ -370,7 +370,7 @@ public function b2b($Initiator, $SecurityCredential, $Amount, $PartyA, $PartyB, $url = 'https://sandbox.safaricom.co.ke/mpesa/b2b/v1/paymentrequest'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); @@ -414,7 +414,7 @@ public function b2b($Initiator, $SecurityCredential, $Amount, $PartyA, $PartyB, * @return mixed|string */ public function STKPushSimulation($BusinessShortCode, $LipaNaMpesaPasskey, $TransactionType, $Amount, $PartyA, $PartyB, $PhoneNumber, $CallBackURL, $AccountReference, $TransactionDesc, $Remark){ - $environment=env("MPESA_ENV"); + $environment = $this->getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ $url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest'; $token=self::generateLiveToken(); @@ -422,7 +422,7 @@ public function STKPushSimulation($BusinessShortCode, $LipaNaMpesaPasskey, $Tran $url = 'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } @@ -472,7 +472,7 @@ public function STKPushSimulation($BusinessShortCode, $LipaNaMpesaPasskey, $Tran * @return mixed|string */ public static function STKPushQuery($environment, $checkoutRequestID, $businessShortCode, $password, $timestamp){ - $environment=env("MPESA_ENV"); + $environment = self::getConfig("services.mpesa.MPESA_ENV"); if( $environment =="live"){ $url = 'https://api.safaricom.co.ke/mpesa/stkpushquery/v1/query'; @@ -481,7 +481,7 @@ public static function STKPushQuery($environment, $checkoutRequestID, $businessS $url = 'https://sandbox.safaricom.co.ke/mpesa/stkpushquery/v1/query'; $token=self::generateSandBoxToken(); }else{ - return json_encode(["Message"=>"invalid application status"]); + return json_encode(array("Message"=>"invalid application status")); } @@ -511,19 +511,20 @@ public static function STKPushQuery($environment, $checkoutRequestID, $businessS /** *Use this function to confirm all transactions in callback routes + * @param bool $status */ public function finishTransaction($status = true) { if ($status === true) { - $resultArray=[ + $resultArray = array( "ResultDesc"=>"Confirmation Service request accepted successfully", "ResultCode"=>"0" - ]; + ); } else { - $resultArray=[ + $resultArray = array( "ResultDesc"=>"Confirmation Service not accepted", "ResultCode"=>"1" - ]; + ); } header('Content-Type: application/json'); @@ -540,5 +541,22 @@ public function getDataFromCallback(){ return $callbackJSONData; } + /** + * Use this function to get the config from either the config() or env() functions + * @param $config | Environment config + * @return mixed|string + */ + public static function getConfig($config) + { + if(function_exists('config')){ + // fall through to env if config not found + if($value = config($config)){ + return $value; + } + } + $var = explode('.',$config); + $key = (count($var) == 3)?$var[2]:$var[count($var)-1]; + return env($key); + } }