├── .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 |
--------------------------------------------------------------------------------