-
Notifications
You must be signed in to change notification settings - Fork 21
Typescript Support #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,5 @@ node_modules/ | |
| .idea/ | ||
| .env | ||
| package-lock.json | ||
| dist/ | ||
| types/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| { | ||
| "extensions": ["ts"], | ||
| "spec": "test/**/*.ts", | ||
| "require": "ts-node/register" | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,127 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import dotenv from 'dotenv'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import axios, { AxiosResponse } from 'axios'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import crypto from 'crypto'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import constants from 'constants'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dotenv.config(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface MpesaConfig { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baseUrl: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| apiKey: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| publicKey: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| origin: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serviceProviderCode: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mpesaConfig: MpesaConfig | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of a mutable global variable For example: export class MpesaApi {
private readonly config: MpesaConfig;
constructor(config: MpesaConfig) {
validateConfig(config);
this.config = config;
}
public async initiateC2B(/*...*/) {
// ... implementation using this.config
}
// ... other methods
} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function _getBearerToken(mpesa_public_key: string, mpesa_api_key: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function names like
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const publicKey = `-----BEGIN PUBLIC KEY-----\n${mpesa_public_key}\n-----END PUBLIC KEY-----`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const buffer = Buffer.from(mpesa_api_key); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const encrypted = crypto.publicEncrypt({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key: publicKey, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: constants.RSA_PKCS1_PADDING, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This uses the
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, buffer); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return encrypted.toString("base64"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function initialize_api_from_dotenv(): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!mpesaConfig) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mpesaConfig = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| baseUrl: process.env.MPESA_API_HOST || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| apiKey: process.env.MPESA_API_KEY || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| publicKey: process.env.MPESA_PUBLIC_KEY || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| origin: process.env.MPESA_ORIGIN || '', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| serviceProviderCode: process.env.MPESA_SERVICE_PROVIDER_CODE || '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validateConfig(mpesaConfig); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Using M-Pesa environment configuration"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log("Using custom M-Pesa configuration"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+38
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function required_config_arg(argName: string): string { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return `Please provide a valid ${argName} in the configuration when calling initializeApi()`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function validateConfig(configParams: MpesaConfig): void { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!configParams.baseUrl) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw required_config_arg("baseUrl"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!configParams.apiKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw required_config_arg("apiKey"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!configParams.publicKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw required_config_arg("publicKey"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!configParams.origin) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw required_config_arg("origin"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!configParams.serviceProviderCode) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw required_config_arg("serviceProviderCode"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const initializeApi = (configParams: MpesaConfig): void => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| validateConfig(configParams); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mpesaConfig = configParams; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const initiate_c2b = async (amount: number, msisdn: string, transaction_ref: string, thirdparty_ref: string): Promise<any> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function returns a
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initialize_api_from_dotenv(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response: AxiosResponse = await axios({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: 'post', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: `https://${mpesaConfig?.baseUrl}:18352/ipg/v1x/c2bPayment/singleStage/`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Authorization': `Bearer ${_getBearerToken(mpesaConfig?.publicKey || '', mpesaConfig?.apiKey || '')}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Origin': mpesaConfig?.origin || '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_TransactionReference": transaction_ref, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_CustomerMSISDN": msisdn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_Amount": amount.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_ThirdPartyReference": thirdparty_ref, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_ServiceProviderCode": mpesaConfig?.serviceProviderCode || '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of optional chaining ( // At the start of the function:
if (!mpesaConfig) {
throw new Error("M-Pesa API not initialized. Call initializeApi() or set environment variables before making API calls.");
}After adding the guard clause, you can safely remove the optional chaining operators. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (e.response?.data) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw e.response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw e; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+91
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The } catch (e: any) {
if (axios.isAxiosError(e) && e.response?.data) {
throw new Error(`M-Pesa API Error: ${JSON.stringify(e.response.data)}`);
}
throw e;
} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const initiate_b2c = async (amount: number, msisdn: string, transaction_ref: string, thirdparty_ref: string): Promise<any> => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| initialize_api_from_dotenv(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const response: AxiosResponse = await axios({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: 'post', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url: `https://${mpesaConfig?.baseUrl}:18345/ipg/v1x/b2cPayment/`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Authorization': `Bearer ${_getBearerToken(mpesaConfig?.publicKey || '', mpesaConfig?.apiKey || '')}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Origin': mpesaConfig?.origin || '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_TransactionReference": transaction_ref, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_CustomerMSISDN": msisdn, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_Amount": amount.toString(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_ThirdPartyReference": thirdparty_ref, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "input_ServiceProviderCode": mpesaConfig?.serviceProviderCode || '' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e: any) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (e.response?.data) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw e.response.data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw e; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,18 @@ | |
| "name": "mpesa-node-api", | ||
| "version": "1.1.0", | ||
| "description": "Node.js library for M-Pesa API (Mozambique)", | ||
| "main": "index.js", | ||
| "main": "dist/index.js", | ||
| "types": "types/index.d.ts", | ||
| "scripts": { | ||
| "test": "mocha" | ||
| "test": "mocha", | ||
| "build": "tsc" | ||
| }, | ||
| "mocha": { | ||
| "require": "ts-node/register", | ||
| "extension": [ | ||
| "ts" | ||
| ], | ||
| "spec": "test/**/*.ts" | ||
| }, | ||
|
Comment on lines
+11
to
17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You have defined Mocha configuration both in this |
||
| "repository": { | ||
| "type": "git", | ||
|
|
@@ -20,13 +29,19 @@ | |
| }, | ||
| "homepage": "https://github.com/rosariopfernandes/mpesa-node-api", | ||
| "dependencies": { | ||
| "axios": "^0.21.1", | ||
| "axios": "^1.8.4", | ||
| "constants": "0.0.2", | ||
| "crypto": "^1.0.1", | ||
| "dotenv": "^8.2.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@types/chai": "^5.2.1", | ||
| "@types/dotenv": "^6.1.1", | ||
| "@types/mocha": "^10.0.10", | ||
| "@types/node": "^22.13.14", | ||
| "chai": "^4.2.0", | ||
| "mocha": "^8.0.1" | ||
| "mocha": "^11.1.0", | ||
| "ts-node": "^9.1.1", | ||
| "typescript": "^5.8.2" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "module": "nodenext", | ||
| "moduleResolution": "nodenext", | ||
| "target": "esnext", | ||
| "declaration": true, | ||
| "declarationDir": "./types", | ||
| "outDir": "./dist", | ||
| "strict": true, | ||
| "esModuleInterop": true, | ||
| "skipLibCheck": true, | ||
| "resolveJsonModule": true, | ||
| "sourceMap": true, | ||
| "types": ["mocha", "chai"], | ||
| }, | ||
| "include": ["**/*.ts"], | ||
| "exclude": [ | ||
| "node_modules" | ||
| ] | ||
|
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code imports from the
constantsnpm package, which is an old and unnecessary dependency. The required constants are available directly from Node.js's built-incryptomodule. Using the built-in module is more secure and reduces the number of dependencies. You should remove this import and usecrypto.constants.RSA_PKCS1_PADDINGon line 23. After this change, you can remove the"constants": "0.0.2"dependency frompackage.json.