├── .babelrc ├── .env.example ├── .eslintrc.json ├── .github └── workflows │ └── build_on_pr.yml ├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── dist ├── client.cjs ├── configuration.cjs ├── constants.cjs ├── environment.cjs ├── errors.cjs ├── index.cjs ├── operation.cjs ├── response.cjs ├── service.cjs └── version.cjs ├── docs └── .keep ├── examples ├── .keep ├── b2bPayment.js ├── b2cPayment.js ├── c2bPayment.js ├── queryTransactionStatus.js └── reversal.js ├── package.json ├── src ├── client.js ├── configuration.js ├── constants.js ├── environment.js ├── errors.js ├── index.js ├── operation.js ├── response.js ├── service.js └── version.js └── test ├── .keep ├── client.js ├── configuration.js ├── environment.js ├── operation.js └── service.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | API_KEY=null 2 | PUBLIC_KEY=null 3 | SERVICE_PROVIDER_CODE=null 4 | PHONE_NUMBER=null 5 | SECURITY_CREDENTIAL=null 6 | INITIATOR_IDENTIFIER=null -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": ["error"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/build_on_pr.yml: -------------------------------------------------------------------------------- 1 | name: Build package on PR opening 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - closed 9 | branches: 10 | - develop 11 | - master 12 | - main 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v2 21 | 22 | - name: Use Node.js 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: "20" 26 | 27 | - name: Install dependencies 28 | run: npm install 29 | 30 | - name: Run pre-commit script 31 | run: npm run pre-commit 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /package-lock.json 3 | .env 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": false, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "skipFiles": ["/**"], 12 | "cwd": "${workspaceFolder}", 13 | "runtimeExecutable": "npm", 14 | "runtimeArgs": ["run-script", "test"] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript M-Pesa SDK 2 | 3 | 4 |

5 | Total Downloads 6 | Latest Stable Version 7 | License 8 |

