├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── develop-pr.yml │ ├── develop.yml │ ├── main-pr.yml │ ├── main.yml │ └── publish.yml ├── .gitignore ├── .husky ├── .gitignore ├── pre-commit └── prepare-commit-msg ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.json ├── package-lock.json ├── package.json ├── src ├── index.ts ├── modules │ ├── azampay.ts │ └── instances.ts └── shared │ ├── enums │ └── azampay.enum.ts │ ├── helpers │ └── error.helper.ts │ └── interfaces │ └── base.interface.ts ├── test ├── constants │ └── instance.constants.ts ├── helper.spec.ts └── instance.spec.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | src/types/global.d.ts -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": ["@typescript-eslint", "node", "prettier"], 5 | "parserOptions": { 6 | "sourceType": "module", 7 | "tsconfigRootDir": "./", 8 | "project": ["./tsconfig.json"] 9 | }, 10 | "extends": [ 11 | "eslint:recommended", 12 | "plugin:node/recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 16 | "plugin:prettier/recommended" 17 | ], 18 | "rules": { 19 | "prettier/prettier": "warn", 20 | "node/no-missing-import": "off", 21 | "node/no-empty-function": "off", 22 | "node/no-unsupported-features/es-syntax": "off", 23 | "node/no-missing-require": "off", 24 | "node/shebang": "off", 25 | "@typescript-eslint/no-use-before-define": "off", 26 | "quotes": ["warn", "single", { "avoidEscape": true }], 27 | "node/no-unpublished-import": "off", 28 | "@typescript-eslint/no-unsafe-assignment": "off", 29 | "@typescript-eslint/no-var-requires": "off", 30 | "@typescript-eslint/ban-ts-comment": "off", 31 | "@typescript-eslint/no-explicit-any": "off" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the repository to show as TypeScript rather than JS in GitHub 2 | *.js linguist-detectable=false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐛 Bug Report" 3 | about: Report a reproducible bug or regression. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Current Behavior 11 | 12 | 13 | 14 | ## Expected Behavior 15 | 16 | 17 | 18 | ## Steps to Reproduce the Problem 19 | 20 | 1. 21 | 1. 22 | 1. 23 | 24 | ## Environment 25 | 26 | - Version: 27 | - Platform: 28 | - Node.js Version: 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature request 3 | about: Suggest an amazing new idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | --- 8 | 9 | ## Feature Request 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | 13 | 14 | 15 | **Describe the solution you'd like** 16 | 17 | 18 | 19 | **Describe alternatives you've considered** 20 | 21 | 22 | 23 | ## Are you willing to resolve this issue by submitting a Pull Request? 24 | 25 | 28 | 29 | - [ ] Yes, I have the time, and I know how to start. 30 | - [ ] Yes, I have the time, but I don't know how to start. I would need guidance. 31 | - [ ] No, I don't have the time, although I believe I could do it if I had the time... 32 | - [ ] No, I don't have the time and I wouldn't even know how to start. 33 | 34 | 37 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ### Description of change 9 | 10 | 23 | 24 | ### Pull-Request Checklist 25 | 26 | 31 | 32 | - [ ] Code is up-to-date with the `main` branch 33 | - [ ] `npm run lint` passes with this change 34 | - [ ] `npm run test` passes with this change 35 | - [ ] This pull request links relevant issues as `Fixes #0000` 36 | - [ ] There are new or updated unit tests validating the change 37 | - [ ] Documentation has been updated to reflect this change 38 | - [ ] The new commits follow conventions outlined in the [conventional commit spec](https://www.conventionalcommits.org/en/v1.0.0/) 39 | 40 | 43 | -------------------------------------------------------------------------------- /.github/workflows/develop-pr.yml: -------------------------------------------------------------------------------- 1 | name: CREATE DEVELOP PR 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [15.x] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: 🔀 16 | uses: BaharaJr/create-pr@0.0.1 17 | with: 18 | GITHUB_TOKEN: ${{secrets.TOKEN}} 19 | DESTINATION_BRANCH: develop 20 | KEYWORD: release 21 | -------------------------------------------------------------------------------- /.github/workflows/develop.yml: -------------------------------------------------------------------------------- 1 | name: RELEASE 2 | on: 3 | pull_request: 4 | branches: [develop] 5 | 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: 🚚 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: 🧱 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: lts/* 21 | 22 | - name: ➕ 23 | run: npm ci 24 | 25 | - name: ✅ 26 | run: npm test 27 | 28 | - name: 💚 29 | run: npm run build -------------------------------------------------------------------------------- /.github/workflows/main-pr.yml: -------------------------------------------------------------------------------- 1 | name: CREATE RELEASE PR 2 | 3 | on: 4 | push: 5 | branches: [develop] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [15.x] 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: 🔀 17 | uses: BaharaJr/create-pr@0.0.1 18 | with: 19 | GITHUB_TOKEN: ${{secrets.TOKEN}} 20 | DESTINATION_BRANCH: main 21 | KEYWORD: build 22 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: RELEASE 2 | on: 3 | pull_request: 4 | branches: [main] 5 | 6 | jobs: 7 | release: 8 | name: Release 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: 🚚 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: 🧱 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: lts/* 21 | 22 | - name: ➕ 23 | run: npm ci 24 | 25 | - name: ✅ 26 | run: npm test 27 | 28 | - name: 💚 29 | run: npm run build 30 | 31 | - name: 🚀 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 34 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | run: npx semantic-release 36 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - name: set global attributes 15 | run: | 16 | git config --global user.email "neicoreadams@gmail.com" 17 | git config --global user.name "neicore" 18 | git remote set-url origin https://x-access-token:${{ secrets.TOKEN }}@github.com/${{ github.repository }} 19 | - name: Setup Node 20 | uses: actions/setup-node@v2 21 | with: 22 | node-version: '14.x' 23 | registry-url: 'https://registry.npmjs.org' 24 | - name: Install dependencies and build 🔧 25 | run: npm install && npm run prebuild && npm run build 26 | - name: Publish package on NPM 📦 27 | run: npm publish --access public 28 | env: 29 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 30 | # update version 31 | - name: install jq 32 | run: | 33 | sudo apt-get update 34 | sudo apt-get -y install jq 35 | 36 | - name: get version 37 | run: | 38 | PACKAGE_VERSION=$(cat ./package.json | jq '.version' | tr -d '"') 39 | echo "::set-output name=PACKAGE_VERSION::$(cat ./package.json | jq '.version' | tr -d '"')" 40 | id: version 41 | - name: bump-version 42 | id: bump_version 43 | uses: flexcodelabs/bump-version@0.0.2 44 | with: 45 | GITHUB_TOKEN: ${{secrets.TOKEN}} 46 | PACKAGE_VERSION: ${{ steps.version.outputs.PACKAGE_VERSION }} 47 | DELETE_BRANCH: false 48 | CHANGELOG_PATH: ./CHANGELOG.md 49 | PACKAGE_JSON_PATH: ./package.json 50 | # Commit and push the latest version 51 | - name: update main branch 52 | run: | 53 | git add ./package.json 54 | git add ./CHANGELOG.md 55 | git commit -m "Skip CI" 56 | git push 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env.test 73 | .env.local 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | out 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and not Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | 108 | # Stores VSCode versions used for testing VSCode extensions 109 | .vscode-test 110 | 111 | # yarn v2 112 | .yarn/cache 113 | .yarn/unplugged 114 | .yarn/build-state.yml 115 | .yarn/install-state.gz 116 | .pnp.* 117 | 118 | # Compiled code 119 | lib/ 120 | 121 | .env 122 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | exec 9 | 2.1 [Get Token](#get-token)
10 | 2.2 [Bank Checkout](#bank-checkout)
11 | 2.3 [Mno Checkout](#mno-checkout)
12 | 2.4 [Disbursement](#disbursement)
13 | 2.5 [Name Lookup](#name-lookup)
14 | 2.6 [Transaction Status](#transaction-status)
15 | 2.7 [Post Checkout](#post-checkout)
16 | 2.7 [Payment Partners](#payment-partners) 17 | 18 |
19 | 20 | # Installation and Use 21 | 22 | From terminal in the root directory of your project, run 23 | 24 | ```sh 25 | 26 | npm i azampay 27 | 28 | ``` 29 | 30 |
31 | 32 | # Use 33 | 34 |
35 | ES6 Import 36 | 37 | ```JS 38 | 39 | import azampay from 'azampay' 40 | 41 | ``` 42 | 43 |
44 | 45 |
46 | Module Require 47 | 48 | ```JS 49 | 50 | const azampay = require('azampay').default 51 | 52 | ``` 53 | 54 |
55 | 56 | - The `import` or `require` from `azampay` package exports two properties. 57 | 58 | 1. `getToken` method that is used to retrieve access token from Azam Pay 59 | 2. `instance` a class that can be used or extended to add functionalities to the `azampay` package 60 | 61 | ## Get Token 62 | 63 | `Requirement Definition` 64 | 65 | | Property | Description | Type | Required | 66 | | ------------ | -------------------------------------------------------------------- | ------ | -------- | 67 | | env | Enum: SANDBOX \| LIVE. Azampay environment. Default SANDBOX | String | [ ] | 68 | | clientId | It will be the client id generated during application registration. | String | [x] | 69 | | appName | It will be the name of application. | String | [x] | 70 | | clientSecret | It will be the secret key generated during application registration. | String | [x] | 71 | | apiKey | Azam Pay API key given as token in the settings page. | String | [x] | 72 | 73 | `Request Payload` 74 | 75 | ```JSON 76 | 77 | { 78 | "env": "string", 79 | "clientId": "string", 80 | "appName": "string", 81 | "clientSecret": "string", 82 | "apiKey": "string", 83 | } 84 | 85 | ``` 86 | 87 | `Request Method` 88 | 89 | ```JS 90 | 91 | const token = await azampay.getToken(payload); 92 | 93 | ``` 94 | 95 | `Response` 96 | 97 | If successful, the response will be of type [TokenResponse](#token-response) or [ErrorResponse](#error-response) 98 | 99 | ```JS 100 | 101 | { 102 | data: { accessToken: string; expire: string }; 103 | success: boolean; 104 | message: string; 105 | code: string | number; 106 | statusCode: number; 107 | bankCheckout: ( 108 | payload: BankCheckout, 109 | options: RequestOptions 110 | ) => Promise; 111 | 112 | mnoCheckout: ( 113 | payload: MnoCheckout, 114 | options: RequestOptions 115 | ) => Promise; 116 | 117 | postCheckout: ( 118 | payload: PostCheckOut, 119 | options: RequestOptions 120 | ) => Promise; 121 | 122 | disburse: ( 123 | payload: Disburse, 124 | options: RequestOptions 125 | ) => Promise; 126 | 127 | transactionStatus: ( 128 | payload: TransactionStatus, 129 | options: RequestOptions 130 | ) => Promise; 131 | 132 | nameLookup: ( 133 | payload: NameLookup, 134 | options: RequestOptions 135 | ) => Promise; 136 | } 137 | 138 | ``` 139 | 140 | `Response Definition` 141 | 142 | | Property | Description | Type | 143 | | ----------------- | ---------------------------------------------------------------------------------------- | ---------------- | 144 | | data | Azam Pay respnse with access token and expire time | Object | 145 | | success | A `true` boolean value indicating that the request was successfull | Boolean | 146 | | message | Response message | String | 147 | | code | Response code. | Number \| String | 148 | | statusCode | Response Http Status code. Possibly `200` or `201` | Number | 149 | | bankCheckout | A method to initiate a Bank checkout with Azam Pay. | Method | 150 | | mnoCheckout | A method to initiate MNO checkout with Azam Pay. | Method | 151 | | disburse | A method used to lookup the name associated with a bank account or Mobile Money account | Method | 152 | | transactionStatus | A method used to retrieve the status of a disbursement transaction made through AzamPay. | Method | 153 | | nameLookup | A method used to lookup the name associated with a bank account or Mobile Money account. | Method | 154 | 155 |
156 | 157 | # Bank Checkout 158 | 159 | Initiating a bank checkout, we can use two methods, one from the token results method and the other from the Azam Pay instance exported from the azampay package. 160 | 161 | Bank checkout method takes two arguments, the first with the request payload for Azam Pay bank checkout and second is [optional with additional options](#request-options). 162 | 163 | `Request Payload` 164 | 165 | ```JS 166 | 167 | { 168 | 169 | amount: string; 170 | currencyCode: string; 171 | merchantAccountNumber: string; 172 | merchantMobileNumber: string; 173 | merchantName?: string | null; 174 | otp: string; 175 | provider: 'CRDB' | 'NMB' | 176 | referenceId: string; 177 | additionalProperties?: Record | null; 178 | } 179 | 180 | ``` 181 | 182 | 1. `Request Payload Definition` 183 | 184 | | Property | Definition | Type | 185 | | --------------------- | ---------------------------------------------------------------------------------------------------------- | ------ | 186 | | amount | This is amount that will be charged from the given account. | String | 187 | | currencyCode | Code of currency | String | 188 | | merchantAccountNumber | This is the account number/MSISDN that consumer will provide. The amount will be deducted from this. | String | 189 | | merchantMobileNumber | Mobile number | String | 190 | | merchantName | Nullable consumer name. | String | 191 | | otp | One time password | String | 192 | | provider | Enum: CRDB \| NMB BankProvider. | String | 193 | | referenceId | This id belongs to the calling application. Maximum Allowed length for this field is 128 ascii characters. | String | 194 | | additionaProperties | This is additional JSON data that calling application can provide. This is optional. | Object | 195 | 196 | 2. `Request Options Definition` 197 | 198 | - These are optional if you use the method from the response in [getToken](#get-token) otherwise mandatory if using the instance and the options were not passed during instantiation. 199 | 200 | `Method` 201 | 202 |
203 | Request from Token Response 204 | 205 | ```JS 206 | 207 | await token.bankCheckout(payload, options) 208 | 209 | ``` 210 | 211 |
212 | 213 |
214 | Request from Instance 215 | 216 | ```JS 217 | 218 | const instance = new azampay.instance({accessToken: token.data.accessToken,apiKey: 'YOUR API KEY'}) 219 | await instance.bankCheckout(payload, options) 220 | 221 | ``` 222 | 223 |
224 | 225 | `Response` 226 | 227 | ```JS 228 | { 229 | transactionId: string, 230 | message: string, 231 | succcess: true 232 | } 233 | 234 | ``` 235 | 236 | # 237 | 238 | NB: Every response has a `success` property denoting whether the request was successful or not and if it wasn't, the response will be of type [Error Response](#error-response) 239 | 240 |
241 | 242 | ## MNO Checkout 243 | 244 | For request method explanation, refer to [Bank Checkout](#bank-checkout)'s explanation. 245 | 246 | 1. `Request Payload Definition` 247 | 248 | | Property | Definition | Type | 249 | | ------------------- | ------------------------------------------------------------------------------------------------------------ | ------ | 250 | | amount | This is amount that will be charged from the given account. | String | 251 | | currencyCode | Code of currency | String | 252 | | accountNumber | This is the account number/MSISDN that consumer will provide. The amount will be deducted from this account. | String | 253 | | provider | Enum: Airtel \| Tigo \| Halopesa \| Azampesa \| Mpesa. | String | 254 | | externalId | This id belongs to the calling application. Maximum Allowed length for this field is 128 ascii characters. | String | 255 | | additionaProperties | This is additional JSON data that calling application can provide. This is optional. | Object | 256 | 257 | ```JS 258 | 259 | { 260 | accountNumber: string; 261 | amount: string; 262 | currency: string; 263 | externalId: string; 264 | provider: 'Airtel' | 'Tigo' | 'Halopesa' | 'Azampesa' | 'Mpesa' | string; 265 | additionalProperties?: Record | null; 266 | } 267 | 268 | ``` 269 | 270 | `Method` 271 | 272 |
273 | Request from Token Response 274 | 275 | ```JS 276 | 277 | await token.mnoCheckout(payload, options) 278 | 279 | ``` 280 | 281 |
282 | 283 |
284 | Request from Instance 285 | 286 | ```JS 287 | 288 | const instance = new azampay.instance({accessToken: token.data.accessToken,apiKey: 'YOUR API KEY'}) 289 | await instance.mnoCheckout(payload, options) 290 | 291 | ``` 292 | 293 |
294 | 295 | `Response` 296 | The response is the same as the [Bank Checkout](#bank-checkout)'s 297 | 298 |
299 | 300 | ## Disbursement 301 | 302 | This method allows for the transfer of money from other countries to Tanzania. It requires the authorization token generated above, passed as a header in the request. The request should also contain details of the source, destination, and transfer details. Additionally, the request can include an external reference ID and remarks. 303 | 304 | This method takes two arguments, mandatory payload and the other optional options as explained in [Bank Checkout](#bank-checkout) 305 | 306 | `Payload` 307 | 308 | ```JS 309 | 310 | { 311 | source: Source; 312 | destination: Destination; 313 | transferDetails: TransferDetails; 314 | externalReferenceId: string; 315 | remarks: string; 316 | } 317 | 318 | ``` 319 | 320 | | Property | Description | Type | 321 | | ------------------- | --------------------------------------------------- | -------------------------------- | 322 | | source | Contains information about the source account. | [Object](#source-or-destination) | 323 | | destination | Contains information about the destination account. | [Object](#source-or-destination) | 324 | | transferDetails | Contains information about the transfer. | [Object](#transfer-details) | 325 | | externalReferenceId | An external reference ID to track the transaction. | String | 326 | | remarks | Any remarks to be included with the transaction. | String | 327 | 328 | `Method` 329 | 330 |
331 | Request from Token Response 332 | 333 | ```JS 334 | 335 | await token.disburse(payload, options) 336 | 337 | ``` 338 | 339 |
340 | 341 |
342 | Request from Instance 343 | 344 | ```JS 345 | 346 | const instance = new azampay.instance({accessToken: token.data.accessToken,apiKey: 'YOUR API KEY'}) 347 | await instance.disburse(payload, options) 348 | 349 | ``` 350 | 351 |
352 | 353 | `Response` 354 | 355 | ```JS 356 | 357 | { 358 | data: string; 359 | message: string; 360 | success: boolean; 361 | statusCode: number; 362 | } 363 | 364 | ``` 365 | 366 | `Description` 367 | 368 | | Property | Description | Type | 369 | | ---------- | --------------------------------------------------------------------- | ------- | 370 | | data | A string containing the status of the transaction. | String | 371 | | message | A string containing a human-readable message describing the response. | String | 372 | | success | A boolean indicating whether the request was successful or not. | Boolean | 373 | | statusCode | An integer indicating the status code of the response. | Number | 374 | 375 |
376 | 377 | ## Name Lookup 378 | 379 | This method is used to lookup the name associated with a bank account or Mobile Money account. 380 | 381 | This method takes two arguments, mandatory payload and the other optional options as explained in [Bank Checkout](#bank-checkout) 382 | 383 | `Payload` 384 | 385 | ```JS 386 | 387 | { 388 | bankName: string; 389 | accountNumber: string; 390 | } 391 | 392 | ``` 393 | 394 | `Description` 395 | 396 | | Property | Description | Type | 397 | | ------------- | ----------------------------------------------------------- | ------ | 398 | | bankName | Bank name or Mobile Money name associated with the account. | String | 399 | | accountNumber | Bank account number or Mobile Money number. | String | 400 | 401 | `Method` 402 | 403 |
404 | Request from Token Response 405 | 406 | ```JS 407 | 408 | await token.nameLookup(payload, options) 409 | 410 | ``` 411 | 412 |
413 | 414 |
415 | Request from Instance 416 | 417 | ```JS 418 | 419 | const instance = new azampay.instance({accessToken: token.data.accessToken,apiKey: 'YOUR API KEY'}) 420 | await instance.nameLookup(payload, options) 421 | 422 | ``` 423 | 424 |
425 | 426 | `Response` 427 | 428 | ```JS 429 | 430 | { 431 | name: string; 432 | message: string; 433 | success: boolean; 434 | accountNumber: string; 435 | bankName: string; 436 | } 437 | 438 | ``` 439 | 440 | `Description` 441 | 442 | | Property | Description | Type | 443 | | ------------- | ---------------------------------------------------------------- | ------- | 444 | | bankName | Bank name or Mobile Money name associated with the account. | String | 445 | | accountNumber | Bank account number or Mobile Money number. | String | 446 | | name | Name associated with the account. | String | 447 | | message | A brief description of the response status. | String | 448 | | success | A boolean value indicating if the request was successful or not. | Boolean | 449 | 450 |
451 | 452 | ## Transaction Status 453 | 454 | This method allows you to retrieve the status of a disbursement transaction made through AzamPay. 455 | This method takes two arguments, mandatory payload and the other optional options as explained in [Bank Checkout](#bank-checkout) 456 | 457 | `Payload` 458 | 459 | ```JS 460 | 461 | { 462 | reference: string; 463 | bankName: string; 464 | } 465 | 466 | ``` 467 | 468 | `Description` 469 | 470 | | Property | Description | Type | 471 | | --------- | ---------------------------------------------------------------------------------------- | ------ | 472 | | reference | The transaction ID you received when making the disbursement request. | String | 473 | | bankName | The name of the mobile network operator (MNO) you used to make the disbursement request. | String | 474 | 475 | `Method` 476 | 477 |
478 | Request from Token Response 479 | 480 | ```JS 481 | 482 | await token.transactionStatus(payload, options) 483 | 484 | ``` 485 | 486 |
487 | 488 |
489 | Request from Instance 490 | 491 | ```JS 492 | 493 | const instance = new azampay.instance({accessToken: token.data.accessToken,apiKey: 'YOUR API KEY'}) 494 | await instance.transactionStatus(payload, options) 495 | 496 | ``` 497 | 498 |
499 | 500 | `Response` 501 | 502 | | Property | Description | Type | 503 | | ---------- | --------------------------------------------------------------------- | ------- | 504 | | data | A string containing the status of the transaction. | String | 505 | | message | A string containing a human-readable message describing the response. | String | 506 | | success | A boolean indicating whether the request was successful or not. | Boolean | 507 | | statusCode | An integer indicating the status code of the response. | Number | 508 | 509 |
510 | 511 | ## Post Checkout 512 | 513 | For this post request, send all params that are mentioned below to this end point. 514 | 515 | This end point will respond back with the URL of your payments. Merchant Application can open this url in a new window to continue with the checkout process of the transaction 516 | 517 |
518 | 519 | `Payload` 520 | 521 | ```JS 522 | 523 | { 524 | appName: string; 525 | clientId: string; 526 | vendorId: string; 527 | language: string; 528 | currency: string; 529 | externalId: string; 530 | requestOrigin: string; 531 | redirectFailURL: string; 532 | redirectSuccessURL: string; 533 | vendorName: string; 534 | amount: string; 535 | cart: Cart; 536 | } 537 | 538 | ``` 539 | 540 | `Description` 541 | 542 | | Property | Description | Type | 543 | | ------------------ | ----------------------------------------------------------- | ------ | 544 | | amount | This is amount that will be charged from the given account. | String | 545 | | appName | This is the application name. | String | 546 | | cart | Shoping cart with multiple item. | Object | 547 | | clientId | Client id is unique id for identify client. | String | 548 | | externalId | 30 charecters long unique string. | String | 549 | | language | Language code for transalate the application. | String | 550 | | redirectFailURL | URL that you want to redirected to at transaction failure. | String | 551 | | redirectSuccessURL | URL that you want to redirected to at transaction success. | String | 552 | | requestOrigin | URL which the request is being originated. | String | 553 | | vendorId | Unique id for validate vendor. | String | 554 | | vendorName | Name of vendor. | String | 555 | 556 | `Response` 557 | 558 | This returns a success boolean property indicating whether the operation was successful or not and a data string 559 | 560 | ```JS 561 | 562 | { 563 | data: string 564 | success: boolean; 565 | [key: string]: unknown; 566 | } 567 | 568 | ``` 569 | 570 |
571 | 572 | ## Payment Partners 573 | 574 | This method will return the registered partners of the provided merchant 575 | 576 | This method takes optional options argument as explained in [Bank Checkout](#bank-checkout) 577 | 578 | `Method` 579 | 580 |
581 | Request from Token Response 582 | 583 | ```JS 584 | 585 | await token.partners(options) 586 | 587 | ``` 588 | 589 |
590 | 591 |
592 | Request from Instance 593 | 594 | ```JS 595 | 596 | const instance = new azampay.instance({accessToken: token.data.accessToken,apiKey: 'YOUR API KEY'}) 597 | await instance.partners(options) 598 | 599 | ``` 600 | 601 |
602 | 603 | `Response` 604 | 605 | ```JS 606 | { 607 | success: boolean; 608 | partners: Partners[]; 609 | } 610 | 611 | ``` 612 | 613 | `Description` 614 | 615 | | Property | Description | Type | 616 | | -------- | ---------------------------------------------------------------- | ------------------ | 617 | | success | A boolean value indicating if the request was successful or not. | Boolean | 618 | | partners | An array of payment partners. | [Array](#partners) | 619 | 620 |
621 | 622 | ## Partners 623 | 624 | | Property | Description | Type | 625 | | ---------------- | ------------------------------------------------------------------------------- | ------ | 626 | | currency | Currency code that will convert amount into specific currency format | String | 627 | | logoUrl | Payment Partner logo URL | String | 628 | | partnerName | Name of the payment partner e.g (Azampesa, Airtel, Halopesa, Tigopesa, vodacom) | String | 629 | | paymentPartnerId | Unique id for payment partner | String | 630 | | paymentVendorId | Unique id for payment vendor | String | 631 | | provider | Provider enum value e.g (airtel=2, tigo=3, halopesa=4, azampesa=5, Mpesa=10) | String | 632 | | vendorName | Name of vendor | String | 633 | 634 | # Request Options 635 | 636 | ```JS 637 | 638 | { 639 | apiKey: string; 640 | accessToken: string; 641 | env: 'LIVE' | 'SANDBOX'; 642 | } 643 | 644 | ``` 645 | 646 |
647 | 648 | # Source or Destination 649 | 650 | `Payload` 651 | 652 | ```JS 653 | { 654 | countryCode: string; 655 | fullName: string; 656 | bankName: string; 657 | accountNumber: string; 658 | currency: string; 659 | } 660 | ``` 661 | 662 | `Description` 663 | 664 | | Property | Description | Type | 665 | | ------------- | ------------------------------------------- | ------ | 666 | | countryCode | Country / Destination code | String | 667 | | fullName | Source / Destination account full name | String | 668 | | bankName | Source / Destination account bank name | String | 669 | | accountNumber | Source / Destination account account number | String | 670 | | currency | Source / Destination account currency | String | 671 | 672 |
673 | 674 | # Transfer Details 675 | 676 | `Payload` 677 | 678 | ```JS 679 | 680 | { 681 | type: string; 682 | amount: number; 683 | date: string; 684 | } 685 | 686 | ``` 687 | 688 | `Description` 689 | 690 | | Property | Description | Type | 691 | | -------- | --------------- | ------ | 692 | | type | Transfer type | String | 693 | | amount | Transfer amount | String | 694 | | date | Transfer date | String | 695 | 696 |
697 | 698 | # Error Response 699 | 700 | ```JS 701 | 702 | { 703 | 704 | success: boolean; 705 | message: string; 706 | code: string | number; 707 | statusCode: number; 708 | } 709 | 710 | ``` 711 | 712 | `Property Definition` 713 | 714 | | Property | Description | Type | 715 | | ---------- | ------------------------------------------------------------------------ | ---------------- | 716 | | success | A `false` boolean value indicating that the request was not successfull. | Boolean | 717 | | message | Error message | String | 718 | | code | Error code | Number \| String | 719 | | statusCode | Error Http Status code | Number | 720 | 721 |
722 | 723 | ### Developed and Maintained with ❤️ at [Flexcode Labs](https://flexcodelabs.com) 724 | -------------------------------------------------------------------------------- /jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "ts-jest", 3 | "testEnvironment": "node", 4 | "testMatch": ["**/test/**/*.spec.ts"], 5 | "collectCoverageFrom": [ 6 | "/src/**/*.ts", 7 | "!/src/types/**/*.ts" 8 | ], 9 | "transform": { 10 | "^.+\\.(ts|tsx)$": "ts-jest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "azampay", 3 | "version": "0.0.4", 4 | "description": "Azampay NodeJs SDK to help you interact with Azampay API", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "files": [ 8 | "lib/**/*" 9 | ], 10 | "scripts": { 11 | "prebuild": "rm -rf dist && npm run clean", 12 | "build": "tsc && rm -rf ./lib/test && cp -r ./lib/src/* ./lib/ && rm -rf ./lib/src", 13 | "package": "npm run build -m && ncc build && cd ./lib && find . -name '*.js' -type f -delete && cd ../ && cp -r ./lib/* ./dist/ && rm -rf ./lib && mv dist lib", 14 | "clean": "rm -rf ./lib/", 15 | "cm": "cz", 16 | "release": "npm run package && npm publish", 17 | "lint": "eslint ./src/ --fix", 18 | "prepare": "husky install", 19 | "semantic-release": "semantic-release", 20 | "test:watch": "jest --watch", 21 | "test": "set NODE_TLS_REJECT_UNAUTHORIZED = '0' && jest --coverage", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/flexcodelabs/azampay.git" 27 | }, 28 | "license": "MIT", 29 | "author": { 30 | "name": "Flexcode Labs", 31 | "email": "hi@flexcodelabs.com", 32 | "url": "https://github.com/flexcodelabs" 33 | }, 34 | "engines": { 35 | "node": ">=12.0" 36 | }, 37 | "keywords": [ 38 | "azampay", 39 | "sdk", 40 | "nodejs", 41 | "flexcodelabs", 42 | "flexcode labs" 43 | ], 44 | "bugs": { 45 | "url": "https://github.com/flexcodelabs/azampay/issues" 46 | }, 47 | "homepage": "https://github.com/flexcodelabs/azampay#readme", 48 | "devDependencies": { 49 | "@ryansonshine/commitizen": "^4.2.8", 50 | "@ryansonshine/cz-conventional-changelog": "^3.3.4", 51 | "@types/jest": "^27.5.2", 52 | "@types/node": "^12.20.11", 53 | "@typescript-eslint/eslint-plugin": "^4.22.0", 54 | "@typescript-eslint/parser": "^4.22.0", 55 | "@vercel/ncc": "^0.36.1", 56 | "axios": "^1.4.0", 57 | "conventional-changelog-conventionalcommits": "^5.0.0", 58 | "dotenv": "^16.0.3", 59 | "eslint": "^7.25.0", 60 | "eslint-config-prettier": "^8.3.0", 61 | "eslint-plugin-node": "^11.1.0", 62 | "eslint-plugin-prettier": "^3.4.0", 63 | "http-request-mock": "^1.8.2", 64 | "husky": "^6.0.0", 65 | "jest": "^29.5.0", 66 | "lint-staged": "^10.5.4", 67 | "ncc": "^0.3.6", 68 | "prettier": "^2.2.1", 69 | "semantic-release": "^19.0.2", 70 | "ts-jest": "^29.1.0", 71 | "ts-node": "^10.2.1", 72 | "typescript": "^4.2.4" 73 | }, 74 | "config": { 75 | "commitizen": { 76 | "path": "./node_modules/@ryansonshine/cz-conventional-changelog" 77 | } 78 | }, 79 | "lint-staged": { 80 | "*.ts": "eslint --cache --cache-location .eslintcache --fix" 81 | }, 82 | "release": { 83 | "branches": [ 84 | "main" 85 | ], 86 | "plugins": [ 87 | [ 88 | "@semantic-release/commit-analyzer", 89 | { 90 | "preset": "conventionalcommits", 91 | "releaseRules": [ 92 | { 93 | "type": "build", 94 | "scope": "deps", 95 | "release": "patch" 96 | } 97 | ] 98 | } 99 | ], 100 | [ 101 | "@semantic-release/release-notes-generator", 102 | { 103 | "preset": "conventionalcommits", 104 | "presetConfig": { 105 | "types": [ 106 | { 107 | "type": "feat", 108 | "section": "Features" 109 | }, 110 | { 111 | "type": "fix", 112 | "section": "Bug Fixes" 113 | }, 114 | { 115 | "type": "build", 116 | "section": "Dependencies and Other Build Updates", 117 | "hidden": false 118 | } 119 | ] 120 | } 121 | } 122 | ], 123 | "@semantic-release/npm", 124 | "@semantic-release/github" 125 | ] 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as azampay from './modules/instances'; 2 | export default azampay; 3 | -------------------------------------------------------------------------------- /src/modules/azampay.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError } from 'axios'; 2 | import * as https from 'https'; 3 | import { AUTHENTICATOR, CHECKOUT } from '../shared/enums/azampay.enum'; 4 | import { sanitizeErrorResponse } from '../shared/helpers/error.helper'; 5 | import { 6 | AzamPayInstance, 7 | AzamPayToken, 8 | ErrorResponse, 9 | PartnersResponse, 10 | PostCheckOutInterface, 11 | BankCheckout, 12 | CheckoutResponse, 13 | Disburse, 14 | DisburseResponse, 15 | MnoCheckout, 16 | NameLookup, 17 | NameLookupResponse, 18 | PostCheckOut, 19 | RequestOptions, 20 | TokenPayload, 21 | TransactionStatus, 22 | TransactionStatusResponse, 23 | } from '../shared/interfaces/base.interface'; 24 | 25 | https.globalAgent.options.rejectUnauthorized = false; 26 | 27 | /** 28 | * @class Azam Pay request instance. 29 | */ 30 | export class AzamPay { 31 | constructor(private instance?: AzamPayInstance) {} 32 | 33 | private headers = { 34 | 'Content-Type': 'application/json', 35 | }; 36 | 37 | /** 38 | * @method getToken A static method for getting token 39 | * @param payload Azam Pay request payload 40 | * @returns Access Token and Expiry time 41 | */ 42 | 43 | static getToken = async ( 44 | payload: TokenPayload 45 | ): Promise => { 46 | try { 47 | const { data } = await axios.post( 48 | `${ 49 | AUTHENTICATOR[payload.env ?? 'SANDBOX'] 50 | }/AppRegistration/GenerateToken`, 51 | { 52 | appName: payload.appName, 53 | clientId: payload.clientId, 54 | clientSecret: payload.clientSecret, 55 | } 56 | ); 57 | return { ...data, success: true, statusCode: 200 } as AzamPayToken; 58 | } catch (e) { 59 | return sanitizeErrorResponse(e as AxiosError); 60 | } 61 | }; 62 | 63 | /** 64 | * @method bankCheckout 65 | * @param payload BankCheckout Bank checkout and make payment to requested provider. 66 | * @param options Request options for any additional options or independent request methods. 67 | * @returns CheckoutResponse or ErrorResponse 68 | */ 69 | 70 | bankCheckout = async ( 71 | payload: BankCheckout, 72 | options?: RequestOptions 73 | ): Promise => { 74 | return await this.getBankCheckout(payload, options); 75 | }; 76 | 77 | /** 78 | * @method mnoCheckout 79 | * @param payload BankCheckout Bank checkout and make payment to requested provider. 80 | * @param options Request options for any additional options or independent request methods. 81 | * @returns CheckoutResponse or ErrorResponse 82 | */ 83 | 84 | mnoCheckout = async ( 85 | payload: MnoCheckout, 86 | options?: RequestOptions 87 | ): Promise => { 88 | return await this.getMnoCheckout(payload, options); 89 | }; 90 | 91 | /** 92 | * @method postCheckout 93 | * @param payload Post checkout payload. 94 | * @param options Request options for any additional options or independent request methods. 95 | * @returns string or ErrorResponse 96 | */ 97 | postCheckout = async ( 98 | payload: PostCheckOut, 99 | options?: RequestOptions 100 | ): Promise => { 101 | return await this.getPostCheckout(payload, options); 102 | }; 103 | 104 | /** 105 | * @method disburse 106 | * @param payload Disburse payload. 107 | * @param options Request options for any additional options or independent request methods. 108 | * @returns DisburseResponse or ErrorResponse 109 | */ 110 | disburse = async ( 111 | payload: Disburse, 112 | options?: RequestOptions 113 | ): Promise => { 114 | return this.getDisbursement(payload, options); 115 | }; 116 | 117 | /** 118 | * @method partners. A method used to retrieve payment partners from Azam Pay 119 | * @returns PartnersResponse 120 | */ 121 | 122 | partners = async ( 123 | options?: RequestOptions 124 | ): Promise => { 125 | return await this.getPartners(options); 126 | }; 127 | 128 | /** 129 | * @method nameLookup This API is used to lookup the name associated with a bank account or Mobile Money account. 130 | * @param payload NameLookup payload. 131 | * @param options Request options for any additional options or independent request methods. 132 | * @returns NameLookupResponse or ErrorResponse 133 | */ 134 | 135 | nameLookup = async ( 136 | payload: NameLookup, 137 | options?: RequestOptions 138 | ): Promise => { 139 | return await this.getNameLookup(payload, options); 140 | }; 141 | 142 | /** 143 | * @method disburse This method allows for the transfer of money from other countries to Tanzania. It requires the authorization token generated above, passed as a header in the request. The request should also contain details of the source, destination, and transfer details. Additionally, the request can include an external reference ID and remarks. 144 | * @param payload Disburse payload. 145 | * @param options Request options for any additional options or independent request methods. 146 | * @returns DisburseResponse or ErrorResponse 147 | */ 148 | transactionStatus = async ( 149 | payload: TransactionStatus, 150 | options?: RequestOptions 151 | ): Promise => { 152 | return await this.getStatus(payload, options); 153 | }; 154 | 155 | private getStatus = async ( 156 | payload: TransactionStatus, 157 | options?: RequestOptions 158 | ): Promise => { 159 | try { 160 | const { data } = await axios.get( 161 | `${ 162 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 163 | }/azampay/gettransactionstatus?pgReferenceId=${ 164 | payload.reference 165 | }&bankName=${payload.bankName}`, 166 | { 167 | headers: { 168 | ...this.headers, 169 | Authorization: `Bearer ${ 170 | this.instance?.accessToken ?? options?.accessToken ?? '' 171 | }`, 172 | }, 173 | } 174 | ); 175 | return { 176 | ...data, 177 | success: true, 178 | statusCode: 200, 179 | } as TransactionStatusResponse; 180 | } catch (e) { 181 | return sanitizeErrorResponse(e as AxiosError); 182 | } 183 | }; 184 | private getPartners = async ( 185 | options?: RequestOptions 186 | ): Promise => { 187 | try { 188 | const { data } = await axios.get( 189 | `${ 190 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 191 | }/api/v1/Partner/GetPaymentPartners`, 192 | { 193 | headers: { 194 | ...this.headers, 195 | Authorization: `Bearer ${ 196 | this.instance?.accessToken ?? options?.accessToken ?? '' 197 | }`, 198 | }, 199 | } 200 | ); 201 | return { 202 | partners: data, 203 | success: true, 204 | statusCode: 200, 205 | } as PartnersResponse; 206 | } catch (e) { 207 | return sanitizeErrorResponse(e as AxiosError); 208 | } 209 | }; 210 | 211 | private getNameLookup = async ( 212 | payload: NameLookup, 213 | options?: RequestOptions 214 | ): Promise => { 215 | try { 216 | const { data } = await axios.post( 217 | `${ 218 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 219 | }/azampay/namelookup`, 220 | payload, 221 | { 222 | headers: { 223 | ...this.headers, 224 | Authorization: `Bearer ${ 225 | this.instance?.accessToken ?? options?.accessToken ?? '' 226 | }`, 227 | }, 228 | } 229 | ); 230 | return { ...data, success: true, statusCode: 200 } as NameLookupResponse; 231 | } catch (e) { 232 | return sanitizeErrorResponse(e as AxiosError); 233 | } 234 | }; 235 | private getDisbursement = async ( 236 | payload: Disburse, 237 | options?: RequestOptions 238 | ): Promise => { 239 | try { 240 | const { data } = await axios.post( 241 | `${ 242 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 243 | }/azampay/createtransfer`, 244 | payload, 245 | { 246 | headers: { 247 | ...this.headers, 248 | Authorization: `Bearer ${ 249 | this.instance?.accessToken ?? options?.accessToken ?? '' 250 | }`, 251 | }, 252 | } 253 | ); 254 | return { ...data, success: true, statusCode: 200 } as DisburseResponse; 255 | } catch (e) { 256 | return sanitizeErrorResponse(e as AxiosError); 257 | } 258 | }; 259 | 260 | private getBankCheckout = async ( 261 | payload: BankCheckout, 262 | options?: RequestOptions 263 | ): Promise => { 264 | try { 265 | const { data } = await axios.post( 266 | `${ 267 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 268 | }/azampay/bank/checkout`, 269 | payload, 270 | { 271 | headers: { 272 | ...this.headers, 273 | Authorization: `Bearer ${ 274 | this.instance?.accessToken ?? options?.accessToken ?? '' 275 | }`, 276 | 'X-API-Key': this.instance?.apiKey ?? options?.apiKey, 277 | }, 278 | } 279 | ); 280 | return { ...data, success: true, statusCode: 200 } as CheckoutResponse; 281 | } catch (e) { 282 | return sanitizeErrorResponse(e as AxiosError); 283 | } 284 | }; 285 | private getMnoCheckout = async ( 286 | payload: MnoCheckout, 287 | options?: RequestOptions 288 | ): Promise => { 289 | try { 290 | const { data } = await axios.post( 291 | `${ 292 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 293 | }/azampay/mno/checkout`, 294 | payload, 295 | { 296 | headers: { 297 | ...this.headers, 298 | Authorization: `Bearer ${ 299 | this.instance?.accessToken ?? options?.accessToken ?? '' 300 | }`, 301 | 'X-API-Key': this.instance?.apiKey ?? options?.apiKey, 302 | }, 303 | } 304 | ); 305 | return { ...data, success: true, statusCode: 200 } as CheckoutResponse; 306 | } catch (e) { 307 | return sanitizeErrorResponse(e as AxiosError); 308 | } 309 | }; 310 | 311 | private getPostCheckout = async ( 312 | payload: PostCheckOut, 313 | options?: RequestOptions 314 | ): Promise => { 315 | try { 316 | const { data } = await axios.post( 317 | `${ 318 | CHECKOUT[this.instance?.env ?? options?.env ?? 'SANDBOX'] 319 | }/api/v1/Partner/PostCheckout`, 320 | payload 321 | ); 322 | return { data, success: true, statusCode: 200 } as PostCheckOutInterface; 323 | } catch (e) { 324 | return sanitizeErrorResponse(e as AxiosError); 325 | } 326 | }; 327 | } 328 | -------------------------------------------------------------------------------- /src/modules/instances.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ErrorResponse, 3 | TokenResponse, 4 | TokenPayload, 5 | } from '../shared/interfaces/base.interface'; 6 | import { AzamPay } from './azampay'; 7 | 8 | /** 9 | * @method getToken. Token request method 10 | * @param payload 11 | * @returns TokenResponse 12 | */ 13 | export const getToken = async ( 14 | payload: TokenPayload 15 | ): Promise => { 16 | const token = await AzamPay.getToken(payload); 17 | if (token.success) { 18 | const azamPay = new AzamPay({ 19 | accessToken: token.data?.accessToken, 20 | apiKey: payload?.apiKey, 21 | env: payload.env, 22 | }); 23 | return { 24 | ...token, 25 | bankCheckout: azamPay.bankCheckout, 26 | mnoCheckout: azamPay.mnoCheckout, 27 | postCheckout: azamPay.postCheckout, 28 | disburse: azamPay.disburse, 29 | transactionStatus: azamPay.transactionStatus, 30 | nameLookup: azamPay.nameLookup, 31 | partners: azamPay.partners, 32 | } as TokenResponse; 33 | } 34 | return token as TokenResponse; 35 | }; 36 | 37 | /** 38 | * @class Azam Pay request instance. 39 | */ 40 | export const instance = AzamPay; 41 | -------------------------------------------------------------------------------- /src/shared/enums/azampay.enum.ts: -------------------------------------------------------------------------------- 1 | export enum AUTHENTICATOR { 2 | SANDBOX = 'https://authenticator-sandbox.azampay.co.tz', 3 | LIVE = 'https://authenticator.azampay.co.tz', 4 | } 5 | 6 | export enum CHECKOUT { 7 | SANDBOX = 'https://sandbox.azampay.co.tz', 8 | LIVE = 'https://checkout.azampay.co.tz', 9 | } 10 | 11 | export enum REQUESTMETHODS { 12 | PUT = 'PUT', 13 | POST = 'POST', 14 | GET = 'GET', 15 | PATCH = 'PATCH', 16 | DELETE = 'DELETE', 17 | } 18 | -------------------------------------------------------------------------------- /src/shared/helpers/error.helper.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError } from 'axios'; 2 | import { ErrorResponse } from '../interfaces/base.interface'; 3 | 4 | export const ErrorMessage = ( 5 | errors: 6 | | { 7 | [key: string]: string | string[] | number | Record; 8 | } 9 | | string 10 | ): string => { 11 | if (typeof errors === 'object') { 12 | return Object.keys(errors) 13 | .map(key => { 14 | if (errors[key] && Array.isArray(errors[key])) { 15 | return errors[key]; 16 | } 17 | 18 | if (typeof errors[key] === 'object') { 19 | return Object.keys(errors[key]) 20 | .map(key => 21 | ErrorMessage( 22 | errors[key] as { 23 | [key: string]: 24 | | string 25 | | string[] 26 | | number 27 | | Record; 28 | } 29 | ) 30 | ) 31 | .join(', '); 32 | } 33 | return errors[key]; 34 | }) 35 | .join(', '); 36 | } 37 | 38 | return errors; 39 | }; 40 | 41 | const sanitizedAxiosError = ( 42 | error: AxiosError, 43 | serverError: AxiosError 44 | ) => { 45 | if (serverError?.response) { 46 | return { 47 | ...serverError?.response?.data, 48 | message: ErrorMessage( 49 | serverError?.response?.data?.errors ?? 50 | ([ 51 | serverError?.response?.data?.message ?? 52 | serverError?.response?.statusText ?? 53 | error.message, 54 | ] as any) 55 | ), 56 | error: 57 | serverError?.response?.data?.message ?? 58 | serverError?.response?.statusText ?? 59 | error.message, 60 | code: serverError?.code || 'FAILED', 61 | statusCode: serverError?.response?.status || 400, 62 | success: false, 63 | mnoCheckout: undefined, 64 | data: undefined, 65 | bankCheckout: undefined, 66 | }; 67 | } 68 | return { 69 | ...error, 70 | success: false, 71 | message: 72 | serverError?.message ?? error.response?.statusText ?? error?.message, 73 | error: error.response?.statusText ?? error?.message, 74 | statusCode: error.response?.status ?? 400, 75 | code: serverError?.code || 'FAILED', 76 | mnoCheckout: Function, 77 | data: null, 78 | bankCheckout: null, 79 | }; 80 | }; 81 | export const sanitizeErrorResponse = (error: AxiosError): ErrorResponse => { 82 | const serverError = error as AxiosError; 83 | if (axios.isAxiosError(error)) { 84 | return sanitizedAxiosError(error, serverError) as ErrorResponse; 85 | } 86 | return { 87 | success: false, 88 | message: 89 | serverError?.response?.statusText ?? 90 | serverError?.message ?? 91 | 'Internal server error', 92 | error: 93 | serverError?.response?.statusText ?? 94 | serverError?.message ?? 95 | 'Internal server error', 96 | statusCode: 400, 97 | code: 'FAILED', 98 | mnoCheckout: undefined, 99 | data: undefined, 100 | bankCheckout: undefined, 101 | }; 102 | }; 103 | -------------------------------------------------------------------------------- /src/shared/interfaces/base.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @interface TokenPayload Request payload for token. 3 | */ 4 | export interface TokenPayload { 5 | /** 6 | * @string It will be the name of application. 7 | */ 8 | appName: string; 9 | 10 | /** 11 | * @string It will be the client id generated during application registration. 12 | */ 13 | clientId: string; 14 | 15 | /** 16 | * @string It will be the secret key generated during application registration. 17 | */ 18 | clientSecret: string; 19 | 20 | /** 21 | * @string Azam Pay API key given as token in the settings page. 22 | */ 23 | apiKey: string; 24 | 25 | /** 26 | * @string [SANDBOX | LIVE] Azam Pay environment. 27 | */ 28 | env?: 'SANDBOX' | 'LIVE'; 29 | } 30 | 31 | /** 32 | * @interface BankCheckout Bank checkout and make payment to requested provider. 33 | */ 34 | export interface BankCheckout { 35 | /** 36 | * @string This is amount that will be charged from the given account. 37 | */ 38 | amount: string; 39 | 40 | /** 41 | * @string Code of currency 42 | */ 43 | currencyCode: 'TZS' | string; 44 | 45 | /** 46 | * @string This is the account number/MSISDN that consumer will provide. The amount will be deducted from this account. 47 | */ 48 | merchantAccountNumber: string; 49 | 50 | /** 51 | * @string Mobile number 52 | */ 53 | merchantMobileNumber: string; 54 | 55 | /** 56 | * @string Nullable consumer name. 57 | */ 58 | merchantName?: string | null; 59 | 60 | /** 61 | * @string One time password 62 | */ 63 | otp: string; 64 | 65 | /** 66 | * @string [CRDB | NMB] BankProvider. 67 | */ 68 | provider: 'CRDB' | 'NMB' | string; 69 | 70 | /** 71 | * @string This id belongs to the calling application. Maximum Allowed length for this field is 128 ascii characters. 72 | */ 73 | referenceId: string; 74 | 75 | /** 76 | * @Object This is additional JSON data that calling application can provide. This is optional. 77 | */ 78 | additionalProperties?: Record | null; 79 | } 80 | 81 | /** 82 | * @interface MnoCheckout Mobile Money Operator checkout and make payment to requested provider. 83 | */ 84 | export interface MnoCheckout { 85 | /** 86 | * @string This is the account number/MSISDN that consumer will provide. The amount will be deducted from this account. 87 | */ 88 | accountNumber: string; 89 | 90 | /** 91 | *@string This is amount that will be charged from the given account. 92 | */ 93 | amount: string; 94 | 95 | /** 96 | * @string This is the transaciton currency. Current support values are only (TZS) 97 | */ 98 | currency: string; 99 | 100 | /** 101 | * @string This id belongs to the calling application. Maximum Allowed length for this field is 128 ascii characters 102 | */ 103 | externalId: string; 104 | 105 | /** 106 | * @string ['Airtel' | 'Tigo' | 'Halopesa' | 'Azampesa' | 'Mpesa'] MNO Provider. 107 | */ 108 | provider: 'Airtel' | 'Tigo' | 'Halopesa' | 'Azampesa' | 'Mpesa' | string; 109 | 110 | /** 111 | * @object This is additional JSON data that calling application can provide. This is optional. 112 | */ 113 | additionalProperties?: Record | null; 114 | } 115 | 116 | /** 117 | * @interface CheckoutResponse Response for both BankCheckout and MnoCheckout 118 | */ 119 | export interface CheckoutResponse { 120 | /** 121 | * @string Each successfull transaction will be given a valid transaction id. 122 | */ 123 | transactionId: string; 124 | 125 | /** 126 | * @string This is the status message of checkout request. 127 | */ 128 | msg: string; 129 | 130 | /** 131 | * @string This is the status message of checkout request. 132 | */ 133 | message: string; 134 | 135 | /** 136 | * @boolean A boolean value indicating if the request was successful or not. 137 | */ 138 | success: boolean; 139 | 140 | /** 141 | * @number Response Http Status code. 142 | */ 143 | statusCode: number; 144 | } 145 | 146 | /** 147 | * Request options for request method 148 | */ 149 | export interface RequestOptions { 150 | /** 151 | * Token from Azampay app registration 152 | */ 153 | apiKey?: string; 154 | 155 | /** 156 | * Access token from Azampay got on request 157 | */ 158 | accessToken?: string; 159 | 160 | /** 161 | * Azampay environment 162 | */ 163 | env?: 'LIVE' | 'SANDBOX'; 164 | } 165 | 166 | /** 167 | * @interface PostCheckOut Request interface 168 | */ 169 | export interface PostCheckOut { 170 | /** 171 | * @string It will be the name of application. 172 | */ 173 | appName: string; 174 | 175 | /** 176 | * @string Client id is unique id for identify client 177 | */ 178 | clientId: string; 179 | 180 | /** 181 | * @string [uuid] Unique id for validate vendor 182 | */ 183 | vendorId: string; 184 | 185 | /** 186 | * @string Language code for transalate the application 187 | */ 188 | language: string; 189 | 190 | /** 191 | *@string Currency code that will convert amount into specific currency format 192 | */ 193 | currency: string; 194 | 195 | /** 196 | * @string 30 charecters long unique string 197 | */ 198 | externalId: string; 199 | 200 | /** 201 | * @string The URL where the request is originating 202 | */ 203 | requestOrigin: string; 204 | 205 | /** 206 | * @string URL that you want to redirected to at transaction failure 207 | */ 208 | redirectFailURL: string; 209 | 210 | /** 211 | * @string URL that you want to redirected to at transaction success 212 | */ 213 | redirectSuccessURL: string; 214 | 215 | /** 216 | * @string Name of vendor 217 | */ 218 | vendorName: string; 219 | 220 | /** 221 | * @string This is amount that will be charged from the given account. 222 | */ 223 | amount: string; 224 | 225 | /** 226 | * @object Shoping cart with multiple item 227 | */ 228 | cart: Cart; 229 | } 230 | 231 | export interface Disburse { 232 | /** 233 | * @object Contains information about the source account. 234 | */ 235 | source: Source; 236 | 237 | /** 238 | *@object Contains information about the destination account. 239 | */ 240 | destination: Destination; 241 | 242 | /** 243 | *@object Contains information about the transfer. 244 | */ 245 | transferDetails: TransferDetails; 246 | 247 | /** 248 | * @string An external reference ID to track the transaction. 249 | */ 250 | externalReferenceId: string; 251 | 252 | /** 253 | * @string Any remarks to be included with the transaction. 254 | */ 255 | remarks: string; 256 | } 257 | 258 | /** 259 | * @interface Source Account Details 260 | */ 261 | export interface Source { 262 | /** 263 | * @string Country code 264 | */ 265 | countryCode: string; 266 | 267 | /** 268 | * @string Source account full name 269 | */ 270 | fullName: string; 271 | 272 | /** 273 | * @string Source account bank name 274 | */ 275 | bankName: string; 276 | 277 | /** 278 | * @string Source account account number 279 | */ 280 | accountNumber: string; 281 | 282 | /** 283 | * @string Source account currency 284 | */ 285 | currency: string; 286 | } 287 | 288 | /** 289 | * @interface Destination Account Details 290 | */ 291 | export interface Destination { 292 | /** 293 | * @string Country code 294 | */ 295 | countryCode: string; 296 | 297 | /** 298 | * @string Destination account full name 299 | */ 300 | fullName: string; 301 | 302 | /** 303 | * @string Destination account bank name 304 | */ 305 | bankName: string; 306 | 307 | /** 308 | * @string Destination account account number 309 | */ 310 | accountNumber: string; 311 | 312 | /** 313 | * @string Destination account currency 314 | */ 315 | currency: string; 316 | } 317 | 318 | /** 319 | * @interface TransferDetails 320 | */ 321 | export interface TransferDetails { 322 | /** 323 | * @string Transfer type 324 | */ 325 | type: string; 326 | 327 | /** 328 | * @number Transfer amount 329 | */ 330 | amount: number; 331 | 332 | /** 333 | * @string Transfer date 334 | */ 335 | date: string; 336 | } 337 | 338 | /** 339 | * @interface DisburseResponse Response for disbursement 340 | */ 341 | 342 | export interface DisburseResponse { 343 | /** 344 | * @string A string containing the status of the transaction. 345 | */ 346 | data: string; 347 | 348 | /** 349 | * @string A string containing a human-readable message describing the response. 350 | */ 351 | message: string; 352 | 353 | /** 354 | * @boolean A boolean indicating whether the request was successful or not. 355 | */ 356 | success: boolean; 357 | 358 | /** 359 | * @number Http status code for the request 360 | */ 361 | statusCode: number; 362 | } 363 | 364 | /** 365 | * @interface NameLookup An interface for looking up account name 366 | */ 367 | export interface NameLookup { 368 | /** 369 | * @string Bank name or Mobile Money name associated with the account. 370 | */ 371 | bankName: string; 372 | 373 | /** 374 | * @string Bank account number or Mobile Money number. 375 | */ 376 | accountNumber: string; 377 | } 378 | 379 | /** 380 | * @interface NameLookupResponse An interface used to lookup the name associated with a bank account or Mobile Money account. 381 | */ 382 | export interface NameLookupResponse { 383 | /** 384 | * @string Name associated with the account. 385 | */ 386 | name: string; 387 | 388 | /** 389 | * @string A brief description of the response status. 390 | */ 391 | message: string; 392 | 393 | /** 394 | * @boolean A boolean value indicating if the request was successful or not. 395 | */ 396 | success: boolean; 397 | 398 | /** 399 | * @string Account number. 400 | */ 401 | accountNumber: string; 402 | 403 | /** 404 | * @string Bank name or Mobile Money name associated with the account. 405 | */ 406 | bankName: string; 407 | 408 | /** 409 | * @number Response Http Status code. 410 | */ 411 | statusCode: number; 412 | } 413 | 414 | /** 415 | * @interface TransactionStatusResponse This API allows you to retrieve the status of a disbursement transaction made through AzamPay. 416 | */ 417 | export interface TransactionStatusResponse { 418 | /** 419 | * @string A string containing the status of the transaction. 420 | */ 421 | data: string; 422 | 423 | /** 424 | * @string A string containing a human-readable message describing the response. 425 | */ 426 | message: string; 427 | 428 | /** 429 | * @boolean A boolean indicating whether the request was successful or not. 430 | */ 431 | success: boolean; 432 | 433 | /** 434 | * @number An integer indicating the status code of the response. 435 | */ 436 | statusCode: number; 437 | } 438 | 439 | /** 440 | * @interface TransactionStatus Interface for status method that allows you to retrieve the status of a disbursement transaction made through AzamPay. 441 | */ 442 | export interface TransactionStatus { 443 | /** 444 | *@string The transaction ID you received when making the disbursement request. 445 | */ 446 | reference: string; 447 | 448 | /** 449 | * @string The name of the mobile network operator (MNO) you used to make the disbursement request. 450 | */ 451 | bankName: string; 452 | } 453 | 454 | /** 455 | * @interface TokenResponse Token request response with bankCheckout and mnoCheckout. 456 | */ 457 | export interface TokenResponse { 458 | /** 459 | * @interface TOKENDETAILS Token request response with access token and expiry time. 460 | */ 461 | data?: TOKENDETAILS; 462 | 463 | /** 464 | * @string This is the status message of checkout request. 465 | */ 466 | message: unknown; 467 | 468 | /** 469 | * @boolean A boolean value indicating if the request was successful or not. 470 | */ 471 | success: boolean; 472 | 473 | /** 474 | * @number Response Http Status code. 475 | */ 476 | statusCode: number; 477 | 478 | /** 479 | * @method bankCheckout A method to initiate a Bank checkout with Azam Pay. 480 | */ 481 | bankCheckout?: ( 482 | payload: BankCheckout, 483 | options?: RequestOptions 484 | ) => Promise; 485 | 486 | /** 487 | * @method mnoCheckout A method to initiate MNO checkout with Azam Pay. 488 | */ 489 | mnoCheckout?: ( 490 | payload: MnoCheckout, 491 | options?: RequestOptions 492 | ) => Promise; 493 | 494 | /** 495 | * @method postCheckout A method to initiate post checkout with Azam Pay. 496 | */ 497 | postCheckout?: ( 498 | payload: PostCheckOut, 499 | options?: RequestOptions 500 | ) => Promise; 501 | 502 | /** 503 | * @method disburse A method used to lookup the name associated with a bank account or Mobile Money account. 504 | */ 505 | 506 | disburse?: ( 507 | payload: Disburse, 508 | options?: RequestOptions 509 | ) => Promise; 510 | 511 | /** 512 | * @method transactionStatus Allows you to retrieve the status of a disbursement transaction made through AzamPay. 513 | */ 514 | 515 | transactionStatus?: ( 516 | payload: TransactionStatus, 517 | options?: RequestOptions 518 | ) => Promise; 519 | 520 | /** 521 | * @method nameLookup Is used to lookup the name associated with a bank account or Mobile Money account. 522 | */ 523 | 524 | nameLookup?: ( 525 | payload: NameLookup, 526 | options?: RequestOptions 527 | ) => Promise; 528 | 529 | /** 530 | * @method partners A method to retrieve payment partner 531 | */ 532 | 533 | partners: ( 534 | options?: RequestOptions 535 | ) => Promise; 536 | } 537 | 538 | /** 539 | * @interface Partners Payment partners 540 | */ 541 | export interface Partners { 542 | /** 543 | * @string Payment Partner logo URL 544 | */ 545 | logoUrl: string; 546 | 547 | /*** 548 | * @string Name of the payment partner e.g (Azampesa, Airtel, Halopesa, Tigopesa, vodacom) 549 | */ 550 | partnerName: string; 551 | 552 | /** 553 | * @string Provider enum value e.g (airtel=2, tigo=3, halopesa=4, azampesa=5, Mpesa=10) 554 | */ 555 | provider: number; 556 | 557 | /** 558 | * @string Name of the vendor 559 | */ 560 | vendorName: string; 561 | 562 | /** 563 | * @string Unique id for payment vendor 564 | */ 565 | paymentVendorId: string; 566 | 567 | /** 568 | * @string Unique id for payment partner 569 | */ 570 | paymentPartnerId: string; 571 | 572 | /** 573 | * @string Currency code that will convert amount into specific currency format 574 | */ 575 | currency: string; 576 | } 577 | 578 | /** 579 | * @interface PartnersResponse 580 | */ 581 | export interface PartnersResponse { 582 | /** 583 | * @number Response Http Status code. 584 | */ 585 | statusCode: number; 586 | 587 | /** 588 | * @boolean A boolean value indicating if the request was successful or not. 589 | */ 590 | success: boolean; 591 | 592 | /** 593 | * @interface Partners An array of payment partners 594 | */ 595 | partners: Partners[]; 596 | } 597 | 598 | /** 599 | * @interface AzamPayToken Token request response with access token details and status code. 600 | */ 601 | export interface AzamPayToken { 602 | /** 603 | * @interface TOKENDETAILS Token request response with access token and expiry time. 604 | */ 605 | data: TOKENDETAILS; 606 | 607 | /** 608 | * @string Response message. 609 | */ 610 | message: string; 611 | 612 | /** 613 | * @boolean A boolean value indicating if the request was successful or not. 614 | */ 615 | success: boolean; 616 | 617 | /** 618 | * @number Response Http Status code. 619 | */ 620 | statusCode: number; 621 | } 622 | 623 | /** 624 | * @interface TOKENDETAILS Token request response with access token and expiry time. 625 | */ 626 | export interface TOKENDETAILS { 627 | /** 628 | * @string Azam Pay access token. 629 | */ 630 | accessToken: string; 631 | 632 | /** 633 | * @string Azam Pay access token expire time. 634 | */ 635 | expire: string; 636 | } 637 | 638 | /** 639 | * @interface ErrorResponse Response when an error occurred while doing a request to Azam Pay APIs 640 | */ 641 | 642 | export interface ErrorResponse { 643 | /** 644 | * @boolean A boolean value indicating if the request was successful or not. 645 | */ 646 | success: boolean; 647 | 648 | /** 649 | * @string Response message. 650 | */ 651 | message: string; 652 | 653 | /** 654 | * @string Response code. 655 | */ 656 | code?: string; 657 | 658 | /** 659 | * @number Response Http Status code. 660 | */ 661 | statusCode: number; 662 | 663 | /** 664 | * @Object Additional response object. 665 | */ 666 | [key: string]: unknown; 667 | 668 | data?: TOKENDETAILS; 669 | bankCheckout?: ( 670 | payload: BankCheckout, 671 | options?: RequestOptions 672 | ) => Promise; 673 | mnoCheckout?: ( 674 | payload: MnoCheckout, 675 | options?: RequestOptions 676 | ) => Promise; 677 | 678 | postCheckout?: ( 679 | payload: PostCheckOut, 680 | options?: RequestOptions 681 | ) => Promise; 682 | 683 | disburse?: ( 684 | payload: Disburse, 685 | options?: RequestOptions 686 | ) => Promise; 687 | 688 | transactionStatus?: ( 689 | payload: TransactionStatus, 690 | options?: RequestOptions 691 | ) => Promise; 692 | 693 | nameLookup?: ( 694 | payload: NameLookup, 695 | options?: RequestOptions 696 | ) => Promise; 697 | transactionId?: string; 698 | 699 | partners?: (options?: RequestOptions) => Promise; 700 | } 701 | export interface AzamPayErrors { 702 | [key: string]: unknown; 703 | } 704 | 705 | /** 706 | * @interface AzamPayInstance An optional Azam Pay interface for initiating the request instance. 707 | */ 708 | export interface AzamPayInstance { 709 | /** 710 | * @string Azam Pay access token. 711 | */ 712 | accessToken?: string; 713 | 714 | /** 715 | * @string AzamPayInstance Azam Pay API key given as token in the settings page. 716 | */ 717 | apiKey?: string; 718 | 719 | /** 720 | * @string AzamPayInstance Azam Pay environment. Either SANDBOX | LIVE the default is SANDBOX. 721 | */ 722 | env?: 'LIVE' | 'SANDBOX'; 723 | } 724 | 725 | /** 726 | * @method PostCallback request payload 727 | */ 728 | export interface PostCallback { 729 | /** 730 | * @string This is the application name. 731 | */ 732 | appName: string; 733 | 734 | /** 735 | * @string Client id is unique id for identify client. 736 | */ 737 | clientId: string; 738 | 739 | /** 740 | * @string Unique id for validate vendor. 741 | */ 742 | vendorId: string; 743 | 744 | /** 745 | * @string Language code for transalate the application. 746 | */ 747 | language: string; 748 | 749 | /** 750 | * @string Currency code that will convert amount into specific currency format. 751 | */ 752 | currency: string; 753 | 754 | /** 755 | * @string 30 charecters long unique string. 756 | */ 757 | externalId: string; 758 | 759 | /** 760 | * @string URL which the request is being originated. 761 | */ 762 | requestOrigin: string; 763 | 764 | /** 765 | * @string URL that you want to redirected to at transaction failure. 766 | */ 767 | redirectFailURL: string; 768 | 769 | /** 770 | * @string URL that you want to redirected to at transaction success. 771 | */ 772 | redirectSuccessURL: string; 773 | /** 774 | * @string Name of vendor. 775 | */ 776 | vendorName: string; 777 | /** 778 | * @string This is amount that will be charged from the given account. 779 | */ 780 | amount: string; 781 | /** 782 | * @object Shoping cart with multiple item 783 | */ 784 | cart: Cart; 785 | } 786 | 787 | /** 788 | * @object Shoping cart with multiple item 789 | */ 790 | export interface Cart { 791 | /** 792 | * @string An array of vendor items. 793 | */ 794 | items: Item[]; 795 | } 796 | 797 | export interface Item { 798 | /** 799 | * @string Vendor Item name. 800 | */ 801 | name: string; 802 | } 803 | 804 | /** 805 | * @interface PostCheckOutInterface Response for post checkout 806 | */ 807 | 808 | export interface PostCheckOutInterface { 809 | /** 810 | * @string This is the status message of checkout request. 811 | */ 812 | data: string; 813 | 814 | /** 815 | * @boolean A boolean value indicating if the request was successful or not. 816 | */ 817 | success: boolean; 818 | 819 | /** 820 | * @number Response Http Status code. 821 | */ 822 | statusCode: number; 823 | 824 | /** 825 | * @uknown Generic response for post checkout 826 | */ 827 | [key: string]: unknown; 828 | } 829 | -------------------------------------------------------------------------------- /test/constants/instance.constants.ts: -------------------------------------------------------------------------------- 1 | export const MnoCheckout = { 2 | accountNumber: '1292-123', 3 | amount: '2000', 4 | currency: 'TZS', 5 | externalId: '123', 6 | provider: 'Tigo', 7 | additionalProperties: null, 8 | }; 9 | export const BankCheckout = { 10 | amount: '2000', 11 | currencyCode: 'TZS', 12 | merchantAccountNumber: '1292-123', 13 | merchantMobileNumber: '255123123123', 14 | merchantName: null, 15 | otp: '1234', 16 | provider: 'NMB', 17 | referenceId: '123321', 18 | }; 19 | 20 | export const PostCheckout = { 21 | amount: '100', 22 | appName: 'AzampayApp', 23 | cart: { items: [{ name: 'Shoes' }] }, 24 | clientId: '1292123', 25 | currency: 'TZS', 26 | externalId: 'EXT89772223', 27 | language: 'en', 28 | redirectFailURL: 'https://failure', 29 | redirectSuccessURL: 'https://success', 30 | requestOrigin: 'https://requestorigin.org', 31 | vendorId: '5', 32 | vendorName: 'Vendor 1', 33 | }; 34 | 35 | export const DisburseConstant = { 36 | source: { 37 | countryCode: 'US', 38 | fullName: 'John Doe', 39 | bankName: 'Bank of America', 40 | accountNumber: '123456789', 41 | currency: 'USD', 42 | }, 43 | destination: { 44 | countryCode: 'TZ', 45 | fullName: 'Jane Doe', 46 | bankName: 'Azania Bank', 47 | accountNumber: '987654321', 48 | currency: 'TZS', 49 | }, 50 | transferDetails: { 51 | type: 'SWIFT', 52 | amount: 5000, 53 | date: '2022-01-01', 54 | }, 55 | externalReferenceId: '123', 56 | remarks: 'Payment for goods', 57 | }; 58 | -------------------------------------------------------------------------------- /test/helper.spec.ts: -------------------------------------------------------------------------------- 1 | import { ErrorMessage } from '../src/shared/helpers/error.helper'; 2 | 3 | jest.setTimeout(300000); 4 | 5 | describe('Error Sanitizer', () => { 6 | it('Should return error given an object', () => { 7 | const error = ErrorMessage({ message: 'Error' }); 8 | expect(error).toBe('Error'); 9 | }); 10 | 11 | it('Should return error given a string', () => { 12 | const error = ErrorMessage('Error'); 13 | expect(error).toBe('Error'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/instance.spec.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | import azampay from '../src'; 3 | import { 4 | ErrorResponse, 5 | TokenResponse, 6 | } from '../src/shared/interfaces/base.interface'; 7 | import { 8 | BankCheckout, 9 | DisburseConstant, 10 | MnoCheckout, 11 | PostCheckout, 12 | } from './constants/instance.constants'; 13 | dotenv.config(); 14 | jest.setTimeout(300000); 15 | 16 | /** 17 | * Azampay response for token request. The response can either be of type TokenResponse or ErrorResponse 18 | */ 19 | let results: TokenResponse | ErrorResponse; 20 | 21 | describe('Get Token Success', () => { 22 | it('Should return a token', async () => { 23 | results = await azampay.getToken({ 24 | env: 'SANDBOX', 25 | clientId: process.env.CLIENTID ?? '', 26 | appName: process.env.NAME ?? '', 27 | clientSecret: process.env.SECRET ?? '', 28 | apiKey: process.env.TOKEN ?? '', 29 | }); 30 | expect(results.success).toBe(true); 31 | }); 32 | }); 33 | 34 | describe('Checkout from Token Response', () => { 35 | it('Should perform MNO checkout', async () => { 36 | const mno = results.mnoCheckout 37 | ? await results?.mnoCheckout(MnoCheckout) 38 | : undefined; 39 | expect(mno).toBeDefined(); 40 | expect(mno?.success).toBe(true); 41 | expect(mno?.transactionId).toBeDefined(); 42 | }); 43 | 44 | it('Should perform Bank checkout', async () => { 45 | const bank = results.bankCheckout 46 | ? await results?.bankCheckout(BankCheckout) 47 | : undefined; 48 | 49 | expect(bank).toBeDefined(); 50 | expect(bank?.msg).toBeDefined(); 51 | }); 52 | 53 | it('Should get transaction status ', async () => { 54 | const transactionStatus = results.transactionStatus 55 | ? await results?.transactionStatus({ 56 | bankName: 'NMB', 57 | reference: 'db67aa0e67284b9d84fb9389874e7dad', 58 | }) 59 | : undefined; 60 | expect(transactionStatus).toBeDefined(); 61 | expect(transactionStatus?.message).toBeDefined(); 62 | expect(transactionStatus?.success).toBe(false); 63 | }); 64 | 65 | it('Should lookup a name', async () => { 66 | const transactionStatus = results.nameLookup 67 | ? await results?.nameLookup({ 68 | bankName: 'string', 69 | accountNumber: 'string', 70 | }) 71 | : undefined; 72 | expect(transactionStatus).toBeDefined(); 73 | expect(transactionStatus?.message).toBeDefined(); 74 | expect(transactionStatus?.success).toBe(false); 75 | }); 76 | 77 | it('Should do disbursement', async () => { 78 | const disbursement = results.disburse 79 | ? await results?.disburse(DisburseConstant) 80 | : undefined; 81 | expect(disbursement).toBeDefined(); 82 | expect(disbursement?.message).toBe('Not Found'); 83 | expect(disbursement?.success).toBe(false); 84 | }); 85 | 86 | it('Should do a post checkout', async () => { 87 | const checkout = results.postCheckout 88 | ? await results?.postCheckout(PostCheckout) 89 | : undefined; 90 | expect(checkout).toBeDefined(); 91 | expect(checkout).toBeDefined(); 92 | }); 93 | }); 94 | 95 | describe('Checkout from Instance', () => { 96 | it('Should perform Bank checkout', async () => { 97 | const instance = new azampay.instance({ 98 | accessToken: results.data?.accessToken, 99 | apiKey: process.env.TOKEN ?? '', 100 | }); 101 | const bank = await instance.bankCheckout(BankCheckout); 102 | expect(bank).toBeDefined(); 103 | expect(bank?.msg).toBeDefined(); 104 | }); 105 | }); 106 | 107 | describe('Get Token Fail', () => { 108 | it('Should return a token', async () => { 109 | results = await azampay.getToken({ 110 | env: 'SANDBOX', 111 | clientId: '', 112 | appName: '', 113 | clientSecret: '', 114 | apiKey: '', 115 | }); 116 | expect(results.success).toBe(false); 117 | expect(results.statusCode).toBe(400); 118 | expect(results.mnoCheckout).toBeUndefined(); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": false, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./lib/", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 44 | 45 | /* Module Resolution Options */ 46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 50 | // "typeRoots": [], /* List of folders to include type definitions from. */ 51 | // "types": [], /* Type declaration files to be included in compilation. */ 52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 56 | 57 | /* Source Map Options */ 58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 62 | 63 | /* Experimental Options */ 64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 66 | 67 | /* Advanced Options */ 68 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 70 | }, 71 | "include": ["src/**/*.ts", "test/**/*.ts"], 72 | "exclude": ["*.spec.ts"] 73 | } 74 | --------------------------------------------------------------------------------