├── .gitignore
├── LICENSE
├── README.md
├── composer.json
├── composer.lock
├── config.example.php
├── phpdoc.xml
├── phpunit.xml
├── src
├── Config.php
├── Transaction.php
├── TransactionResponse.php
├── helpers
│ └── ValidationHelper.php
└── interfaces
│ ├── ConfigInterface.php
│ ├── TransactionInterface.php
│ └── TransactionResponseInterface.php
└── tests
├── MPesaTest.php
├── ValidationTest.php
└── config.test.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /docs/cache/
3 | /vendor/
4 | /tests/coverage/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Abdul Mueid Akhtar
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to M-Pesa PHP API
2 |
3 | This project aims to provide an easy-to-use and up-to-date PHP wrapper for the M-Pesa Mozambique API.
4 |
5 | Target version of M-Pesa API: **v1x**
6 |
7 | ## Installation
8 |
9 | Install using composer:
10 | ```
11 | composer require abdulmueid/mpesa
12 | ```
13 |
14 | ## Usage
15 |
16 | 1. Load the configuration from file.
17 | ```php
18 | $config = \abdulmueid\mpesa\Config::loadFromFile('/path/to/config.php');
19 | ```
20 | See sample configuration file in examples folder.
21 |
22 | 2. Create a Transaction using the configuration.
23 | ```php
24 | $transaction = new \abdulmueid\mpesa\Transaction($config);
25 | ```
26 |
27 | 3. Execute API operations and pass appropriate parameters.
28 |
29 | 1. Initiate a C2B payment collection.
30 | ```php
31 | $c2b = $transaction->c2b(
32 | float $amount,
33 | string $msisdn,
34 | string $reference,
35 | string $third_party_reference
36 | );
37 | ```
38 |
39 | 2. Initiate a B2C payment.
40 | ```php
41 | $b2c = $transaction->b2c(
42 | float $amount,
43 | string $msisdn,
44 | string $reference,
45 | string $third_party_reference
46 | );
47 | ```
48 |
49 | 3. Initiate a B2B payment.
50 | ```php
51 | $b2b = $transaction->b2b(
52 | float $amount,
53 | string $receiver_party_code,
54 | string $reference,
55 | string $third_party_reference
56 | );
57 | ```
58 |
59 | 2. Initiate a reversal.
60 | ```php
61 | $reversal = $transaction->reversal(
62 | float $amount,
63 | string $transaction_id,
64 | string $third_party_reference
65 | );
66 | ```
67 |
68 | 3. Query a transaction.
69 | ```php
70 | $query = $transaction->query(
71 | string $query_reference,
72 | string $third_party_reference
73 | );
74 | ```
75 | 4. Check Response
76 |
77 | All transactions return the `TransactionResponse` object. The object has the following public methods:
78 |
79 | 1. `getCode()` - Returns the response code i.e. `INS-0`
80 |
81 | 2. `getDescription()` - Returns the description.
82 |
83 | 3. `getTransactionID()` - Returns the transaction ID.
84 |
85 | 4. `getConversationID()` - Returns the conversation ID.
86 |
87 | 5. `getTransactionStatus()` - Returns the transaction status. Only populated when calling the `query()` transaction.
88 |
89 | 6. `getResponse()` - Returns the full response JSON object as received from M-Pesa servers. Good for debugging any issues or undocumented behaviors of the M-Pesa API.
90 |
91 | In a typical scenario, code to check for successful transactions should be as follows:
92 |
93 | ```php
94 | $c2b = $transaction->c2b(...);
95 |
96 | if($c2b->getCode() === 'INS-0') {
97 | // Transaction Successful, Do something here
98 | }
99 | ```
100 |
101 | ## Testing
102 | This repo provides Unit Tests to validate the objects and their interaction with M-Pesa.
103 |
104 | To run tests,
105 | 1. Open the `phpunit.xml` file and add the require credentials/parameters as supplied by M-Pesa.
106 | 2. Run `phpunit`
107 | 3. Check the handset for USSD prompts to approve test transactions.
108 |
109 | All tests use 1MT as the test amount.
110 |
111 |
112 | ## License
113 |
114 | This library is release under the MIT License. See LICENSE file for details.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "abdulmueid/mpesa",
3 | "description": "PHP SDK for M-Pesa Mozambique API",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Abdul Mueid Akhtar",
9 | "email": "abdul.mueid@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": "^7.2",
14 | "ext-curl": "*",
15 | "ext-json": "*",
16 | "ext-openssl": "*"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "abdulmueid\\mpesa\\": "src/"
21 | }
22 | },
23 | "require-dev": {
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "9e0f53be4f5aa060409c788af2039970",
8 | "packages": [],
9 | "packages-dev": [],
10 | "aliases": [],
11 | "minimum-stability": "stable",
12 | "stability-flags": [],
13 | "prefer-stable": false,
14 | "prefer-lowest": false,
15 | "platform": {
16 | "php": "^7.2",
17 | "ext-curl": "*",
18 | "ext-json": "*",
19 | "ext-openssl": "*"
20 | },
21 | "platform-dev": []
22 | }
23 |
--------------------------------------------------------------------------------
/config.example.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright (c) Abdul Mueid akhtar
5 | * @license http://mit-license.org/
6 | *
7 | * @link https://github.com/abdulmueid/mpesa-php-api
8 | */
9 |
10 | return [
11 | 'public_key' => '',
12 | 'api_host' => 'api.sandbox.vm.co.mz',
13 | 'api_key' => '',
14 | 'origin' => '',
15 | 'service_provider_code' => '',
16 | 'initiator_identifier' => '',
17 | 'security_credential' => ''
18 | ];
--------------------------------------------------------------------------------
/phpdoc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ./docs/cache
5 |
6 |
7 | ./docs
8 |
9 |
10 |
11 |
12 |
13 | ./src
14 |
15 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | ./tests/
39 |
40 |
41 |
42 |
43 | ./src/
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/Config.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Abdul Mueid akhtar
7 | * @license http://mit-license.org/
8 | *
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa;
13 |
14 | use abdulmueid\mpesa\interfaces\ConfigInterface;
15 |
16 | /**
17 | * Class Config
18 | * @package abdulmueid\mpesa
19 | */
20 | class Config implements ConfigInterface
21 | {
22 | /**
23 | * Public Key for the M-Pesa API. Used for generating Authorization bearer tokens.
24 | * @var string
25 | */
26 | private $public_key;
27 |
28 | /**
29 | * Hostname for the API - Use Sandbox or Production hostname
30 | * @var string
31 | */
32 | private $api_host;
33 | /**
34 | * API Key for the M-Pesa API. Used for creating authorize trasactions on the API
35 | * @var string
36 | */
37 | private $api_key;
38 |
39 | /**
40 | * Origin Header - Used for identifying hostname which is sending transaction requests
41 | * @var string
42 | */
43 | private $origin;
44 |
45 | /**
46 | * Service Provider Code - Provided by Vodacom
47 | * @var string
48 | */
49 | private $service_provider_code;
50 |
51 | /**
52 | * Initiator Identifier - Provided by Vodacom
53 | * @var string
54 | */
55 | private $initiator_identifier;
56 |
57 | /**
58 | * Security Credential - Provided by Vodacom
59 | * @var string
60 | */
61 | private $security_credential;
62 |
63 | /**
64 | * Config constructor.
65 | * @param string $public_key
66 | * @param string $api_host
67 | * @param string $api_key
68 | * @param string $origin
69 | * @param string $service_provider_code
70 | * @param string $initiator_identifier
71 | * @param string $security_credential
72 | */
73 | public function __construct(
74 | string $public_key,
75 | string $api_host,
76 | string $api_key,
77 | string $origin,
78 | string $service_provider_code,
79 | string $initiator_identifier,
80 | string $security_credential
81 | )
82 | {
83 | $this->public_key = $public_key;
84 | $this->api_host = $api_host;
85 | $this->api_key = $api_key;
86 | $this->origin = $origin;
87 | $this->service_provider_code = $service_provider_code;
88 | $this->initiator_identifier = $initiator_identifier;
89 | $this->security_credential = $security_credential;
90 | }
91 |
92 | /**
93 | * Loads the configuration from a file and returns a Config instance
94 | * @param string $file_path
95 | * @return Config
96 | */
97 | public static function loadFromFile(string $file_path)
98 | {
99 | $config = require $file_path;
100 | return new Config(
101 | $config['public_key'],
102 | $config['api_host'],
103 | $config['api_key'],
104 | $config['origin'],
105 | $config['service_provider_code'],
106 | $config['initiator_identifier'],
107 | $config['security_credential']
108 | );
109 | }
110 |
111 | /**
112 | * Encrypts the API key with public key and returns a usable Bearer Token
113 | *
114 | * @return string
115 | */
116 | public function getBearerToken(): string
117 | {
118 | $key = "-----BEGIN PUBLIC KEY-----\n";
119 | $key .= wordwrap($this->getPublicKey(), 60, "\n", true);
120 | $key .= "\n-----END PUBLIC KEY-----";
121 | $pk = openssl_get_publickey($key);
122 | openssl_public_encrypt($this->getApiKey(), $token, $pk, OPENSSL_PKCS1_PADDING);
123 | return 'Bearer ' . base64_encode($token);
124 | }
125 |
126 | /**
127 | * Returns M-Pesa Public Key
128 | * @return string
129 | */
130 | public function getPublicKey(): string
131 | {
132 | return $this->public_key;
133 | }
134 |
135 | /**
136 | * Returns API Hostname
137 | * @return string
138 | */
139 | public function getApiHost(): string
140 | {
141 | return $this->api_host;
142 | }
143 |
144 | /**
145 | * Returns API Key
146 | * @return string
147 | */
148 | public function getApiKey(): string
149 | {
150 | return $this->api_key;
151 | }
152 |
153 | /**
154 | * Returns Origin
155 | * @return string
156 | */
157 | public function getOrigin(): string
158 | {
159 | return $this->origin;
160 | }
161 |
162 | /**
163 | * Returns Service Provider Code
164 | * @return string
165 | */
166 | public function getServiceProviderCode(): string
167 | {
168 | return $this->service_provider_code;
169 | }
170 |
171 | /**
172 | * Returns Initiator Identifier
173 | * @return string
174 | */
175 | public function getInitiatorIdentifier(): string
176 | {
177 | return $this->initiator_identifier;
178 | }
179 |
180 | /**
181 | * Returns Security Credential
182 | * @return string
183 | */
184 | public function getSecurityCredential(): string
185 | {
186 | return $this->security_credential;
187 | }
188 | }
--------------------------------------------------------------------------------
/src/Transaction.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Abdul Mueid akhtar
7 | * @license http://mit-license.org/
8 | *
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa;
13 |
14 | use abdulmueid\mpesa\helpers\ValidationHelper;
15 | use abdulmueid\mpesa\interfaces\ConfigInterface;
16 | use abdulmueid\mpesa\interfaces\TransactionInterface;
17 | use abdulmueid\mpesa\interfaces\TransactionResponseInterface;
18 | use Exception;
19 |
20 | /**
21 | * Class Transaction
22 | * @package abdulmueid\mpesa
23 | */
24 | class Transaction implements TransactionInterface
25 | {
26 | /**
27 | * The configuration class
28 | * @var ConfigInterface
29 | */
30 | private $config;
31 |
32 | /**
33 | * Transaction constructor.
34 | * @param ConfigInterface $config
35 | */
36 | public function __construct(ConfigInterface $config)
37 | {
38 | $this->config = $config;
39 | }
40 |
41 | /**
42 | * Initiates a C2B transaction on the M-Pesa API.
43 | * @param float $amount
44 | * @param string $msisdn
45 | * @param string $reference
46 | * @param string $third_party_reference
47 | * @return TransactionResponseInterface
48 | * @throws Exception
49 | */
50 | public function c2b(
51 | float $amount,
52 | string $msisdn,
53 | string $reference,
54 | string $third_party_reference
55 | ): TransactionResponseInterface
56 | {
57 | $msisdn = ValidationHelper::normalizeMSISDN($msisdn);
58 | $amount = round($amount, 2);
59 | $payload = [
60 | 'input_ServiceProviderCode' => $this->config->getServiceProviderCode(),
61 | 'input_CustomerMSISDN' => $msisdn,
62 | 'input_Amount' => $amount,
63 | 'input_TransactionReference' => $reference,
64 | 'input_ThirdPartyReference' => $third_party_reference
65 | ];
66 | $payload = json_encode($payload);
67 | $request_handle = curl_init(
68 | 'https://' . $this->config->getApiHost() . ':18352/ipg/v1x/c2bPayment/singleStage/'
69 | );
70 | curl_setopt($request_handle, CURLOPT_RETURNTRANSFER, true);
71 | curl_setopt($request_handle, CURLOPT_HTTPHEADER, [
72 | 'Content-Type: application/json',
73 | 'Content-Length: ' . strlen($payload),
74 | 'Origin: ' . $this->config->getOrigin(),
75 | 'Authorization: ' . $this->config->getBearerToken()
76 | ]);
77 | curl_setopt($request_handle, CURLOPT_CUSTOMREQUEST, 'POST');
78 | curl_setopt($request_handle, CURLOPT_POSTFIELDS, $payload);
79 | $result = curl_exec($request_handle);
80 | return new TransactionResponse($result);
81 | }
82 |
83 | /**
84 | * Initiates a B2C transaction on the M-Pesa API.
85 | * @param float $amount
86 | * @param string $msisdn
87 | * @param string $reference
88 | * @param string $third_party_reference
89 | * @return TransactionResponseInterface
90 | * @throws Exception
91 | */
92 | public function b2c(
93 | float $amount,
94 | string $msisdn,
95 | string $reference,
96 | string $third_party_reference
97 | ): TransactionResponseInterface
98 | {
99 | $msisdn = ValidationHelper::normalizeMSISDN($msisdn);
100 | $amount = round($amount, 2);
101 | $payload = [
102 | 'input_ServiceProviderCode' => $this->config->getServiceProviderCode(),
103 | 'input_CustomerMSISDN' => $msisdn,
104 | 'input_Amount' => $amount,
105 | 'input_TransactionReference' => $reference,
106 | 'input_ThirdPartyReference' => $third_party_reference
107 | ];
108 | $payload = json_encode($payload);
109 | $request_handle = curl_init(
110 | 'https://' . $this->config->getApiHost() . ':18345/ipg/v1x/b2cPayment/'
111 | );
112 | curl_setopt($request_handle, CURLOPT_RETURNTRANSFER, true);
113 | curl_setopt($request_handle, CURLOPT_HTTPHEADER, [
114 | 'Content-Type: application/json',
115 | 'Content-Length: ' . strlen($payload),
116 | 'Origin: ' . $this->config->getOrigin(),
117 | 'Authorization: ' . $this->config->getBearerToken()
118 | ]);
119 | curl_setopt($request_handle, CURLOPT_CUSTOMREQUEST, 'POST');
120 | curl_setopt($request_handle, CURLOPT_POSTFIELDS, $payload);
121 | $result = curl_exec($request_handle);
122 | return new TransactionResponse($result);
123 | }
124 |
125 | /**
126 | * Initiates a B2B transaction on the M-Pesa API.
127 | * @param float $amount
128 | * @param string $receiver_party_code
129 | * @param string $reference
130 | * @param string $third_party_reference
131 | * @return TransactionResponseInterface
132 | */
133 | public function b2b(
134 | float $amount,
135 | string $receiver_party_code,
136 | string $reference,
137 | string $third_party_reference
138 | ): TransactionResponseInterface
139 | {
140 | $amount = round($amount, 2);
141 | $payload = [
142 | 'input_Amount' => $amount,
143 | 'input_TransactionReference' => $reference,
144 | 'input_ThirdPartyReference' => $third_party_reference,
145 | 'input_PrimaryPartyCode' => $this->config->getServiceProviderCode(),
146 | 'input_ReceiverPartyCode' => $receiver_party_code,
147 | ];
148 | $payload = json_encode($payload);
149 | $request_handle = curl_init(
150 | 'https://' . $this->config->getApiHost() . ':18349/ipg/v1x/b2bPayment/'
151 | );
152 | curl_setopt($request_handle, CURLOPT_RETURNTRANSFER, true);
153 | curl_setopt($request_handle, CURLOPT_HTTPHEADER, [
154 | 'Content-Type: application/json',
155 | 'Content-Length: ' . strlen($payload),
156 | 'Origin: ' . $this->config->getOrigin(),
157 | 'Authorization: ' . $this->config->getBearerToken()
158 | ]);
159 | curl_setopt($request_handle, CURLOPT_CUSTOMREQUEST, 'POST');
160 | curl_setopt($request_handle, CURLOPT_POSTFIELDS, $payload);
161 | $result = curl_exec($request_handle);
162 | return new TransactionResponse($result);
163 | }
164 |
165 | /**
166 | * Initiates a Reversal transaction on the M-Pesa API.
167 | * @param float $amount
168 | * @param string $transaction_id
169 | * @param string $third_party_reference
170 | * @return TransactionResponseInterface
171 | */
172 | public function reversal(
173 | float $amount,
174 | string $transaction_id,
175 | string $third_party_reference
176 | ): TransactionResponseInterface
177 | {
178 | $amount = round($amount, 2);
179 | $payload = [
180 | 'input_Amount' => $amount,
181 | 'input_TransactionID' => $transaction_id,
182 | 'input_ThirdPartyReference' => $third_party_reference,
183 | 'input_ServiceProviderCode' => $this->config->getServiceProviderCode(),
184 | 'input_InitiatorIdentifier' => $this->config->getInitiatorIdentifier(),
185 | 'input_SecurityCredential' => $this->config->getSecurityCredential(),
186 |
187 | ];
188 |
189 | $payload = json_encode($payload);
190 | $request_handle = curl_init('https://' . $this->config->getApiHost() . ':18354/ipg/v1x/reversal/');
191 | curl_setopt($request_handle, CURLOPT_RETURNTRANSFER, true);
192 | curl_setopt($request_handle, CURLOPT_HTTPHEADER, [
193 | 'Content-Type: application/json',
194 | 'Content-Length: ' . strlen($payload),
195 | 'Origin: ' . $this->config->getOrigin(),
196 | 'Authorization: ' . $this->config->getBearerToken()
197 | ]);
198 | curl_setopt($request_handle, CURLOPT_CUSTOMREQUEST, 'PUT');
199 | curl_setopt($request_handle, CURLOPT_POSTFIELDS, $payload);
200 | $result = curl_exec($request_handle);
201 | return new TransactionResponse($result);
202 | }
203 |
204 | /**
205 | * Initiates a transaction Query on the M-Pesa API.
206 | * @param string $query_reference
207 | * @param string $third_party_reference
208 | * @return TransactionResponseInterface
209 | */
210 | public function query(
211 | string $query_reference,
212 | string $third_party_reference
213 | ): TransactionResponseInterface
214 | {
215 | $payload = [
216 | 'input_QueryReference' => $query_reference,
217 | 'input_ServiceProviderCode' => $this->config->getServiceProviderCode(),
218 | 'input_ThirdPartyReference' => $third_party_reference
219 | ];
220 | $payload = http_build_query($payload);
221 | $request_handle = curl_init(
222 | 'https://' . $this->config->getApiHost() . ':18353/ipg/v1x/queryTransactionStatus/?' . $payload
223 | );
224 | curl_setopt($request_handle, CURLOPT_RETURNTRANSFER, true);
225 | curl_setopt($request_handle, CURLOPT_HTTPHEADER, [
226 | 'Content-Type: application/json',
227 | 'Origin: ' . $this->config->getOrigin(),
228 | 'Authorization: ' . $this->config->getBearerToken()
229 | ]);
230 | $result = curl_exec($request_handle);
231 | return new TransactionResponse($result);
232 | }
233 | }
--------------------------------------------------------------------------------
/src/TransactionResponse.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Abdul Mueid akhtar
7 | * @license http://mit-license.org/
8 | *
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa;
13 |
14 | use abdulmueid\mpesa\interfaces\TransactionResponseInterface;
15 |
16 | /**
17 | * Class TransactionResponse
18 | * @package abdulmueid\mpesa
19 | */
20 | class TransactionResponse implements TransactionResponseInterface
21 | {
22 | /**
23 | * Full response from the M-Pesa API
24 | * @var string
25 | */
26 | private $response;
27 |
28 | /**
29 | * Response Code from M-Pesa API
30 | * @var string
31 | */
32 | private $code;
33 |
34 | /**
35 | * Response Description from M-Pesa API
36 | * @var string
37 | */
38 | private $description;
39 |
40 | /**
41 | * Transaction ID from M-Pesa Payment and Refund API
42 | * @var string
43 | */
44 | private $transaction_id;
45 |
46 | /**
47 | * Conversation ID from M-Pesa Payment and Refund API
48 | * @var string
49 | */
50 | private $conversation_id;
51 |
52 | /**
53 | * Transaction Status from M-Pesa Query API
54 | * @var string
55 | */
56 | private $transaction_status;
57 |
58 | /**
59 | * TransactionResponse constructor.
60 | * @param string $response
61 | */
62 | public function __construct(string $response)
63 | {
64 | $this->response = $response;
65 | $params = json_decode($this->response, true);
66 | $this->code = $params['output_ResponseCode'] ?? '';
67 | $this->description = $params['output_ResponseDesc'] ?? '';
68 | $this->transaction_id = $params['output_TransactionID'] ?? '';
69 | $this->conversation_id = $params['output_ConversationID'] ?? '';
70 | $this->transaction_status = $params['output_ResponseTransactionStatus'] ?? '';
71 | }
72 |
73 | /**
74 | * Returns the Response Code
75 | * @return string
76 | */
77 | public function getCode(): string
78 | {
79 | return $this->code;
80 | }
81 |
82 | /**
83 | * Returns the response description
84 | * @return string
85 | */
86 | public function getDescription(): string
87 | {
88 | return $this->description;
89 | }
90 |
91 | /**
92 | * Returns the TransactionID
93 | * @return string
94 | */
95 | public function getTransactionID(): string
96 | {
97 | return $this->transaction_id;
98 | }
99 |
100 | /**
101 | * Returns the Conversation ID
102 | * @return string
103 | */
104 | public function getConversationID(): string
105 | {
106 | return $this->conversation_id;
107 | }
108 |
109 | /**
110 | * Returns the Transaction Status from Query API
111 | * @return string
112 | */
113 | public function getTransactionStatus(): string
114 | {
115 | return $this->transaction_status;
116 | }
117 |
118 | /**
119 | * Returns the raw response from M-Pesa API
120 | * @return string
121 | */
122 | public function getResponse(): string
123 | {
124 | return $this->response;
125 | }
126 | }
--------------------------------------------------------------------------------
/src/helpers/ValidationHelper.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Kishan Nareshpal Jadav
7 | *
8 | * @license http://mit-license.org/
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa\helpers;
13 |
14 | use Exception;
15 |
16 | /**
17 | * Class ValidationHelper
18 | * @package abdulmueid\mpesa\helpers
19 | */
20 | class ValidationHelper
21 | {
22 | /**
23 | * Validates and normalizes the MSISDN to 25884|5xxxxxxx as accepted by the M-Pesa API.
24 | * – Restricts MSISDN to only Vodacom Prefixes (84, 85).
25 | * – Accepts MSISDN in the following formats:
26 | * * (84|85)xxxxxxx
27 | * * 258(84|85)xxxxxxx
28 | * * +258(84|85)xxxxxxx
29 | * * 00258(84|85)xxxxxxx
30 | *
31 | * @param string $msisdn msisdn which will be validated and normalized afterwards.
32 | * @return string normalized phone number: 258(84|85)xxxxxxx
33 | * @throws Exception
34 | */
35 | public static function normalizeMSISDN($msisdn)
36 | {
37 | // $matchGroup array contains 5 pairs:
38 | // – [0] -> the full match.
39 | // – [1] -> starts with + or 00.
40 | // – [2] -> the country code 258.
41 | // – [3] -> the phone number without the country code. Includes the local prefix.
42 | // – [4] -> the local prefix, either 84 or 85.
43 | $matchGroup = array();
44 | $isValid = preg_match('/^(\+|00)?(258)?((84|85)\d{7})$/', $msisdn, $matchGroup);
45 |
46 | if ($isValid) {
47 | // $match = $matchGroup[0];
48 | // $containsPlusOrZeroZero = $matchGroup[1] != null;
49 | // $containsCountryCode = $matchGroup[2] != null;
50 | // $containsLocalPrefix = $matchGroup[4] != null;
51 | $matchedPhoneNumber = $matchGroup[3];
52 | $normalizedPhoneNumber = "258" . $matchedPhoneNumber;
53 | return $normalizedPhoneNumber;
54 | } else {
55 | throw new Exception("The provided number " . $msisdn . " is not valid Vodacom MSISDN.");
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/interfaces/ConfigInterface.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Abdul Mueid akhtar
7 | * @license http://mit-license.org/
8 | *
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa\interfaces;
13 |
14 | /**
15 | * Interface ConfigInterface
16 | * @package abdulmueid\mpesa\interfaces
17 | */
18 | interface ConfigInterface
19 | {
20 |
21 | /**
22 | * Returns the Public Key
23 | *
24 | * @return string
25 | */
26 | public function getPublicKey(): string;
27 |
28 | /**
29 | * Returns the API Key
30 | *
31 | * @return string
32 | */
33 | public function getApiKey(): string;
34 |
35 | /**
36 | * Returns the Origin Header
37 | *
38 | * @return string
39 | */
40 | public function getOrigin(): string;
41 |
42 | /**
43 | * Returns the Service Provider Code
44 | *
45 | * @return string
46 | */
47 | public function getServiceProviderCode(): string;
48 |
49 | /**
50 | * Returns the Initiator Identifier
51 | *
52 | * @return string
53 | */
54 | public function getInitiatorIdentifier(): string;
55 |
56 | /**
57 | * Returns the Security Credential
58 | *
59 | * @return string
60 | */
61 | public function getSecurityCredential(): string;
62 |
63 | /**
64 | * Encrypts the API key with public key and returns a usable Bearer Token
65 | *
66 | * @return string
67 | */
68 | public function getBearerToken(): string;
69 |
70 | /**
71 | * Returns API Hostname
72 | *
73 | * @return string
74 | */
75 | public function getApiHost(): string;
76 | }
--------------------------------------------------------------------------------
/src/interfaces/TransactionInterface.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Abdul Mueid akhtar
7 | * @license http://mit-license.org/
8 | *
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa\interfaces;
13 |
14 | /**
15 | * Interface TransactionInterface
16 | * @package abdulmueid\mpesa\interfaces
17 | */
18 | interface TransactionInterface
19 | {
20 | /**
21 | * Initiates a C2B transaction on the M-Pesa API.
22 | * @param float $amount
23 | * @param string $msisdn
24 | * @param string $reference
25 | * @param string $third_party_reference
26 | * @return TransactionResponseInterface
27 | */
28 | public function c2b(float $amount, string $msisdn, string $reference, string $third_party_reference): TransactionResponseInterface;
29 |
30 | /**
31 | * Initiates a B2C transaction on the M-Pesa API.
32 | * @param float $amount
33 | * @param string $msisdn
34 | * @param string $reference
35 | * @param string $third_party_reference
36 | * @return TransactionResponseInterface
37 | */
38 | public function b2c(float $amount, string $msisdn, string $reference, string $third_party_reference): TransactionResponseInterface;
39 |
40 | /**
41 | * Initiates a B2B transaction on the M-Pesa API.
42 | * @param float $amount
43 | * @param string $receiver_party_code
44 | * @param string $reference
45 | * @param string $third_party_reference
46 | * @return TransactionResponseInterface
47 | */
48 | public function b2b(float $amount, string $receiver_party_code, string $reference, string $third_party_reference): TransactionResponseInterface;
49 |
50 | /**
51 | * Initiates a transaction Reversal on the M-Pesa API.
52 | * @param float $amount
53 | * @param string $transaction_id
54 | * @param string $third_party_reference
55 | * @return TransactionResponseInterface
56 | */
57 | public function reversal(float $amount, string $transaction_id, string $third_party_reference): TransactionResponseInterface;
58 |
59 | /**
60 | * Initiates a transaction Query on the M-Pesa API.
61 | * @param string $query_reference
62 | * @param string $third_party_reference
63 | * @return TransactionResponseInterface
64 | */
65 | public function query(string $query_reference, string $third_party_reference): TransactionResponseInterface;
66 | }
--------------------------------------------------------------------------------
/src/interfaces/TransactionResponseInterface.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) Abdul Mueid akhtar
7 | * @license http://mit-license.org/
8 | *
9 | * @link https://github.com/abdulmueid/mpesa-php-api
10 | */
11 |
12 | namespace abdulmueid\mpesa\interfaces;
13 |
14 | /**
15 | * Interface TransactionResponseInterface
16 | * @package abdulmueid\mpesa\interfaces
17 | */
18 | interface TransactionResponseInterface
19 | {
20 | /**
21 | * Returns the Response Code
22 | *
23 | * @return string
24 | */
25 | public function getCode(): string;
26 |
27 | /**
28 | * Returns the response description
29 | *
30 | * @return string
31 | */
32 | public function getDescription(): string;
33 |
34 | /**
35 | * Returns the TransactionID
36 | *
37 | * @return string
38 | */
39 | public function getTransactionID(): string;
40 |
41 | /**
42 | * Returns the Conversation ID
43 | *
44 | * @return string
45 | */
46 | public function getConversationID(): string;
47 |
48 | /**
49 | * Returns the Transaction Status from Query API
50 | *
51 | * @return string
52 | */
53 | public function getTransactionStatus(): string;
54 |
55 | /**
56 | * Returns the raw response from M-Pesa API
57 | *
58 | * @return string
59 | */
60 | public function getResponse(): string;
61 | }
--------------------------------------------------------------------------------
/tests/MPesaTest.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright (c) Abdul Mueid akhtar
5 | * @license http://mit-license.org/
6 | *
7 | * @link https://github.com/abdulmueid/mpesa-php-api
8 | */
9 |
10 | use abdulmueid\mpesa\Config;
11 | use abdulmueid\mpesa\interfaces\TransactionResponseInterface;
12 |
13 | class MPesaTest extends \PHPUnit\Framework\TestCase
14 | {
15 | /**
16 | * @var \abdulmueid\mpesa\interfaces\TransactionInterface
17 | */
18 | private $transaction;
19 |
20 | /**
21 | * @var float
22 | */
23 | private $amount;
24 |
25 | /**
26 | * @var string
27 | */
28 | private $msisdn;
29 |
30 | /**
31 | * @var string
32 | */
33 | private $b2b_receiver;
34 |
35 | /**
36 | * @return TransactionResponseInterface
37 | * @throws Exception
38 | */
39 | public function testB2C(): TransactionResponseInterface
40 | {
41 |
42 | $payment = $this->transaction->b2c(
43 | $this->amount,
44 | $this->msisdn,
45 | bin2hex(random_bytes(6)),
46 | bin2hex(random_bytes(6))
47 | );
48 | $this->assertInstanceOf(
49 | \abdulmueid\mpesa\TransactionResponse::class,
50 | $payment
51 | );
52 | $this->assertNotEmpty($payment->getResponse());
53 | $this->assertNotEmpty($payment->getCode());
54 | $this->assertNotEmpty($payment->getDescription());
55 | $this->assertNotEmpty($payment->getTransactionID());
56 | $this->assertNotEmpty($payment->getConversationID());
57 | $this->assertEmpty($payment->getTransactionStatus());
58 | $this->assertStringStartsWith('INS-', $payment->getCode());
59 | return $payment;
60 | }
61 |
62 | /**
63 | * @return TransactionResponseInterface
64 | * @throws Exception
65 | */
66 | public function testB2B(): TransactionResponseInterface
67 | {
68 |
69 | $payment = $this->transaction->b2b(
70 | $this->amount,
71 | $this->b2b_receiver,
72 | bin2hex(random_bytes(6)),
73 | bin2hex(random_bytes(6))
74 | );
75 | $this->assertInstanceOf(
76 | \abdulmueid\mpesa\TransactionResponse::class,
77 | $payment
78 | );
79 | $this->assertNotEmpty($payment->getResponse());
80 | $this->assertNotEmpty($payment->getCode());
81 | $this->assertNotEmpty($payment->getDescription());
82 | $this->assertNotEmpty($payment->getTransactionID());
83 | $this->assertNotEmpty($payment->getConversationID());
84 | $this->assertEmpty($payment->getTransactionStatus());
85 | $this->assertStringStartsWith('INS-', $payment->getCode());
86 | return $payment;
87 | }
88 |
89 | /**
90 | * @return TransactionResponseInterface
91 | * @throws Exception
92 | */
93 | public function testC2B(): TransactionResponseInterface
94 | {
95 |
96 | $payment = $this->transaction->c2b(
97 | $this->amount,
98 | $this->msisdn,
99 | bin2hex(random_bytes(6)),
100 | bin2hex(random_bytes(6))
101 | );
102 | $this->assertInstanceOf(
103 | \abdulmueid\mpesa\TransactionResponse::class,
104 | $payment
105 | );
106 | $this->assertNotEmpty($payment->getResponse());
107 | $this->assertNotEmpty($payment->getCode());
108 | $this->assertNotEmpty($payment->getDescription());
109 | $this->assertNotEmpty($payment->getTransactionID());
110 | $this->assertNotEmpty($payment->getConversationID());
111 | $this->assertEmpty($payment->getTransactionStatus());
112 | $this->assertStringStartsWith('INS-', $payment->getCode());
113 | return $payment;
114 | }
115 |
116 | /**
117 | * @depends testC2B
118 | * @param $payment TransactionResponseInterface
119 | * @return TransactionResponseInterface
120 | * @throws Exception
121 | */
122 | public function testReversal(TransactionResponseInterface $payment): TransactionResponseInterface
123 | {
124 | $refund = $this->transaction->reversal(
125 | $this->amount,
126 | $payment->getTransactionID(),
127 | bin2hex(random_bytes(6))
128 | );
129 | $this->assertInstanceOf(
130 | \abdulmueid\mpesa\TransactionResponse::class,
131 | $refund
132 | );
133 | $this->assertNotEmpty($refund->getResponse());
134 | $this->assertNotEmpty($refund->getCode());
135 | $this->assertNotEmpty($refund->getDescription());
136 | $this->assertNotEmpty($refund->getTransactionID());
137 | $this->assertNotEmpty($refund->getConversationID());
138 | $this->assertEmpty($refund->getTransactionStatus());
139 | $this->assertStringStartsWith('INS-', $refund->getCode());
140 | return $refund;
141 | }
142 |
143 | /**
144 | * @depends testReversal
145 | * @param $refund TransactionResponseInterface
146 | * @throws Exception
147 | */
148 | public function testQuery(TransactionResponseInterface $refund)
149 | {
150 | $query = $this->transaction->query(
151 | $refund->getTransactionID(),
152 | bin2hex(random_bytes(6))
153 | );
154 | $this->assertInstanceOf(
155 | \abdulmueid\mpesa\TransactionResponse::class,
156 | $query
157 | );
158 | $this->assertNotEmpty($query->getResponse());
159 | $this->assertNotEmpty($query->getCode());
160 | $this->assertNotEmpty($query->getDescription());
161 | $this->assertEmpty($query->getTransactionID());
162 | $this->assertNotEmpty($query->getConversationID());
163 | $this->assertNotEmpty($query->getTransactionStatus());
164 | $this->assertStringStartsWith('INS-', $query->getCode());
165 | }
166 |
167 | protected function setUp(): void
168 | {
169 | $config = Config::loadFromFile(__DIR__ . '/config.test.php');
170 | $this->transaction = new \abdulmueid\mpesa\Transaction($config);
171 | $this->amount = 1;
172 | $this->msisdn = getenv('MPESA_CUSTOMER_MSISDN'); // Full MSISDN i.e. 258840000000
173 | $this->b2b_receiver = getenv('MPESA_B2B_RECEIVER');
174 | }
175 | }
--------------------------------------------------------------------------------
/tests/ValidationTest.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright (c) Abdul Mueid akhtar
5 | * @license http://mit-license.org/
6 | *
7 | * @link https://github.com/abdulmueid/mpesa-php-api
8 | */
9 |
10 | use abdulmueid\mpesa\helpers\ValidationHelper;
11 | use PHPUnit\Framework\TestCase;
12 |
13 | class ValidationTest extends TestCase
14 | {
15 | private $validMSISDNs;
16 | private $invalidStrings;
17 |
18 | /**
19 | * @return bool
20 | * @throws Exception
21 | */
22 | public function testValidMSISDNs()
23 | {
24 | foreach ($this->validMSISDNs as $msisdn) {
25 | $this->assertIsString(ValidationHelper::normalizeMSISDN($msisdn));
26 | }
27 | return true;
28 | }
29 |
30 | /**
31 | * @return bool
32 | */
33 | public function testInvalidStrings()
34 | {
35 | foreach ($this->invalidStrings as $string) {
36 | $invalidString = true;
37 | try {
38 | ValidationHelper::normalizeMSISDN($string); // Should throw exception
39 | $invalidString = false;
40 | } catch (Exception $e) {
41 | }
42 | $this->assertTrue($invalidString, $string . " is not supposed to be valid");
43 | }
44 | return true;
45 | }
46 |
47 | protected function setUp(): void
48 | {
49 | // Set up valid MSISDNs
50 | $this->validMSISDNs = [];
51 | $this->validMSISDNs[] = "841231234";
52 | $this->validMSISDNs[] = "258841231234";
53 | $this->validMSISDNs[] = "+258841231234";
54 | $this->validMSISDNs[] = "00258841231234";
55 | $this->validMSISDNs[] = "851231234";
56 | $this->validMSISDNs[] = "258851231234";
57 | $this->validMSISDNs[] = "+258851231234";
58 | $this->validMSISDNs[] = "00258851231234";
59 |
60 | // Set up invalid strings
61 | $this->invalidStrings = [];
62 | // Short Length
63 | $this->invalidStrings[] = "84123123";
64 | $this->invalidStrings[] = "25884123123";
65 | $this->invalidStrings[] = "+25884123123";
66 | $this->invalidStrings[] = "0025884123123";
67 | // Long Length
68 | $this->invalidStrings[] = "8412312345";
69 | $this->invalidStrings[] = "2588412312345";
70 | $this->invalidStrings[] = "+2588412312345";
71 | $this->invalidStrings[] = "002588412312345";
72 | // Mcel prefixes - 82, 83
73 | $this->invalidStrings[] = "821231234";
74 | $this->invalidStrings[] = "258821231234";
75 | $this->invalidStrings[] = "+258821231234";
76 | $this->invalidStrings[] = "00258821231234";
77 | $this->invalidStrings[] = "831231234";
78 | $this->invalidStrings[] = "258831231234";
79 | $this->invalidStrings[] = "+258831231234";
80 | $this->invalidStrings[] = "00258831231234";
81 | // Movitel prefixes - 86, 87
82 | $this->invalidStrings[] = "861231234";
83 | $this->invalidStrings[] = "258861231234";
84 | $this->invalidStrings[] = "+258861231234";
85 | $this->invalidStrings[] = "00258861231234";
86 | $this->invalidStrings[] = "871231234";
87 | $this->invalidStrings[] = "258871231234";
88 | $this->invalidStrings[] = "+258871231234";
89 | $this->invalidStrings[] = "00258871231234";
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tests/config.test.php:
--------------------------------------------------------------------------------
1 |
4 | * @copyright Copyright (c) Abdul Mueid akhtar
5 | * @license http://mit-license.org/
6 | *
7 | * @link https://github.com/abdulmueid/mpesa-php-api
8 | */
9 |
10 | return [
11 | 'public_key' => getenv('MPESA_PUBLIC_KEY'),
12 | 'api_host' => getenv('MPESA_API_HOST'),
13 | 'api_key' => getenv('MPESA_API_KEY'),
14 | 'origin' => getenv('MPESA_ORIGIN'),
15 | 'service_provider_code' => getenv('MPESA_SERVICE_PROVIDER_CODE'),
16 | 'initiator_identifier' => getenv('MPESA_INITIATOR_IDENTIFIER'),
17 | 'security_credential' => getenv('MPESA_SECURITY_CREDENTIAL')
18 | ];
--------------------------------------------------------------------------------