9 | 10 | This is a library willing to help you to integrate the [Vodacom M-Pesa](https://developer.mpesa.vm.co.mz) operations to your application. 11 | 12 |
13 | 14 | ### Features 15 | 16 | Using this library, you can implement the following operations: 17 | 18 | - Receive money from a mobile account to a business account (C2B) 19 | - Send money from a business account to a mobile account (B2C) 20 | - Send money from a business account to another business account (B2B) 21 | - Revert any of the transactions above mentioned 22 | - Query the status of a transaction 23 | 24 |

25 | 26 | ## Requirements 27 | 28 | - [NodeJS v12.0+](https://nodejs.org) 29 | - [NPM](https://npmjs.com) or [Yarn](https://yarnpkg.com) 30 | - Valid credentials obtained from the [Mpesa Developer](https://developer.mpesa.vm.co.mz) portal 31 | 32 | 33 |

34 | 35 | 36 | ## Installation 37 | 38 |
39 | 40 | ### Using NPM 41 | 42 | ```bash 43 | npm install --save @paymentsds/mpesa 44 | ``` 45 |
46 | 47 | ### Using Yarn 48 | ```bash 49 | yarn add @paymentsds/mpesa 50 | ``` 51 | 52 |

53 | 54 | ## Usage 55 | 56 | Using this SDK is very simple and fast, let us see some examples: 57 | 58 |
59 | 60 | #### Client configuration 61 | ```javascript 62 | import { Client } from '@paymentsds/mpesa' 63 | 64 | const client = new Client({ 65 | apiKey: '', // API Key 66 | publicKey: '', // Public Key 67 | serviceProviderCode: '', // Service Provider Code, 68 | initiatorIdentifier: '', // Initiator Identifier, 69 | securityIdentifier: '', // Security Credential 70 | timeout: '', // time in seconds 71 | debugging: true, 72 | verifySSL: false, 73 | userAgent: '' 74 | }); 75 | ``` 76 | 77 |
78 | 79 | #### C2B Transaction (Receive money from mobile account) 80 | 81 |
82 | 83 | ##### On ES6 Modules 84 | 85 | ```javascript 86 | import { Client } from '@paymentsds/mpesa' 87 | 88 | const client = new Client({ 89 | apiKey: '', // API Key 90 | publicKey: '', // Public Key 91 | serviceProviderCode: '' // Service Provider Code 92 | }); 93 | 94 | const paymentData = { 95 | from: '841234567', // Customer MSISDN 96 | reference: '11114', // Third Party Reference 97 | transaction: 'T12344CC', // Transaction Reference 98 | amount: '10' // Amount 99 | }; 100 | 101 | client.receive(paymentData).then(r => { 102 | // Handle success scenario 103 | }).catch(e =>{ 104 | // Handle success scenario 105 | }); 106 | ``` 107 | 108 | ##### CommonJS 109 | 110 | ```javascript 111 | var Client = require("@paymentsds/mpesa").Client; 112 | 113 | var client = new Client({ 114 | apiKey: '', // API Key 115 | publicKey: '', // Public Key 116 | serviceProviderCode: '' // Service Provider Code 117 | }); 118 | 119 | var paymentData = { 120 | from: '841234567', // Customer MSISDN 121 | reference: '11114', // Third Party Reference 122 | transaction: 'T12344CC', // Transaction Reference 123 | amount: '10' // Amount 124 | }; 125 | 126 | client.receive(paymentData).then(function(r) { 127 | // Handle success scenario 128 | }).catch(function(e) { 129 | // Handle success scenario 130 | }); 131 | ``` 132 | 133 |
134 | 135 | #### B2C Transaction (Sending money to mobile account) 136 | 137 |
138 | 139 | ##### On ES6 Modules 140 | 141 | ```javascript 142 | import { Client } from '@paymentsds/mpesa' 143 | 144 | const client = new Client({ 145 | apiKey: '', // API Key 146 | publicKey: '', // Public Key 147 | serviceProviderCode: '' // Service Provider Code 148 | }); 149 | 150 | const paymentData = { 151 | to: '841234567', // Customer MSISDN 152 | reference: '11114', // Third Party Reference 153 | transaction: 'T12344CC', // Transaction Reference 154 | amount: '10' // Amount 155 | }; 156 | 157 | client.send(paymentData).then(function(r) { 158 | // Handle success scenario 159 | }).catch(function(e) { 160 | // Handle failure scenario 161 | }); 162 | ``` 163 | 164 | ##### CommonJS 165 | 166 | ```javascript 167 | let Client = require("@paymentsds/mpesa").Client; 168 | 169 | let client = new Client({ 170 | apiKey: '', // API Key 171 | publicKey: '', // Public Key 172 | serviceProviderCode: '' // Service Provider Code 173 | }); 174 | 175 | let paymentData = { 176 | to: '841234567', // Customer MSISDN 177 | reference: '11114', // Third Party Reference 178 | transaction: 'T12344CC', // Transaction Reference 179 | amount: '10' // Amount 180 | }; 181 | 182 | client.send(paymentData).then(r => { 183 | // Handle success scenario 184 | }).catch(e =>{ 185 | // Handle failure scenario 186 | }); 187 | ``` 188 | 189 |
190 | 191 | #### B2B Transaction (Sending money to business account) 192 | 193 |
194 | 195 | ##### On ES6 Modules 196 | 197 | ```javascript 198 | import { Client } from '@paymentsds/mpesa' 199 | 200 | const client = new Client({ 201 | apiKey: '', // API Key 202 | publicKey: '', // Public Key 203 | serviceProviderCode: '' // Service Provider Code 204 | }); 205 | 206 | const paymentData = { 207 | to: '979797', // Receiver Party Code 208 | reference: '11114', // Third Party Reference 209 | transaction: 'T12344CC', // Transaction Reference 210 | amount: '10' // Amount 211 | }; 212 | 213 | client.send(paymentData).then(r => { 214 | // Handle success scenario 215 | }).catch(e =>{ 216 | // Handle failure scenario 217 | }); 218 | ``` 219 | 220 | ##### CommonJS 221 | 222 | ```javascript 223 | let Client = require("@paymentsds/mpesa").Client; 224 | 225 | let client = new Client({ 226 | apiKey: '', // API Key 227 | publicKey: '', // Public Key 228 | serviceProviderCode: '' // Service Provider Code 229 | }); 230 | 231 | let paymentData = { 232 | to: '979797', // Receiver Party Code 233 | reference: '11114', // Third Party Reference 234 | transaction: 'T12344CC', // Transaction Reference 235 | amount: '10' // Amount 236 | }; 237 | 238 | client.send(paymentData).then(function(r) { 239 | // Handle success scenario 240 | }).catch(function(e) { 241 | // Handle failure scenario 242 | }); 243 | ``` 244 | 245 |
246 | 247 | 248 | #### Transaction Reversal 249 | 250 |
251 | 252 | ##### On ES6 Modules 253 | 254 | ```javascript 255 | import { Client } from '@paymentsds/mpesa' 256 | 257 | const client = new Client({ 258 | apiKey: '', // API Key 259 | publicKey: '', // Public Key 260 | serviceProviderCode: '', // Service Provider Code, 261 | initiatorIdentifier: '', // Initiator Identifier, 262 | securityCredential: '' // Security Credential 263 | }); 264 | 265 | const reversionData = { 266 | reference: '11114', // Third Party Reference 267 | transaction: 'T12344CC', // Transaction ID 268 | amount: '10' // Reversal Amount 269 | }; 270 | 271 | client.revert(reversionData).then(r => { 272 | // Handle success scenario 273 | }).catch(e =>{ 274 | // Handle failure scenario 275 | }); 276 | ``` 277 | 278 | ##### CommonJS 279 | 280 | ```javascript 281 | let Client = require("@paymentsds/mpesa").Client; 282 | 283 | let client = new Client({ 284 | apiKey: '', // API Key 285 | publicKey: '', // Public Key 286 | serviceProviderCode: '', // Service Provider Code, 287 | initiatorIdentifier: '', // Initiator Identifier, 288 | securityCredential: '' // Security Credential 289 | }); 290 | 291 | let reversionData = { 292 | reference: '11114', // Third Party Reference 293 | transaction: 'T12344CC', // Transaction ID 294 | amount: '10' // Reversal Amount 295 | }; 296 | 297 | client.revert(reversionData).then(function(r) { 298 | // Handle success scenario 299 | }).catch(function(e) { 300 | // Handle failure scenario 301 | }); 302 | ``` 303 | 304 |
305 | 306 | #### Query the transaction status 307 | 308 |
309 | 310 | ##### On ES6 Modules 311 | 312 | ```javascript 313 | import { Client } from '@paymentsds/mpesa' 314 | 315 | const client = new Client({ 316 | apiKey: '', // API Key 317 | publicKey: '', // Public Key 318 | serviceProviderCode: '', // Service Provider Code, 319 | }); 320 | 321 | const reversionData = { 322 | reference: '11114', // Third Party Reference 323 | subject: '5C1400CVRO', // Query Reference 324 | }; 325 | 326 | client.query(reversionData).then(r => { 327 | // Handle success scenario 328 | }).catch(e => { 329 | // Handle failure scenario 330 | }); 331 | ``` 332 | 333 | ##### CommonJS 334 | 335 | ```javascript 336 | let Client = require('@paymentsds/mpesa'); 337 | 338 | let client = new Client({ 339 | apiKey: '', // API Key 340 | publicKey: '', // Public Key 341 | serviceProviderCode: '', // Service Provider Code, 342 | }); 343 | 344 | let reversionData = { 345 | reference: '11114', // Third Party Reference 346 | subject: '5C1400CVRO', // Query Reference 347 | }; 348 | 349 | client.query(reversionData).then(function(r) { 350 | // Handle success scenario 351 | }).catch(function(e) { 352 | // Handle failure scenario 353 | }); 354 | ``` 355 | 356 |

357 | 358 | ## Friends 359 | 360 | - [M-Pesa SDK for Python](https://github.com/paymentsds/mpesa-python-sdk) 361 | - [M-Pesa SDK for Java](https://github.com/paymentsds/mpesa-java-sdk) 362 | - [M-Pesa SDK for PHP](https://github.com/paymentsds/mpesa-php-sdk) 363 | - [M-Pesa SDK for Ruby](https://github.com/paymentsds/mpesa-ruby-sdk) 364 | 365 | 366 |

367 | 368 | ## Authors 369 | 370 | - [Anísio Mandlate](https://github.com/AnisioMandlate) 371 | - [Edson Michaque](https://github.com/edsonmichaque) 372 | - [Elton Laice](https://github.com/eltonlaice) 373 | - [Nélio Macombo](https://github.com/neliomacombo) 374 | 375 | 376 |

377 | 378 | ## Contributing 379 | 380 | Thank you for considering contributing to this package. If you wish to do it, email us at [developers@paymentsds.org](mailto:developers@paymentsds.org) and we will get back to you as soon as possible. 381 | 382 | 383 |

384 | 385 | ## Security Vulnerabilities 386 | 387 | If you discover a security vulnerability, please email us at [developers@paymentsds.org](mailto:developers@paymentsds.org) and we will address the issue with the needed urgency. 388 | 389 |

390 | 391 | ## License 392 | 393 | Copyright 2022 © The PaymentsDS Team 394 | 395 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 396 | 397 | http://www.apache.org/licenses/LICENSE-2.0 398 | 399 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 400 | -------------------------------------------------------------------------------- /dist/client.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Client = void 0; 7 | 8 | var _service = require("./service.cjs"); 9 | 10 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 11 | 12 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 13 | 14 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 15 | 16 | var Client = /*#__PURE__*/function () { 17 | function Client(args) { 18 | _classCallCheck(this, Client); 19 | 20 | this.service = new _service.Service(args); 21 | } 22 | /** 23 | * Sends money to mobile or business wallet 24 | * @param {Object.} data 25 | */ 26 | 27 | 28 | _createClass(Client, [{ 29 | key: "send", 30 | value: function send(data) { 31 | return this.service.handleSend(data); 32 | } 33 | /** 34 | * Receives money from a mobile wallet 35 | * @param {Object.} data 36 | */ 37 | 38 | }, { 39 | key: "receive", 40 | value: function receive(data) { 41 | return this.service.handleReceive(data); 42 | } 43 | /** 44 | * Reverts a successful transaction 45 | * @param {Object.} data 46 | */ 47 | 48 | }, { 49 | key: "revert", 50 | value: function revert(data) { 51 | return this.service.handleRevert(data); 52 | } 53 | /** 54 | * Queries the status of a given transaction 55 | * @param {Object.} data 56 | */ 57 | 58 | }, { 59 | key: "query", 60 | value: function query(data) { 61 | return this.service.handleQuery(data); 62 | } 63 | }]); 64 | 65 | return Client; 66 | }(); 67 | 68 | exports.Client = Client; -------------------------------------------------------------------------------- /dist/configuration.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Configuration = void 0; 7 | 8 | var _crypto = _interopRequireDefault(require("crypto")); 9 | 10 | var _environment = require("./environment.cjs"); 11 | 12 | var _constants = require("./constants.cjs"); 13 | 14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 15 | 16 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } 17 | 18 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 19 | 20 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 21 | 22 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 23 | 24 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 25 | 26 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 27 | 28 | var Configuration = /*#__PURE__*/function () { 29 | function Configuration(args) { 30 | _classCallCheck(this, Configuration); 31 | 32 | this.environment = _constants.SANDBOX; 33 | this.verifySSL = false; 34 | this.timeout = 0; 35 | this.debugging = true; 36 | this.origin = "*"; 37 | this.userAgent = "".concat(_constants.USER_AGENT, "/").concat(_constants.VERSION.toString()); 38 | 39 | if (args !== null && args !== undefined) { 40 | var _iterator = _createForOfIteratorHelper(Configuration.PARAMS), 41 | _step; 42 | 43 | try { 44 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 45 | var key = _step.value; 46 | 47 | if (Object.prototype.hasOwnProperty.call(args, key)) { 48 | if (key === "host") { 49 | this.environment = _environment.Environment.fromURL(args[key]); 50 | } else { 51 | this[key] = args[key]; 52 | } 53 | } 54 | } 55 | } catch (err) { 56 | _iterator.e(err); 57 | } finally { 58 | _iterator.f(); 59 | } 60 | } 61 | } 62 | 63 | _createClass(Configuration, [{ 64 | key: "generateURL", 65 | value: function generateURL(operation) { 66 | return "".concat(this.environment.toURL()).concat(operation.toURL()); 67 | } 68 | }, { 69 | key: "generateBaseURL", 70 | value: function generateBaseURL(operation) { 71 | return "".concat(this.environment.toURL(), ":").concat(operation.port); 72 | } 73 | }, { 74 | key: "generateAccessToken", 75 | value: function generateAccessToken() { 76 | var hasKeys = Object.prototype.hasOwnProperty.call(this, "apiKey") && Object.prototype.hasOwnProperty.call(this, "publicKey"); 77 | var hasAccessToken = Object.prototype.hasOwnProperty.call(this, "accessToken"); 78 | 79 | if (hasKeys) { 80 | var publicKey = formatPublicKey(this.publicKey); 81 | var apiKeyBuffer = Buffer.from(this.apiKey); 82 | 83 | var encryptedApiKey = _crypto["default"].publicEncrypt({ 84 | key: publicKey, 85 | padding: _crypto["default"].constants.RSA_PKCS1_PADDING 86 | }, apiKeyBuffer); 87 | 88 | this.auth = encryptedApiKey.toString("base64"); 89 | } 90 | 91 | if (hasAccessToken) { 92 | this.auth = this.accessToken; 93 | } 94 | 95 | function formatPublicKey(publicKey) { 96 | var header = "-----BEGIN PUBLIC KEY-----"; 97 | var footer = "-----END PUBLIC KEY-----"; 98 | return "".concat(header, "\n").concat(publicKey, "\n").concat(footer); 99 | } 100 | } 101 | }]); 102 | 103 | return Configuration; 104 | }(); 105 | 106 | exports.Configuration = Configuration; 107 | Configuration.PARAMS = ["host", "apiKey", "publicKey", "accessToken", "verifySSL", "timeout", "debugging", "userAgent", "origin", "securityCredential", "serviceProviderCode", "initiatorIdentifier"]; -------------------------------------------------------------------------------- /dist/constants.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.VERSION = exports.USER_AGENT = exports.SANDBOX = exports.REVERSAL = exports.QUERY_TRANSACTION_STATUS = exports.PRODUCTION = exports.PATTERNS = exports.OPERATIONS = exports.HTTP = exports.ERRORS = exports.C2B_PAYMENT = exports.B2C_PAYMENT = exports.B2B_PAYMENT = void 0; 7 | 8 | var _operation = require("./operation.cjs"); 9 | 10 | var _version = require("./version.cjs"); 11 | 12 | var _environment = require("./environment.cjs"); 13 | 14 | var _OPERATIONS; 15 | 16 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 17 | 18 | var USER_AGENT = "MPesa"; 19 | exports.USER_AGENT = USER_AGENT; 20 | var C2B_PAYMENT = "C2B_PAYMENT"; 21 | exports.C2B_PAYMENT = C2B_PAYMENT; 22 | var B2B_PAYMENT = "B2B_PAYMENT"; 23 | exports.B2B_PAYMENT = B2B_PAYMENT; 24 | var B2C_PAYMENT = "B2C_PAYMENT"; 25 | exports.B2C_PAYMENT = B2C_PAYMENT; 26 | var REVERSAL = "REVERSAL"; 27 | exports.REVERSAL = REVERSAL; 28 | var QUERY_TRANSACTION_STATUS = "QUERY_TRANSACTION_STATUS"; 29 | exports.QUERY_TRANSACTION_STATUS = QUERY_TRANSACTION_STATUS; 30 | var VERSION = new _version.Version(0, 1, 0); 31 | exports.VERSION = VERSION; 32 | var HTTP = { 33 | METHOD: { 34 | GET: "get", 35 | PUT: "put", 36 | POST: "post" 37 | }, 38 | HEADERS: { 39 | USER_AGENT: "User-Agent", 40 | CONTENT_TYPE: "Content-Type", 41 | ORIGIN: "Origin", 42 | AUTHORIZATION: "Authorization" 43 | } 44 | }; 45 | exports.HTTP = HTTP; 46 | var SANDBOX = new _environment.Environment({ 47 | scheme: "https", 48 | domain: "api.sandbox.vm.co.mz" 49 | }); 50 | exports.SANDBOX = SANDBOX; 51 | var PRODUCTION = new _environment.Environment({ 52 | scheme: "https", 53 | domain: "api.mpesa.vm.co.mz" 54 | }); 55 | exports.PRODUCTION = PRODUCTION; 56 | var PATTERNS = { 57 | PHONE_NUMBER: /^((00|\+)?(258))?8[45][0-9]{7}$/, 58 | MONEY_AMOUNT: /^[1-9][0-9]*(\.[0-9]+)?$/, 59 | WORD: /^\w+$/, 60 | SERVICE_PROVIDER_CODE: /^[0-9]{5,6}$/ 61 | }; 62 | exports.PATTERNS = PATTERNS; 63 | var OPERATIONS = (_OPERATIONS = {}, _defineProperty(_OPERATIONS, C2B_PAYMENT, new _operation.Operation({ 64 | method: HTTP.METHOD.POST, 65 | port: "18352", 66 | path: "/ipg/v1x/c2bPayment/singleStage/", 67 | mapping: { 68 | from: "input_CustomerMSISDN", 69 | to: "input_ServiceProviderCode", 70 | amount: "input_Amount", 71 | transaction: "input_TransactionReference", 72 | reference: "input_ThirdPartyReference" 73 | }, 74 | validation: { 75 | from: PATTERNS.PHONE_NUMBER, 76 | to: PATTERNS.SERVICE_PROVIDER_CODE, 77 | amount: PATTERNS.MONEY_AMOUNT, 78 | transaction: PATTERNS.WORD, 79 | reference: PATTERNS.WORD 80 | }, 81 | required: ["to", "from", "amount", "transaction", "reference"], 82 | optional: ["from"] 83 | })), _defineProperty(_OPERATIONS, B2B_PAYMENT, new _operation.Operation({ 84 | method: HTTP.METHOD.POST, 85 | port: "18349", 86 | path: "/ipg/v1x/b2bPayment/", 87 | mapping: { 88 | from: "input_PrimaryPartyCode", 89 | to: "input_ReceiverPartyCode", 90 | amount: "input_Amount", 91 | transaction: "input_TransactionReference", 92 | reference: "input_ThirdPartyReference" 93 | }, 94 | validation: { 95 | from: PATTERNS.SERVICE_PROVIDER_CODE, 96 | to: PATTERNS.SERVICE_PROVIDER_CODE, 97 | amount: PATTERNS.MONEY_AMOUNT, 98 | transaction: PATTERNS.WORD, 99 | reference: PATTERNS.WORD 100 | }, 101 | required: ["to", "from", "amount", "transaction", "reference"], 102 | optional: ["from"] 103 | })), _defineProperty(_OPERATIONS, B2C_PAYMENT, new _operation.Operation({ 104 | method: HTTP.METHOD.POST, 105 | port: "18345", 106 | path: "/ipg/v1x/b2cPayment/", 107 | mapping: { 108 | number: "input_CustomerMSISDN", 109 | to: "input_CustomerMSISDN", 110 | from: "input_ServiceProviderCode", 111 | amount: "input_Amount", 112 | transaction: "input_TransactionReference", 113 | reference: "input_ThirdPartyReference" 114 | }, 115 | validation: { 116 | from: PATTERNS.SERVICE_PROVIDER_CODE, 117 | to: PATTERNS.PHONE_NUMBER, 118 | amount: PATTERNS.MONEY_AMOUNT, 119 | transaction: PATTERNS.WORD, 120 | reference: PATTERNS.WORD 121 | }, 122 | required: ["to", "from", "amount", "transaction", "reference"], 123 | optional: ["to"] 124 | })), _defineProperty(_OPERATIONS, REVERSAL, new _operation.Operation({ 125 | method: HTTP.METHOD.PUT, 126 | port: "18354", 127 | path: "/ipg/v1x/reversal/", 128 | mapping: { 129 | to: "input_ServiceProviderCode", 130 | amount: "input_Amount", 131 | reference: "input_ThirdPartyReference", 132 | transaction: "input_TransactionID", 133 | securityCredential: "input_SecurityCredential", 134 | initiatorIdentifier: "input_InitiatorIdentifier" 135 | }, 136 | validation: { 137 | to: PATTERNS.SERVICE_PROVIDER_CODE, 138 | amount: PATTERNS.MONEY_AMOUNT, 139 | reference: PATTERNS.WORD, 140 | transaction: PATTERNS.WORD, 141 | securityCredential: PATTERNS.WORD, 142 | initiatorIdentifier: PATTERNS.WORD 143 | }, 144 | required: ["to", "amount", "reference", "transaction", "securityCredential", "initiatorIdentifier"], 145 | optional: ["to", "securityCredential", "initiatorIdentifier"] 146 | })), _defineProperty(_OPERATIONS, QUERY_TRANSACTION_STATUS, new _operation.Operation({ 147 | method: HTTP.METHOD.GET, 148 | port: "18353", 149 | path: "/ipg/v1x/queryTransactionStatus/", 150 | mapping: { 151 | from: "input_ServiceProviderCode", 152 | subject: "input_QueryReference", 153 | reference: "input_ThirdPartyReference" 154 | }, 155 | validation: { 156 | from: PATTERNS.SERVICE_PROVIDER_CODE, 157 | subject: PATTERNS.WORD, 158 | reference: PATTERNS.WORD 159 | }, 160 | required: ["from", "subject", "reference"], 161 | optional: ["from"] 162 | })), _OPERATIONS); 163 | exports.OPERATIONS = OPERATIONS; 164 | var ERRORS = { 165 | VALIDATION: { 166 | code: 3000, 167 | description: "The data provider is not valid" 168 | }, 169 | MISSING: { 170 | code: 3001, 171 | description: "There are attributes missing to complete the transaction" 172 | }, 173 | AUTHENTICATION: { 174 | code: 3002, 175 | description: "There is not a public key and API key or access token set" 176 | }, 177 | INVALID_OPERATION: { 178 | code: 3003, 179 | descrition: "Unable to detect the operation from the destination number" 180 | }, 181 | INVALID_ENV: { 182 | code: 3003, 183 | descrition: "Unable to detect the base URL" 184 | } 185 | }; 186 | exports.ERRORS = ERRORS; -------------------------------------------------------------------------------- /dist/environment.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Environment = void 0; 7 | 8 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } 9 | 10 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 11 | 12 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 19 | 20 | var Environment = /*#__PURE__*/function () { 21 | function Environment(args) { 22 | _classCallCheck(this, Environment); 23 | 24 | if (args !== null && args !== undefined) { 25 | var _iterator = _createForOfIteratorHelper(Environment.defaultProperties), 26 | _step; 27 | 28 | try { 29 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 30 | var key = _step.value; 31 | 32 | if (Object.prototype.hasOwnProperty.call(args, key)) { 33 | this[key] = args[key]; 34 | } 35 | } 36 | } catch (err) { 37 | _iterator.e(err); 38 | } finally { 39 | _iterator.f(); 40 | } 41 | } 42 | } 43 | 44 | _createClass(Environment, [{ 45 | key: "toURL", 46 | value: function toURL() { 47 | return "".concat(this.scheme, "://").concat(this.domain); 48 | } 49 | /** 50 | * Creates environment object from a given URL 51 | * @param {string} url 52 | */ 53 | 54 | }, { 55 | key: "isValid", 56 | value: function isValid() { 57 | return this.scheme != null && this.domain != null; 58 | } 59 | }], [{ 60 | key: "fromURL", 61 | value: function fromURL(url) { 62 | var parts; 63 | 64 | if (/^https:\/\//.test(url) || /^http:\/\//.test(url)) { 65 | parts = url.split("://"); 66 | } else { 67 | parts = ["https", url]; 68 | } 69 | 70 | return new Environment({ 71 | scheme: parts[0], 72 | domain: parts[1] 73 | }); 74 | } 75 | }]); 76 | 77 | return Environment; 78 | }(); 79 | 80 | exports.Environment = Environment; 81 | Environment.defaultProperties = ["name", "scheme", "domain"]; -------------------------------------------------------------------------------- /dist/errors.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports.ValidationError = exports.TimeoutError = exports.MissingPropertiesError = exports.InvalidReceiverError = exports.InvalidHostError = exports.AuthenticationError = void 0; 9 | 10 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 11 | 12 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 17 | 18 | function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } 19 | 20 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } 21 | 22 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } 23 | 24 | function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); } 25 | 26 | function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct.bind(); } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); } 27 | 28 | function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } } 29 | 30 | function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } 31 | 32 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 33 | 34 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } 35 | 36 | var MissingPropertiesError = /*#__PURE__*/function (_Error) { 37 | _inherits(MissingPropertiesError, _Error); 38 | 39 | var _super = _createSuper(MissingPropertiesError); 40 | 41 | function MissingPropertiesError(name, message) { 42 | var _this; 43 | 44 | _classCallCheck(this, MissingPropertiesError); 45 | 46 | _this = _super.call(this); 47 | _this.name = name || "MissingPropertiesError"; 48 | _this.message = message || "There are attributes missing to complete the transaction"; 49 | return _this; 50 | } 51 | 52 | return _createClass(MissingPropertiesError); 53 | }( /*#__PURE__*/_wrapNativeSuper(Error)); 54 | 55 | exports.MissingPropertiesError = MissingPropertiesError; 56 | 57 | var AuthenticationError = /*#__PURE__*/function (_Error2) { 58 | _inherits(AuthenticationError, _Error2); 59 | 60 | var _super2 = _createSuper(AuthenticationError); 61 | 62 | function AuthenticationError(name, message) { 63 | var _this2; 64 | 65 | _classCallCheck(this, AuthenticationError); 66 | 67 | _this2 = _super2.call(this); 68 | _this2.name = name || "AuthenticationError"; 69 | _this2.message = message || "There is not a public key and API key or access token set"; 70 | return _this2; 71 | } 72 | 73 | return _createClass(AuthenticationError); 74 | }( /*#__PURE__*/_wrapNativeSuper(Error)); 75 | 76 | exports.AuthenticationError = AuthenticationError; 77 | 78 | var InvalidReceiverError = /*#__PURE__*/function (_Error3) { 79 | _inherits(InvalidReceiverError, _Error3); 80 | 81 | var _super3 = _createSuper(InvalidReceiverError); 82 | 83 | function InvalidReceiverError(name, message) { 84 | var _this3; 85 | 86 | _classCallCheck(this, InvalidReceiverError); 87 | 88 | _this3 = _super3.call(this); 89 | _this3.name = name || "InvalidReceiverError"; 90 | _this3.message = message || "The receiver does not look like a valid phone number nor a valid service provider code"; 91 | return _this3; 92 | } 93 | 94 | return _createClass(InvalidReceiverError); 95 | }( /*#__PURE__*/_wrapNativeSuper(Error)); 96 | 97 | exports.InvalidReceiverError = InvalidReceiverError; 98 | 99 | var ValidationError = /*#__PURE__*/function (_Error4) { 100 | _inherits(ValidationError, _Error4); 101 | 102 | var _super4 = _createSuper(ValidationError); 103 | 104 | function ValidationError(name, message) { 105 | var _this4; 106 | 107 | _classCallCheck(this, ValidationError); 108 | 109 | _this4 = _super4.call(this); 110 | _this4.name = name || "ValidationError"; 111 | _this4.message = message || "The data provider is not valid"; 112 | return _this4; 113 | } 114 | 115 | return _createClass(ValidationError); 116 | }( /*#__PURE__*/_wrapNativeSuper(Error)); 117 | 118 | exports.ValidationError = ValidationError; 119 | 120 | var InvalidHostError = /*#__PURE__*/function (_Error5) { 121 | _inherits(InvalidHostError, _Error5); 122 | 123 | var _super5 = _createSuper(InvalidHostError); 124 | 125 | function InvalidHostError(name, message) { 126 | var _this5; 127 | 128 | _classCallCheck(this, InvalidHostError); 129 | 130 | _this5 = _super5.call(this); 131 | _this5.name = name || "InvalidHostError"; 132 | _this5.message = message || "The provider host is not valid"; 133 | return _this5; 134 | } 135 | 136 | return _createClass(InvalidHostError); 137 | }( /*#__PURE__*/_wrapNativeSuper(Error)); 138 | 139 | exports.InvalidHostError = InvalidHostError; 140 | 141 | var TimeoutError = /*#__PURE__*/function (_Error6) { 142 | _inherits(TimeoutError, _Error6); 143 | 144 | var _super6 = _createSuper(TimeoutError); 145 | 146 | function TimeoutError(name, message) { 147 | var _this6; 148 | 149 | _classCallCheck(this, TimeoutError); 150 | 151 | _this6 = _super6.call(this); 152 | _this6.name = name || "TimeoutError"; 153 | _this6.message = message || "The request has taken more time than allowed"; 154 | return _this6; 155 | } 156 | 157 | return _createClass(TimeoutError); 158 | }( /*#__PURE__*/_wrapNativeSuper(Error)); 159 | 160 | exports.TimeoutError = TimeoutError; -------------------------------------------------------------------------------- /dist/index.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | Object.defineProperty(exports, "Client", { 7 | enumerable: true, 8 | get: function get() { 9 | return _client.Client; 10 | } 11 | }); 12 | Object.defineProperty(exports, "Configuration", { 13 | enumerable: true, 14 | get: function get() { 15 | return _configuration.Configuration; 16 | } 17 | }); 18 | Object.defineProperty(exports, "Environment", { 19 | enumerable: true, 20 | get: function get() { 21 | return _environment.Environment; 22 | } 23 | }); 24 | Object.defineProperty(exports, "Operation", { 25 | enumerable: true, 26 | get: function get() { 27 | return _operation.Operation; 28 | } 29 | }); 30 | Object.defineProperty(exports, "Service", { 31 | enumerable: true, 32 | get: function get() { 33 | return _service.Service; 34 | } 35 | }); 36 | Object.defineProperty(exports, "TimeoutError", { 37 | enumerable: true, 38 | get: function get() { 39 | return _errors.TimeoutError; 40 | } 41 | }); 42 | 43 | var _environment = require("./environment.cjs"); 44 | 45 | var _operation = require("./operation.cjs"); 46 | 47 | var _configuration = require("./configuration.cjs"); 48 | 49 | var _client = require("./client.cjs"); 50 | 51 | var _service = require("./service.cjs"); 52 | 53 | var _errors = require("./errors.cjs"); -------------------------------------------------------------------------------- /dist/operation.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Operation = void 0; 7 | 8 | function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } 9 | 10 | function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } 11 | 12 | function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } 13 | 14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 15 | 16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 17 | 18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 19 | 20 | var Operation = /*#__PURE__*/function () { 21 | function Operation(args) { 22 | _classCallCheck(this, Operation); 23 | 24 | if (args !== null && args !== undefined) { 25 | var _iterator = _createForOfIteratorHelper(Operation.defaultProperties), 26 | _step; 27 | 28 | try { 29 | for (_iterator.s(); !(_step = _iterator.n()).done;) { 30 | var key = _step.value; 31 | 32 | if (Object.prototype.hasOwnProperty.call(args, key)) { 33 | this[key] = args[key]; 34 | } 35 | } 36 | } catch (err) { 37 | _iterator.e(err); 38 | } finally { 39 | _iterator.f(); 40 | } 41 | } 42 | } 43 | 44 | _createClass(Operation, [{ 45 | key: "toURL", 46 | value: function toURL() { 47 | if (this.isValid()) { 48 | var pathWithoudSlash = this.path.replace(/^\/+/, ""); 49 | return ":".concat(this.port, "/").concat(pathWithoudSlash); 50 | } 51 | } 52 | }, { 53 | key: "isValid", 54 | value: function isValid() { 55 | return this.name != null && this.port != null && this.path != null; 56 | } 57 | }]); 58 | 59 | return Operation; 60 | }(); 61 | 62 | exports.Operation = Operation; 63 | Operation.defaultProperties = ["name", "method", "port", "path", "mapping", "validation", "required", "optional"]; -------------------------------------------------------------------------------- /dist/response.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Response = void 0; 7 | 8 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 9 | 10 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 11 | 12 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 13 | 14 | var Response = /*#__PURE__*/_createClass(function Response(status, code, description, data) { 15 | _classCallCheck(this, Response); 16 | 17 | this.status = status; 18 | this.code = code; 19 | this.description = description; 20 | this.data = data; 21 | 22 | if (this.status >= 100 && this.status < 300) { 23 | this.success = true; 24 | } else { 25 | this.success = false; 26 | } 27 | }); 28 | 29 | exports.Response = Response; -------------------------------------------------------------------------------- /dist/service.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } 4 | 5 | Object.defineProperty(exports, "__esModule", { 6 | value: true 7 | }); 8 | exports.Service = void 0; 9 | 10 | var _axios = _interopRequireDefault(require("axios")); 11 | 12 | var _configuration = require("./configuration.cjs"); 13 | 14 | var _errors = require("./errors.cjs"); 15 | 16 | var _constants = require("./constants.cjs"); 17 | 18 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } 19 | 20 | function _wrapRegExp() { _wrapRegExp = function _wrapRegExp(re, groups) { return new BabelRegExp(re, void 0, groups); }; var _super = RegExp.prototype, _groups = new WeakMap(); function BabelRegExp(re, flags, groups) { var _this = new RegExp(re, flags); return _groups.set(_this, groups || _groups.get(re)), _setPrototypeOf(_this, BabelRegExp.prototype); } function buildGroups(result, re) { var g = _groups.get(re); return Object.keys(g).reduce(function (groups, name) { return groups[name] = result[g[name]], groups; }, Object.create(null)); } return _inherits(BabelRegExp, RegExp), BabelRegExp.prototype.exec = function (str) { var result = _super.exec.call(this, str); return result && (result.groups = buildGroups(result, this)), result; }, BabelRegExp.prototype[Symbol.replace] = function (str, substitution) { if ("string" == typeof substitution) { var groups = _groups.get(this); return _super[Symbol.replace].call(this, str, substitution.replace(/\$<([^>]+)>/g, function (_, name) { return "$" + groups[name]; })); } if ("function" == typeof substitution) { var _this = this; return _super[Symbol.replace].call(this, str, function () { var args = arguments; return "object" != _typeof(args[args.length - 1]) && (args = [].slice.call(args)).push(buildGroups(args, _this)), substitution.apply(this, args); }); } return _super[Symbol.replace].call(this, str, substitution); }, _wrapRegExp.apply(this, arguments); } 21 | 22 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 23 | 24 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 25 | 26 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } 27 | 28 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 29 | 30 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 31 | 32 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 33 | 34 | var Service = /*#__PURE__*/function () { 35 | function Service(args) { 36 | _classCallCheck(this, Service); 37 | 38 | this.initDefaultConfigs(args); 39 | } 40 | /** 41 | * Initializes default configurations 42 | * @param {Object} args 43 | */ 44 | 45 | 46 | _createClass(Service, [{ 47 | key: "initDefaultConfigs", 48 | value: function initDefaultConfigs(args) { 49 | this.config = new _configuration.Configuration(args); 50 | } 51 | /** 52 | * 53 | * @param {Object} intent 54 | */ 55 | 56 | }, { 57 | key: "handleSend", 58 | value: function handleSend(intent) { 59 | var opcode = this.detectOperation(intent); 60 | return this.handleRequest(opcode, intent); 61 | } 62 | }, { 63 | key: "handleReceive", 64 | value: function handleReceive(intent) { 65 | return this.handleRequest(_constants.C2B_PAYMENT, intent); 66 | } 67 | }, { 68 | key: "handleRevert", 69 | value: function handleRevert(intent) { 70 | return this.handleRequest(_constants.REVERSAL, intent); 71 | } 72 | }, { 73 | key: "handleQuery", 74 | value: function handleQuery(intent) { 75 | return this.handleRequest(_constants.QUERY_TRANSACTION_STATUS, intent); 76 | } 77 | /** 78 | * Validates transaction data and performs performs the needed HTTP request 79 | * @param {string} opcode 80 | * @param {Object.} intent 81 | */ 82 | 83 | }, { 84 | key: "handleRequest", 85 | value: function handleRequest(opcode, intent) { 86 | var data = this.fillOptionalProperties(opcode, intent); 87 | var missingProperties = this.detectMissingProperties(opcode, intent); 88 | 89 | if (missingProperties.length > 0) { 90 | throw new _errors.MissingPropertiesError(); 91 | } 92 | 93 | var validationErrors = this.detectErrors(opcode, data); 94 | 95 | if (validationErrors.length > 0) { 96 | throw new _errors.ValidationError(); 97 | } 98 | 99 | return this.performRequest(opcode, intent); 100 | } 101 | /** 102 | * Detects the operation from the transaction data 103 | * @param {Object.} intent 104 | */ 105 | 106 | }, { 107 | key: "detectOperation", 108 | value: function detectOperation(intent) { 109 | if (Object.prototype.hasOwnProperty.call(intent, "to")) { 110 | if (_constants.PATTERNS.PHONE_NUMBER.test(intent.to)) { 111 | return _constants.B2C_PAYMENT; 112 | } 113 | 114 | if (_constants.PATTERNS.SERVICE_PROVIDER_CODE.test(intent.to)) { 115 | return _constants.B2B_PAYMENT; 116 | } 117 | } 118 | 119 | throw new _errors.InvalidReceiverError(); 120 | } 121 | /** 122 | * Detect validation errors from thransaction data 123 | * @param {string} opcode 124 | * @param {Object.} intent 125 | */ 126 | 127 | }, { 128 | key: "detectErrors", 129 | value: function detectErrors(opcode, intent) { 130 | var operations = _constants.OPERATIONS[opcode]; 131 | var errors = operations.required.filter(function (e) { 132 | var pattern = operations.validation[e]; 133 | return !pattern.test(intent[e]); 134 | }); 135 | return errors; 136 | } 137 | /** 138 | * Detects missing properties from transaction data 139 | * @param {string} opcode 140 | * @param {Object.} data 141 | */ 142 | 143 | }, { 144 | key: "detectMissingProperties", 145 | value: function detectMissingProperties(opcode, data) { 146 | var required = _constants.OPERATIONS[opcode].required; 147 | var missing = required.filter(function (e) { 148 | return !Object.prototype.hasOwnProperty.call(data, e); 149 | }); 150 | return missing; 151 | } 152 | /** 153 | * Complete transaction data from configuration data if it is not already provided 154 | * @param {string} opcode 155 | * @param {Object.} intent 156 | */ 157 | 158 | }, { 159 | key: "fillOptionalProperties", 160 | value: function fillOptionalProperties(opcode, intent) { 161 | var self = this; 162 | 163 | function map(correspondences) { 164 | for (var k in correspondences) { 165 | if (!Object.prototype.hasOwnProperty.call(intent, k) && Object.prototype.hasOwnProperty.call(self.config, correspondences[k])) { 166 | intent[k] = self.config[correspondences[k]]; 167 | } 168 | } 169 | 170 | return intent; 171 | } 172 | 173 | switch (opcode) { 174 | case _constants.C2B_PAYMENT: 175 | return map({ 176 | to: "serviceProviderCode" 177 | }); 178 | 179 | case _constants.B2C_PAYMENT: 180 | case _constants.B2B_PAYMENT: 181 | return map({ 182 | from: "serviceProviderCode" 183 | }); 184 | 185 | case _constants.REVERSAL: 186 | return map({ 187 | initiatorIdentifier: "initiatorIdentifier", 188 | securityCredential: "securityCredential", 189 | to: "serviceProviderCode" 190 | }); 191 | 192 | case _constants.QUERY_TRANSACTION_STATUS: 193 | return map({ 194 | from: "serviceProviderCode" 195 | }); 196 | } 197 | 198 | return intent; 199 | } 200 | /** 201 | * Formats transaction data to the format required by M-Pesa API 202 | * @param {string} opcode 203 | * @param {Object.} intent 204 | */ 205 | 206 | }, { 207 | key: "buildRequestBody", 208 | value: function buildRequestBody(opcode, intent) { 209 | var body = {}; 210 | 211 | for (var oldKey in intent) { 212 | var newKey = _constants.OPERATIONS[opcode].mapping[oldKey]; 213 | 214 | if (opcode === _constants.C2B_PAYMENT && oldKey === "from" || opcode === _constants.B2C_PAYMENT && oldKey == "to") { 215 | body[newKey] = this.normalizePhoneNumber(intent[oldKey]); 216 | } else { 217 | body[newKey] = intent[oldKey]; 218 | } 219 | } 220 | 221 | return body; 222 | } 223 | /** 224 | * Generates HTTP headers required to perform the request 225 | * @param {string} opcode 226 | * @param {Object.} intent 227 | */ 228 | 229 | }, { 230 | key: "buildRequestHeaders", 231 | value: function buildRequestHeaders(opcode, intent) { 232 | var _headers; 233 | 234 | var headers = (_headers = {}, _defineProperty(_headers, _constants.HTTP.HEADERS.USER_AGENT, this.config.userAgent), _defineProperty(_headers, _constants.HTTP.HEADERS.ORIGIN, this.config.origin), _defineProperty(_headers, _constants.HTTP.HEADERS.CONTENT_TYPE, "application.cjson"), _defineProperty(_headers, _constants.HTTP.HEADERS.AUTHORIZATION, "Bearer ".concat(this.config.auth)), _headers); 235 | return headers; 236 | } 237 | }, { 238 | key: "performRequest", 239 | value: function performRequest(opcode, intent) { 240 | this.generateAccessToken(); 241 | 242 | if (Object.prototype.hasOwnProperty.call(this.config, "environment")) { 243 | if (Object.prototype.hasOwnProperty.call(this.config, "auth")) { 244 | var operation = _constants.OPERATIONS[opcode]; 245 | var headers = this.buildRequestHeaders(opcode, intent); 246 | var body = this.buildRequestBody(opcode, intent); 247 | var requestData = { 248 | baseURL: "".concat(this.config.environment.scheme, "://").concat(this.config.environment.domain, ":").concat(operation.port), 249 | insecureHTTPParser: true, 250 | url: operation.path, 251 | method: operation.method, 252 | path: operation.path, 253 | headers: headers, 254 | timeout: this.config.timeout * 1000, 255 | maxRedirects: 0 256 | }; 257 | 258 | if (operation.method === _constants.HTTP.METHOD.GET) { 259 | requestData.params = body; 260 | } else { 261 | requestData.data = body; 262 | } 263 | 264 | var self = this; 265 | return (0, _axios["default"])(requestData).then(function (r) { 266 | return Promise.resolve(self.buildResponse(r)); 267 | })["catch"](function (e) { 268 | return Promise.reject(self.buildResponse(e)); 269 | }); 270 | } 271 | 272 | throw new _errors.AuthenticationError(); 273 | } else { 274 | throw new _errors.InvalidHostError(); 275 | } 276 | } 277 | /** 278 | * Formats the result 279 | * @param {*} result 280 | */ 281 | 282 | }, { 283 | key: "buildResponse", 284 | value: function buildResponse(result) { 285 | if (result.status >= 200 && result.status < 300) { 286 | return { 287 | response: { 288 | status: result.status, 289 | code: result.data.output_ResponseCode, 290 | desc: result.data.output_ResponseDesc 291 | }, 292 | conversation: result.data.output_ConversationID, 293 | transaction: result.data.output_TransactionID, 294 | reference: result.data.output_ThirdPartyReference 295 | }; 296 | } 297 | 298 | return { 299 | response: { 300 | status: result.response.status, 301 | statusText: result.response.statusText, 302 | outputError: result.response.data.output_error 303 | } 304 | }; 305 | } 306 | /** 307 | * Generates access token from public key and API key pair 308 | */ 309 | 310 | }, { 311 | key: "generateAccessToken", 312 | value: function generateAccessToken() { 313 | this.config.generateAccessToken(); 314 | } 315 | }, { 316 | key: "normalizePhoneNumber", 317 | value: function normalizePhoneNumber(phoneNumber) { 318 | var phoneNumberCountryCode = /*#__PURE__*/_wrapRegExp(/^(((00?|\+)?258)?)(8[54][0-9]{7})$/, { 319 | prefix: 2, 320 | localNumber: 4 321 | }); 322 | 323 | return "258".concat(phoneNumber.match(phoneNumberCountryCode).groups.localNumber); 324 | } 325 | }]); 326 | 327 | return Service; 328 | }(); 329 | 330 | exports.Service = Service; -------------------------------------------------------------------------------- /dist/version.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.Version = void 0; 7 | 8 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 9 | 10 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } 11 | 12 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } 13 | 14 | var Version = /*#__PURE__*/function () { 15 | function Version(major, minor, patch) { 16 | _classCallCheck(this, Version); 17 | 18 | this.major = major; 19 | this.minor = minor; 20 | this.patch = patch; 21 | } 22 | 23 | _createClass(Version, [{ 24 | key: "toString", 25 | value: function toString() { 26 | return "".concat(this.major, ".").concat(this.minor, ".").concat(this.patch); 27 | } 28 | }]); 29 | 30 | return Version; 31 | }(); 32 | 33 | exports.Version = Version; -------------------------------------------------------------------------------- /docs/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paymentsds/mpesa-js-sdk/91c7254a740af8aa747bcd1e4e90879cdd696140/docs/.keep -------------------------------------------------------------------------------- /examples/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paymentsds/mpesa-js-sdk/91c7254a740af8aa747bcd1e4e90879cdd696140/examples/.keep -------------------------------------------------------------------------------- /examples/b2bPayment.js: -------------------------------------------------------------------------------- 1 | import { Service, SANDBOX } from '@paysuite/mpesa' 2 | import dotenv from 'dontenv' 3 | 4 | dotenv.config() 5 | 6 | const API_KEY = process.env('API_KEY') 7 | const PUBLIC_KEY = process.env('PUBLIC_KEY') 8 | const USER_AGENT = process.env('USER_AGENT') 9 | const ORIGIN = process.env('ORIGIN') 10 | const HOST = process.env('HOST') 11 | const SERVICE_PROVIDER_CODE = process.env('SERVICE_PROVIDER_CODE') 12 | 13 | const client = new Client({ 14 | apiKey: API_KEY, 15 | publicKey: PUBLIC_KEY, 16 | userAgent: USER_AGENT, 17 | origin: ORIGIN, 18 | host: HOST, 19 | serviceProviderCode: SERVICE_PROVIDER_CODE, 20 | verifySSL: false, 21 | debugging: true, 22 | environment: SANDBOX 23 | }) 24 | 25 | client 26 | .send({ 27 | to: '841234567', 28 | transaction: '123456789', 29 | reference: '123456789', 30 | amount: '100' 31 | }) 32 | .then((r) => { 33 | console.log(r) 34 | }) 35 | .catch((e) => { 36 | console.log(e) 37 | }) 38 | -------------------------------------------------------------------------------- /examples/b2cPayment.js: -------------------------------------------------------------------------------- 1 | import { Service } from '@paysuite/mpesa' 2 | import dotenv from 'dontenv' 3 | 4 | dotenv.config() 5 | 6 | const API_KEY = process.env('API_KEY') 7 | const PUBLIC_KEY = process.env('PUBLIC_KEY') 8 | const USER_AGENT = process.env('USER_AGENT') 9 | const ORIGIN = process.env('ORIGIN') 10 | const HOST = process.env('HOST') 11 | const SERVICE_PROVIDER_CODE = process.env('SERVICE_PROVIDER_CODE') 12 | 13 | const client = new Client({ 14 | apiKey: API_KEY, 15 | publicKey: PUBLIC_KEY, 16 | userAgent: USER_AGENT, 17 | origin: ORIGIN, 18 | host: HOST, 19 | serviceProviderCode: SERVICE_PROVIDER_CODE, 20 | verifySSL: false, 21 | debugging: true, 22 | environment: SANDBOX 23 | }) 24 | 25 | client 26 | .send({ 27 | to: '171717', 28 | transaction: '123456789', 29 | reference: '123456789', 30 | amount: '100' 31 | }) 32 | .then((r) => { 33 | console.log(r) 34 | }) 35 | .catch((e) => { 36 | console.log(e) 37 | }) 38 | -------------------------------------------------------------------------------- /examples/c2bPayment.js: -------------------------------------------------------------------------------- 1 | import { Service } from '@paysuite/mpesa' 2 | import dotenv from 'dontenv' 3 | 4 | dotenv.config() 5 | 6 | const API_KEY = process.env('API_KEY') 7 | const PUBLIC_KEY = process.env('PUBLIC_KEY') 8 | const USER_AGENT = process.env('USER_AGENT') 9 | const ORIGIN = process.env('ORIGIN') 10 | const HOST = process.env('HOST') 11 | const SERVICE_PROVIDER_CODE = process.env('SERVICE_PROVIDER_CODE') 12 | 13 | const client = new Client({ 14 | apiKey: API_KEY, 15 | publicKey: PUBLIC_KEY, 16 | userAgent: USER_AGENT, 17 | origin: ORIGIN, 18 | host: HOST, 19 | serviceProviderCode: SERVICE_PROVIDER_CODE, 20 | verifySSL: false, 21 | debugging: true, 22 | environment: SANDBOX 23 | }) 24 | 25 | client 26 | .receive({ 27 | from: '841234567', 28 | transaction: '123456789', 29 | reference: '123456789', 30 | amount: '100' 31 | }) 32 | .then((r) => { 33 | console.log(r) 34 | }) 35 | .catch((e) => { 36 | console.log(e) 37 | }) 38 | -------------------------------------------------------------------------------- /examples/queryTransactionStatus.js: -------------------------------------------------------------------------------- 1 | import { Service } from '@paysuite/mpesa' 2 | import dotenv from 'dontenv' 3 | 4 | dotenv.config() 5 | 6 | const API_KEY = process.env('API_KEY') 7 | const PUBLIC_KEY = process.env('PUBLIC_KEY') 8 | const USER_AGENT = process.env('USER_AGENT') 9 | const ORIGIN = process.env('ORIGIN') 10 | const HOST = process.env('HOST') 11 | const SERVICE_PROVIDER_CODE = process.env('SERVICE_PROVIDER_CODE') 12 | const SECURITY_CREDENTIAL = process.env('SECURITY_CREDENTIAL') 13 | const INITIATOR_IDENTIFIER = process.env('INITIATOR_IDENTIFIER') 14 | 15 | const client = new Client({ 16 | apiKey: API_KEY, 17 | publicKey: PUBLIC_KEY, 18 | userAgent: USER_AGENT, 19 | origin: ORIGIN, 20 | host: HOST, 21 | serviceProviderCode: SERVICE_PROVIDER_CODE, 22 | initiatorIdentifier: INITIATOR_IDENTIFIER, 23 | securityCredential: SECURITY_CREDENTIAL, 24 | verifySSL: false, 25 | debugging: true, 26 | environment: SANDBOX 27 | }) 28 | 29 | // Replace subject with tranaction, reference ou conversation returned from a successful transaction 30 | client 31 | .query({ subject: '1234567890', reference: '123456789' }) 32 | .then((r) => { 33 | console.log(r) 34 | }) 35 | .catch((e) => { 36 | console.log(e) 37 | }) 38 | -------------------------------------------------------------------------------- /examples/reversal.js: -------------------------------------------------------------------------------- 1 | import { Service } from '@paysuite/mpesa' 2 | import dotenv from 'dontenv' 3 | 4 | dotenv.config() 5 | 6 | const API_KEY = process.env('API_KEY') 7 | const PUBLIC_KEY = process.env('PUBLIC_KEY') 8 | const USER_AGENT = process.env('USER_AGENT') 9 | const ORIGIN = process.env('ORIGIN') 10 | const HOST = process.env('HOST') 11 | const SERVICE_PROVIDER_CODE = process.env('SERVICE_PROVIDER_CODE') 12 | const SECURITY_CREDENTIAL = process.env('SECURITY_CREDENTIAL') 13 | const INITIATOR_IDENTIFIER = process.env('INITIATOR_IDENTIFIER') 14 | 15 | const client = new Client({ 16 | apiKey: API_KEY, 17 | publicKey: PUBLIC_KEY, 18 | userAgent: USER_AGENT, 19 | origin: ORIGIN, 20 | host: HOST, 21 | serviceProviderCode: SERVICE_PROVIDER_CODE, 22 | verifySSL: false, 23 | debugging: true, 24 | environment: SANDBOX, 25 | initiatorIdentifier: INITIATOR_IDENTIFIER, 26 | securityCredential: SECURITY_CREDENTIAL 27 | }) 28 | 29 | client 30 | .revert({ transaction: '123456789', reference: '123456789', amount: '100' }) 31 | .then((r) => { 32 | console.log(r) 33 | }) 34 | .catch((e) => { 35 | console.log(e) 36 | }) 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@paymentsds/mpesa", 3 | "type": "module", 4 | "version": "0.1.0-alpha-9", 5 | "description": "MPesa Javascript API Client", 6 | "main": "./dist/index.cjs", 7 | "exports": { 8 | "import": "./src/index.js", 9 | "require": "./dist/index.cjs" 10 | }, 11 | "scripts": { 12 | "test": "mocha", 13 | "lint": "eslint --fix src/*.js", 14 | "prettier": "prettier -w src/*", 15 | "build": "rm dist/* && babel src -d dist --out-file-extension .cjs && find ./dist -type f -exec sed -i 's/.js/.cjs/g' {} \\;", 16 | "pre-commit": "npm run prettier && npm run build && npm run change-file-imports && git add dist/", 17 | "change-file-imports": "find ./dist -type f -exec sed -i 's/.js/.cjs/g' {} \\;" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/paymentsds/mpesa-js-sdk.git" 22 | }, 23 | "keywords": [ 24 | "mpesa" 25 | ], 26 | "author": "Edson Michaque", 27 | "license": "Apache-2.0", 28 | "bugs": { 29 | "url": "https://github.com/paymentsds/mpesa-js-sdk/issues" 30 | }, 31 | 32 | "homepage": "https://github.com/paymentsds/mpesa-js-sdk#readme", 33 | "dependencies": { 34 | "axios": "^1.6.0" 35 | }, 36 | "devDependencies": { 37 | "@babel/cli": "^7.10.5", 38 | "@babel/core": "^7.10.5", 39 | "@babel/preset-env": "^7.10.4", 40 | "chai": "^4.2.0", 41 | "dotenv": "^16.3.1", 42 | "eslint": "^8.53.0", 43 | "eslint-config-airbnb": "^19.0.4", 44 | "eslint-config-prettier": "^9.0.0", 45 | "eslint-plugin-import": "^2.22.1", 46 | "eslint-plugin-jsx-a11y": "^6.3.1", 47 | "eslint-plugin-prettier": "^5.0.1", 48 | "eslint-plugin-react": "^7.21.2", 49 | "eslint-plugin-react-hooks": "^4.0.0", 50 | "husky": "^8.0.3", 51 | "mocha": "^10.0.0", 52 | "prettier": "^3.0.3", 53 | "sinon": "^17.0.1" 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | import { Service } from "./service.js"; 2 | 3 | export class Client { 4 | constructor(args) { 5 | this.service = new Service(args); 6 | } 7 | 8 | /** 9 | * Sends money to mobile or business wallet 10 | * @param {Object.} data 11 | */ 12 | send(data) { 13 | return this.service.handleSend(data); 14 | } 15 | 16 | /** 17 | * Receives money from a mobile wallet 18 | * @param {Object.} data 19 | */ 20 | receive(data) { 21 | return this.service.handleReceive(data); 22 | } 23 | 24 | /** 25 | * Reverts a successful transaction 26 | * @param {Object.} data 27 | */ 28 | revert(data) { 29 | return this.service.handleRevert(data); 30 | } 31 | 32 | /** 33 | * Queries the status of a given transaction 34 | * @param {Object.} data 35 | */ 36 | query(data) { 37 | return this.service.handleQuery(data); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/configuration.js: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | import { Environment } from "./environment.js"; 4 | import { SANDBOX, USER_AGENT, VERSION } from "./constants.js"; 5 | 6 | class Configuration { 7 | constructor(args) { 8 | this.environment = SANDBOX; 9 | this.verifySSL = false; 10 | this.timeout = 0; 11 | this.debugging = true; 12 | this.origin = "*"; 13 | this.userAgent = `${USER_AGENT}/${VERSION.toString()}`; 14 | 15 | if (args !== null && args !== undefined) { 16 | for (const key of Configuration.PARAMS) { 17 | if (Object.prototype.hasOwnProperty.call(args, key)) { 18 | if (key === "host") { 19 | this.environment = Environment.fromURL(args[key]); 20 | } else { 21 | this[key] = args[key]; 22 | } 23 | } 24 | } 25 | } 26 | } 27 | 28 | generateURL(operation) { 29 | return `${this.environment.toURL()}${operation.toURL()}`; 30 | } 31 | 32 | generateBaseURL(operation) { 33 | return `${this.environment.toURL()}:${operation.port}`; 34 | } 35 | 36 | generateAccessToken() { 37 | const hasKeys = 38 | Object.prototype.hasOwnProperty.call(this, "apiKey") && 39 | Object.prototype.hasOwnProperty.call(this, "publicKey"); 40 | const hasAccessToken = Object.prototype.hasOwnProperty.call(this, "accessToken"); 41 | 42 | if (hasKeys) { 43 | const publicKey = formatPublicKey(this.publicKey); 44 | const apiKeyBuffer = Buffer.from(this.apiKey); 45 | 46 | const encryptedApiKey = crypto.publicEncrypt( 47 | { 48 | key: publicKey, 49 | padding: crypto.constants.RSA_PKCS1_PADDING, 50 | }, 51 | apiKeyBuffer 52 | ); 53 | 54 | this.auth = encryptedApiKey.toString("base64"); 55 | } 56 | 57 | if (hasAccessToken) { 58 | this.auth = this.accessToken; 59 | } 60 | 61 | function formatPublicKey(publicKey) { 62 | const header = "-----BEGIN PUBLIC KEY-----"; 63 | const footer = "-----END PUBLIC KEY-----"; 64 | 65 | return `${header}\n${publicKey}\n${footer}`; 66 | } 67 | } 68 | } 69 | 70 | Configuration.PARAMS = [ 71 | "host", 72 | "apiKey", 73 | "publicKey", 74 | "accessToken", 75 | "verifySSL", 76 | "timeout", 77 | "debugging", 78 | "userAgent", 79 | "origin", 80 | "securityCredential", 81 | "serviceProviderCode", 82 | "initiatorIdentifier", 83 | ]; 84 | 85 | export { Configuration }; 86 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | import { Operation } from "./operation.js"; 2 | import { Version } from "./version.js"; 3 | import { Environment } from "./environment.js"; 4 | 5 | const USER_AGENT = "MPesa"; 6 | const C2B_PAYMENT = "C2B_PAYMENT"; 7 | const B2B_PAYMENT = "B2B_PAYMENT"; 8 | const B2C_PAYMENT = "B2C_PAYMENT"; 9 | const REVERSAL = "REVERSAL"; 10 | const QUERY_TRANSACTION_STATUS = "QUERY_TRANSACTION_STATUS"; 11 | 12 | const VERSION = new Version(0, 1, 0); 13 | 14 | const HTTP = { 15 | METHOD: { 16 | GET: "get", 17 | PUT: "put", 18 | POST: "post", 19 | }, 20 | HEADERS: { 21 | USER_AGENT: "User-Agent", 22 | CONTENT_TYPE: "Content-Type", 23 | ORIGIN: "Origin", 24 | AUTHORIZATION: "Authorization", 25 | }, 26 | }; 27 | 28 | const SANDBOX = new Environment({ 29 | scheme: "https", 30 | domain: "api.sandbox.vm.co.mz", 31 | }); 32 | 33 | const PRODUCTION = new Environment({ 34 | scheme: "https", 35 | domain: "api.mpesa.vm.co.mz", 36 | }); 37 | 38 | const PATTERNS = { 39 | PHONE_NUMBER: /^((00|\+)?(258))?8[45][0-9]{7}$/, 40 | MONEY_AMOUNT: /^[1-9][0-9]*(\.[0-9]+)?$/, 41 | WORD: /^\w+$/, 42 | SERVICE_PROVIDER_CODE: /^[0-9]{5,6}$/, 43 | }; 44 | 45 | const OPERATIONS = { 46 | [C2B_PAYMENT]: new Operation({ 47 | method: HTTP.METHOD.POST, 48 | port: "18352", 49 | path: "/ipg/v1x/c2bPayment/singleStage/", 50 | mapping: { 51 | from: "input_CustomerMSISDN", 52 | to: "input_ServiceProviderCode", 53 | amount: "input_Amount", 54 | transaction: "input_TransactionReference", 55 | reference: "input_ThirdPartyReference", 56 | }, 57 | validation: { 58 | from: PATTERNS.PHONE_NUMBER, 59 | to: PATTERNS.SERVICE_PROVIDER_CODE, 60 | amount: PATTERNS.MONEY_AMOUNT, 61 | transaction: PATTERNS.WORD, 62 | reference: PATTERNS.WORD, 63 | }, 64 | required: ["to", "from", "amount", "transaction", "reference"], 65 | optional: ["from"], 66 | }), 67 | 68 | [B2B_PAYMENT]: new Operation({ 69 | method: HTTP.METHOD.POST, 70 | port: "18349", 71 | path: "/ipg/v1x/b2bPayment/", 72 | mapping: { 73 | from: "input_PrimaryPartyCode", 74 | to: "input_ReceiverPartyCode", 75 | amount: "input_Amount", 76 | transaction: "input_TransactionReference", 77 | reference: "input_ThirdPartyReference", 78 | }, 79 | validation: { 80 | from: PATTERNS.SERVICE_PROVIDER_CODE, 81 | to: PATTERNS.SERVICE_PROVIDER_CODE, 82 | amount: PATTERNS.MONEY_AMOUNT, 83 | transaction: PATTERNS.WORD, 84 | reference: PATTERNS.WORD, 85 | }, 86 | required: ["to", "from", "amount", "transaction", "reference"], 87 | optional: ["from"], 88 | }), 89 | 90 | [B2C_PAYMENT]: new Operation({ 91 | method: HTTP.METHOD.POST, 92 | port: "18345", 93 | path: "/ipg/v1x/b2cPayment/", 94 | mapping: { 95 | number: "input_CustomerMSISDN", 96 | to: "input_CustomerMSISDN", 97 | from: "input_ServiceProviderCode", 98 | amount: "input_Amount", 99 | transaction: "input_TransactionReference", 100 | reference: "input_ThirdPartyReference", 101 | }, 102 | validation: { 103 | from: PATTERNS.SERVICE_PROVIDER_CODE, 104 | to: PATTERNS.PHONE_NUMBER, 105 | amount: PATTERNS.MONEY_AMOUNT, 106 | transaction: PATTERNS.WORD, 107 | reference: PATTERNS.WORD, 108 | }, 109 | required: ["to", "from", "amount", "transaction", "reference"], 110 | optional: ["to"], 111 | }), 112 | 113 | [REVERSAL]: new Operation({ 114 | method: HTTP.METHOD.PUT, 115 | port: "18354", 116 | path: "/ipg/v1x/reversal/", 117 | mapping: { 118 | to: "input_ServiceProviderCode", 119 | amount: "input_Amount", 120 | reference: "input_ThirdPartyReference", 121 | transaction: "input_TransactionID", 122 | securityCredential: "input_SecurityCredential", 123 | initiatorIdentifier: "input_InitiatorIdentifier", 124 | }, 125 | validation: { 126 | to: PATTERNS.SERVICE_PROVIDER_CODE, 127 | amount: PATTERNS.MONEY_AMOUNT, 128 | reference: PATTERNS.WORD, 129 | transaction: PATTERNS.WORD, 130 | securityCredential: PATTERNS.WORD, 131 | initiatorIdentifier: PATTERNS.WORD, 132 | }, 133 | required: [ 134 | "to", 135 | "amount", 136 | "reference", 137 | "transaction", 138 | "securityCredential", 139 | "initiatorIdentifier", 140 | ], 141 | optional: ["to", "securityCredential", "initiatorIdentifier"], 142 | }), 143 | 144 | [QUERY_TRANSACTION_STATUS]: new Operation({ 145 | method: HTTP.METHOD.GET, 146 | port: "18353", 147 | path: "/ipg/v1x/queryTransactionStatus/", 148 | mapping: { 149 | from: "input_ServiceProviderCode", 150 | subject: "input_QueryReference", 151 | reference: "input_ThirdPartyReference", 152 | }, 153 | validation: { 154 | from: PATTERNS.SERVICE_PROVIDER_CODE, 155 | subject: PATTERNS.WORD, 156 | reference: PATTERNS.WORD, 157 | }, 158 | required: ["from", "subject", "reference"], 159 | optional: ["from"], 160 | }), 161 | }; 162 | 163 | const ERRORS = { 164 | VALIDATION: { 165 | code: 3000, 166 | description: "The data provider is not valid", 167 | }, 168 | MISSING: { 169 | code: 3001, 170 | description: "There are attributes missing to complete the transaction", 171 | }, 172 | AUTHENTICATION: { 173 | code: 3002, 174 | description: "There is not a public key and API key or access token set", 175 | }, 176 | INVALID_OPERATION: { 177 | code: 3003, 178 | descrition: "Unable to detect the operation from the destination number", 179 | }, 180 | INVALID_ENV: { 181 | code: 3003, 182 | descrition: "Unable to detect the base URL", 183 | }, 184 | }; 185 | 186 | export { 187 | ERRORS, 188 | PATTERNS, 189 | C2B_PAYMENT, 190 | B2B_PAYMENT, 191 | B2C_PAYMENT, 192 | REVERSAL, 193 | QUERY_TRANSACTION_STATUS, 194 | OPERATIONS, 195 | PRODUCTION, 196 | SANDBOX, 197 | VERSION, 198 | USER_AGENT, 199 | HTTP, 200 | }; 201 | -------------------------------------------------------------------------------- /src/environment.js: -------------------------------------------------------------------------------- 1 | class Environment { 2 | constructor(args) { 3 | if (args !== null && args !== undefined) { 4 | for (const key of Environment.defaultProperties) { 5 | if (Object.prototype.hasOwnProperty.call(args, key)) { 6 | this[key] = args[key]; 7 | } 8 | } 9 | } 10 | } 11 | 12 | toURL() { 13 | return `${this.scheme}://${this.domain}`; 14 | } 15 | 16 | /** 17 | * Creates environment object from a given URL 18 | * @param {string} url 19 | */ 20 | static fromURL(url) { 21 | let parts; 22 | if (/^https:\/\//.test(url) || /^http:\/\//.test(url)) { 23 | parts = url.split("://"); 24 | } else { 25 | parts = ["https", url]; 26 | } 27 | 28 | return new Environment({ 29 | scheme: parts[0], 30 | domain: parts[1], 31 | }); 32 | } 33 | 34 | isValid() { 35 | return this.scheme != null && this.domain != null; 36 | } 37 | } 38 | 39 | Environment.defaultProperties = ["name", "scheme", "domain"]; 40 | 41 | export { Environment }; 42 | -------------------------------------------------------------------------------- /src/errors.js: -------------------------------------------------------------------------------- 1 | class MissingPropertiesError extends Error { 2 | constructor(name, message) { 3 | super(); 4 | this.name = name || "MissingPropertiesError"; 5 | this.message = message || "There are attributes missing to complete the transaction"; 6 | } 7 | } 8 | 9 | class AuthenticationError extends Error { 10 | constructor(name, message) { 11 | super(); 12 | this.name = name || "AuthenticationError"; 13 | this.message = message || "There is not a public key and API key or access token set"; 14 | } 15 | } 16 | 17 | class InvalidReceiverError extends Error { 18 | constructor(name, message) { 19 | super(); 20 | this.name = name || "InvalidReceiverError"; 21 | this.message = 22 | message || 23 | "The receiver does not look like a valid phone number nor a valid service provider code"; 24 | } 25 | } 26 | 27 | class ValidationError extends Error { 28 | constructor(name, message) { 29 | super(); 30 | this.name = name || "ValidationError"; 31 | this.message = message || "The data provider is not valid"; 32 | } 33 | } 34 | 35 | class InvalidHostError extends Error { 36 | constructor(name, message) { 37 | super(); 38 | this.name = name || "InvalidHostError"; 39 | this.message = message || "The provider host is not valid"; 40 | } 41 | } 42 | 43 | class TimeoutError extends Error { 44 | constructor(name, message) { 45 | super(); 46 | this.name = name || "TimeoutError"; 47 | this.message = message || "The request has taken more time than allowed"; 48 | } 49 | } 50 | 51 | export { 52 | MissingPropertiesError, 53 | AuthenticationError, 54 | InvalidReceiverError, 55 | ValidationError, 56 | InvalidHostError, 57 | TimeoutError, 58 | }; 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { Environment } from "./environment.js"; 2 | import { Operation } from "./operation.js"; 3 | import { Configuration } from "./configuration.js"; 4 | import { Client } from "./client.js"; 5 | import { Service } from "./service.js"; 6 | import { TimeoutError } from "./errors.js"; 7 | 8 | export { Environment, Operation, Configuration, Client, Service, TimeoutError }; 9 | -------------------------------------------------------------------------------- /src/operation.js: -------------------------------------------------------------------------------- 1 | class Operation { 2 | constructor(args) { 3 | if (args !== null && args !== undefined) { 4 | for (const key of Operation.defaultProperties) { 5 | if (Object.prototype.hasOwnProperty.call(args, key)) { 6 | this[key] = args[key]; 7 | } 8 | } 9 | } 10 | } 11 | 12 | toURL() { 13 | if (this.isValid()) { 14 | const pathWithoudSlash = this.path.replace(/^\/+/, ""); 15 | return `:${this.port}/${pathWithoudSlash}`; 16 | } 17 | } 18 | 19 | isValid() { 20 | return this.name != null && this.port != null && this.path != null; 21 | } 22 | } 23 | 24 | Operation.defaultProperties = [ 25 | "name", 26 | "method", 27 | "port", 28 | "path", 29 | "mapping", 30 | "validation", 31 | "required", 32 | "optional", 33 | ]; 34 | 35 | export { Operation }; 36 | -------------------------------------------------------------------------------- /src/response.js: -------------------------------------------------------------------------------- 1 | export class Response { 2 | constructor(status, code, description, data) { 3 | this.status = status; 4 | this.code = code; 5 | this.description = description; 6 | this.data = data; 7 | 8 | if (this.status >= 100 && this.status < 300) { 9 | this.success = true; 10 | } else { 11 | this.success = false; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/service.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | import { Configuration } from "./configuration.js"; 4 | // import { Response } from "./response.js"; 5 | import { 6 | ValidationError, 7 | AuthenticationError, 8 | InvalidReceiverError, 9 | MissingPropertiesError, 10 | InvalidHostError, 11 | // TimeoutError 12 | } from "./errors.js"; 13 | 14 | import { 15 | OPERATIONS, 16 | PATTERNS, 17 | C2B_PAYMENT, 18 | B2C_PAYMENT, 19 | B2B_PAYMENT, 20 | REVERSAL, 21 | QUERY_TRANSACTION_STATUS, 22 | // ERRORS, 23 | HTTP, 24 | } from "./constants.js"; 25 | 26 | export class Service { 27 | constructor(args) { 28 | this.initDefaultConfigs(args); 29 | } 30 | 31 | /** 32 | * Initializes default configurations 33 | * @param {Object} args 34 | */ 35 | initDefaultConfigs(args) { 36 | this.config = new Configuration(args); 37 | } 38 | 39 | /** 40 | * 41 | * @param {Object} intent 42 | */ 43 | handleSend(intent) { 44 | const opcode = this.detectOperation(intent); 45 | 46 | return this.handleRequest(opcode, intent); 47 | } 48 | 49 | handleReceive(intent) { 50 | return this.handleRequest(C2B_PAYMENT, intent); 51 | } 52 | 53 | handleRevert(intent) { 54 | return this.handleRequest(REVERSAL, intent); 55 | } 56 | 57 | handleQuery(intent) { 58 | return this.handleRequest(QUERY_TRANSACTION_STATUS, intent); 59 | } 60 | 61 | /** 62 | * Validates transaction data and performs performs the needed HTTP request 63 | * @param {string} opcode 64 | * @param {Object.} intent 65 | */ 66 | handleRequest(opcode, intent) { 67 | const data = this.fillOptionalProperties(opcode, intent); 68 | 69 | const missingProperties = this.detectMissingProperties(opcode, intent); 70 | if (missingProperties.length > 0) { 71 | throw new MissingPropertiesError(); 72 | } 73 | 74 | const validationErrors = this.detectErrors(opcode, data); 75 | 76 | if (validationErrors.length > 0) { 77 | throw new ValidationError(); 78 | } 79 | 80 | return this.performRequest(opcode, intent); 81 | } 82 | 83 | /** 84 | * Detects the operation from the transaction data 85 | * @param {Object.} intent 86 | */ 87 | detectOperation(intent) { 88 | if (Object.prototype.hasOwnProperty.call(intent, "to")) { 89 | if (PATTERNS.PHONE_NUMBER.test(intent.to)) { 90 | return B2C_PAYMENT; 91 | } 92 | 93 | if (PATTERNS.SERVICE_PROVIDER_CODE.test(intent.to)) { 94 | return B2B_PAYMENT; 95 | } 96 | } 97 | 98 | throw new InvalidReceiverError(); 99 | } 100 | 101 | /** 102 | * Detect validation errors from thransaction data 103 | * @param {string} opcode 104 | * @param {Object.} intent 105 | */ 106 | detectErrors(opcode, intent) { 107 | const operations = OPERATIONS[opcode]; 108 | 109 | const errors = operations.required.filter((e) => { 110 | const pattern = operations.validation[e]; 111 | return !pattern.test(intent[e]); 112 | }); 113 | 114 | return errors; 115 | } 116 | 117 | /** 118 | * Detects missing properties from transaction data 119 | * @param {string} opcode 120 | * @param {Object.} data 121 | */ 122 | detectMissingProperties(opcode, data) { 123 | const { required } = OPERATIONS[opcode]; 124 | 125 | const missing = required.filter((e) => { 126 | return !Object.prototype.hasOwnProperty.call(data, e); 127 | }); 128 | 129 | return missing; 130 | } 131 | 132 | /** 133 | * Complete transaction data from configuration data if it is not already provided 134 | * @param {string} opcode 135 | * @param {Object.} intent 136 | */ 137 | fillOptionalProperties(opcode, intent) { 138 | const self = this; 139 | 140 | function map(correspondences) { 141 | for (const k in correspondences) { 142 | if ( 143 | !Object.prototype.hasOwnProperty.call(intent, k) && 144 | Object.prototype.hasOwnProperty.call(self.config, correspondences[k]) 145 | ) { 146 | intent[k] = self.config[correspondences[k]]; 147 | } 148 | } 149 | 150 | return intent; 151 | } 152 | 153 | switch (opcode) { 154 | case C2B_PAYMENT: 155 | return map({ to: "serviceProviderCode" }); 156 | 157 | case B2C_PAYMENT: 158 | case B2B_PAYMENT: 159 | return map({ from: "serviceProviderCode" }); 160 | 161 | case REVERSAL: 162 | return map({ 163 | initiatorIdentifier: "initiatorIdentifier", 164 | securityCredential: "securityCredential", 165 | to: "serviceProviderCode", 166 | }); 167 | 168 | case QUERY_TRANSACTION_STATUS: 169 | return map({ 170 | from: "serviceProviderCode", 171 | }); 172 | } 173 | 174 | return intent; 175 | } 176 | 177 | /** 178 | * Formats transaction data to the format required by M-Pesa API 179 | * @param {string} opcode 180 | * @param {Object.} intent 181 | */ 182 | buildRequestBody(opcode, intent) { 183 | const body = {}; 184 | for (const oldKey in intent) { 185 | const newKey = OPERATIONS[opcode].mapping[oldKey]; 186 | if ( 187 | (opcode === C2B_PAYMENT && oldKey === "from") || 188 | (opcode === B2C_PAYMENT && oldKey == "to") 189 | ) { 190 | body[newKey] = this.normalizePhoneNumber(intent[oldKey]); 191 | } else { 192 | body[newKey] = intent[oldKey]; 193 | } 194 | } 195 | 196 | return body; 197 | } 198 | 199 | /** 200 | * Generates HTTP headers required to perform the request 201 | * @param {string} opcode 202 | * @param {Object.} intent 203 | */ 204 | buildRequestHeaders(opcode, intent) { 205 | const headers = { 206 | [HTTP.HEADERS.USER_AGENT]: this.config.userAgent, 207 | [HTTP.HEADERS.ORIGIN]: this.config.origin, 208 | [HTTP.HEADERS.CONTENT_TYPE]: "application/json", 209 | [HTTP.HEADERS.AUTHORIZATION]: `Bearer ${this.config.auth}`, 210 | }; 211 | 212 | return headers; 213 | } 214 | 215 | performRequest(opcode, intent) { 216 | this.generateAccessToken(); 217 | 218 | if (Object.prototype.hasOwnProperty.call(this.config, "environment")) { 219 | if (Object.prototype.hasOwnProperty.call(this.config, "auth")) { 220 | const operation = OPERATIONS[opcode]; 221 | const headers = this.buildRequestHeaders(opcode, intent); 222 | const body = this.buildRequestBody(opcode, intent); 223 | 224 | const requestData = { 225 | baseURL: `${this.config.environment.scheme}://${this.config.environment.domain}:${operation.port}`, 226 | insecureHTTPParser: true, 227 | url: operation.path, 228 | method: operation.method, 229 | path: operation.path, 230 | headers, 231 | timeout: this.config.timeout * 1000, 232 | maxRedirects: 0, 233 | }; 234 | 235 | if (operation.method === HTTP.METHOD.GET) { 236 | requestData.params = body; 237 | } else { 238 | requestData.data = body; 239 | } 240 | 241 | const self = this; 242 | return axios(requestData) 243 | .then((r) => { 244 | return Promise.resolve(self.buildResponse(r)); 245 | }) 246 | .catch((e) => { 247 | return Promise.reject(self.buildResponse(e)); 248 | }); 249 | } 250 | 251 | throw new AuthenticationError(); 252 | } else { 253 | throw new InvalidHostError(); 254 | } 255 | } 256 | 257 | /** 258 | * Formats the result 259 | * @param {*} result 260 | */ 261 | buildResponse(result) { 262 | if (result.status >= 200 && result.status < 300) { 263 | return { 264 | response: { 265 | status: result.status, 266 | code: result.data.output_ResponseCode, 267 | desc: result.data.output_ResponseDesc, 268 | }, 269 | conversation: result.data.output_ConversationID, 270 | transaction: result.data.output_TransactionID, 271 | reference: result.data.output_ThirdPartyReference, 272 | }; 273 | } 274 | return { 275 | response: { 276 | status: result.response.status, 277 | statusText: result.response.statusText, 278 | outputError: result.response.data.output_error, 279 | }, 280 | }; 281 | } 282 | 283 | /** 284 | * Generates access token from public key and API key pair 285 | */ 286 | generateAccessToken() { 287 | this.config.generateAccessToken(); 288 | } 289 | 290 | normalizePhoneNumber(phoneNumber) { 291 | const phoneNumberCountryCode = /^((?(00?|\+)?258)?)(?8[54][0-9]{7})$/; 292 | 293 | return `258${phoneNumber.match(phoneNumberCountryCode).groups.localNumber}`; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/version.js: -------------------------------------------------------------------------------- 1 | export class Version { 2 | constructor(major, minor, patch) { 3 | this.major = major; 4 | this.minor = minor; 5 | this.patch = patch; 6 | } 7 | 8 | toString() { 9 | return `${this.major}.${this.minor}.${this.patch}`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/paymentsds/mpesa-js-sdk/91c7254a740af8aa747bcd1e4e90879cdd696140/test/.keep -------------------------------------------------------------------------------- /test/client.js: -------------------------------------------------------------------------------- 1 | import { Client } from '../src/client.js' 2 | import chai from 'chai' 3 | import sinon from 'sinon' 4 | import dotenv from 'dotenv' 5 | 6 | dotenv.config(); 7 | 8 | const expect = chai.expect; 9 | 10 | const client = new Client({ 11 | apiKey: process.env.API_KEY, // API Key 12 | publicKey: process.env.PUBLIC_KEY, // Public Key 13 | serviceProviderCode: process.env.SERVICE_PROVIDER_CODE // input_ServiceProviderCode 14 | }); 15 | 16 | const clientRevert = new Client({ 17 | apiKey: process.env.API_KEY, // API Key 18 | publicKey: process.env.PUBLIC_KEY, // Public Key 19 | serviceProviderCode: process.env.SERVICE_PROVIDER_CODE, // input_ServiceProviderCode, 20 | initiatorIdentifier: process.env.INITIATOR_IDENTIFIER, // input_InitiatorIdentifier, 21 | securityCredential: process.env.SECURITY_CREDENTIAL // input_SecurityCredential 22 | }); 23 | 24 | const paymentDataReceive = { 25 | from: process.env.PHONE_NUMBER, // input_CustomerMSISDN 26 | reference: '11114' + Math.floor(Math.random()*100), // input_ThirdPartyReference 27 | transaction: 'T12344CC', // input_TransactionReference 28 | amount: '10' // input_Amount 29 | }; 30 | 31 | const paymentDataSend = { 32 | to: process.env.PHONE_NUMBER, // input_CustomerMSISDN 33 | reference: '11114' + Math.floor(Math.random()*100), // input_ThirdPartyReference 34 | transaction: 'T12344CC', // input_TransactionReference 35 | amount: '10' // input_Amount 36 | }; 37 | 38 | const reversionData = { 39 | reference: '11114' + Math.floor(Math.random()*100), // input_ThirdPartyReference 40 | transaction: 'T12344CC', // input_TransactionID 41 | amount: '10' // input_ReversalAmount 42 | }; 43 | 44 | const paymentDataBusiness = { 45 | to: '979797', // input_ReceiverPartyCode 46 | reference: '11114' + Math.floor(Math.random()*100), // input_ThirdPartyReference 47 | transaction: 'T12344CC', // input_TransactionReference 48 | amount: '10' // input_Amount 49 | }; 50 | 51 | describe("Receive Money from a Mobile Account", function(){ 52 | this.timeout(30000); 53 | it("Receive Money successful", function(done){ 54 | client.receive(paymentDataReceive).then(r => { 55 | expect(r.response.status).to.be.within(200, 201); 56 | done(); 57 | }).catch(e =>{ 58 | done(new Error("test case failed: " + e.response.status)); 59 | }); 60 | }); 61 | }); 62 | 63 | describe("Send Money to a Mobile Account", function(){ 64 | this.timeout(30000); 65 | it("Send Money successful", function(done){ 66 | client.send(paymentDataSend).then(r => { 67 | expect(r.response.status).to.be.within(200, 201); 68 | done(); 69 | }).catch(e =>{ 70 | done(new Error("test case failed: " + e.response.outputError)); 71 | }); 72 | }); 73 | }); 74 | 75 | 76 | describe("Send Money to a Business Account ", function(done){ 77 | this.timeout(30000); 78 | it("Send Money successful", function(done){ 79 | client.send(paymentDataBusiness).then(r => { 80 | expect(r.response.status).to.be.within(200, 201); 81 | done(); 82 | }).catch(e =>{ 83 | done(new Error("test case failed: " + e.response.outputError)); 84 | }); 85 | }); 86 | }); 87 | 88 | describe("Revert a Transaction ", function(){ 89 | it("Revert a Transaction successful", function(done){ 90 | clientRevert.revert(reversionData).then(function(r) { 91 | expect(r.response.status).to.be.within(200, 201); 92 | done(); 93 | }).catch(function(e) { 94 | done(new Error("test case failed: " + e.response.outputError)); 95 | }); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /test/configuration.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { Configuration } from '../src/configuration.js' 3 | 4 | describe('Configuration', function () { 5 | describe('#toURL()', function () { 6 | const config = new Configuration({ 7 | apiKey: '123456789', 8 | publicKey: '123456789', 9 | origin: '*', 10 | userAgent: 'MPesa', 11 | host: 'api.mpesa.vm.co.mz' 12 | }) 13 | 14 | it('should auto-complete scheme', function () { 15 | assert.equal(config.environment.toURL(), 'https://api.mpesa.vm.co.mz') 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/environment.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { Environment } from '../src/index.js' 3 | 4 | describe('Environment', function () { 5 | describe('#fromURL()', function () { 6 | it('should parse url without error', function () { 7 | const environment = Environment.fromURL('example.com') 8 | assert.equal(environment.scheme, 'https') 9 | assert.equal(environment.domain, 'example.com') 10 | }) 11 | }) 12 | describe('#toURL()', function () { 13 | it('should complete scheme', function () { 14 | const environment = Environment.fromURL('example.com') 15 | assert.equal(environment.toURL(), 'https://example.com') 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/operation.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { Operation } from '../src/index.js' 3 | 4 | describe('Operation', function () { 5 | }) 6 | -------------------------------------------------------------------------------- /test/service.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import { Service } from '../src/index.js' 3 | 4 | describe('Service', function () { 5 | const service = new Service(); 6 | 7 | describe('#handleSend', function () {}) 8 | describe('#handleReceive', function () {}) 9 | describe('#handleRevert', function () {}) 10 | describe('#handleQuery', function () {}) 11 | describe('#normalizePhoneNumber', function() { 12 | 13 | const phoneNumber1 = '841234567'; 14 | const phoneNumber2 = '00258841234567'; 15 | const phoneNumber3 = '+258841234567'; 16 | const phoneNumber4 = '0258841234567'; 17 | 18 | it('Should append `258` prefix to phone number', function() { 19 | assert.equal('258841234567', service.normalizePhoneNumber(phoneNumber1)); 20 | }); 21 | 22 | it('Should remove `00` prefix to phone number', function() { 23 | assert.equal('258841234567', service.normalizePhoneNumber(phoneNumber2)); 24 | }); 25 | 26 | it('Should remove `+` prefix to phone number', function() { 27 | assert.equal('258841234567', service.normalizePhoneNumber(phoneNumber3)); 28 | }); 29 | 30 | it('Should remove `0` prefix to phone number', function() { 31 | assert.equal('258841234567', service.normalizePhoneNumber(phoneNumber4)); 32 | }); 33 | }) 34 | }) 35 | --------------------------------------------------------------------------------