UNPKG

mpesajs

Version:

A Node.js SDK for seamless integration with M-Pesa payment gateway, providing easy-to-use methods for handling transactions, payments, and API interactions

267 lines 13.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.StkPush = void 0; const axios_1 = __importStar(require("axios")); const base_64_1 = __importDefault(require("base-64")); const ErrorHandlers_1 = require("./errors/ErrorHandlers"); const crypto_1 = __importDefault(require("crypto")); const RateLimiter_1 = require("./utils/RateLimiter"); const env_1 = require("./utils/env"); /** * STK Push implementation for M-Pesa payment integration. * * This class provides functionality to initiate mobile money payment requests to users * via the Safaricom M-Pesa STK Push API. It handles authentication, request validation, * rate limiting, and error management for M-Pesa payment processing. * * @class StkPush */ class StkPush { /** * Creates an instance of the StkPush class. * * @param {Auth} auth - The authentication instance for generating M-Pesa API tokens * @param {boolean} sandbox - Whether to use the sandbox (testing) environment * (defaults to environment variable or true) */ constructor(auth, sandbox = (0, env_1.getEnvVar)('MPESA_SANDBOX', 'true').toLowerCase() === 'true') { this.auth = auth; this.baseUrl = sandbox ? 'https://apisandbox.safaricom.et/mpesa/stkpush/v3/processrequest' : 'https://api.safaricom.et/mpesa/stkpush/v3/processrequest'; this.rateLimiter = RateLimiter_1.RateLimiter.getInstance(); } /** * Validates the phone number format for M-Pesa transactions. * * @private * @param {string} phoneNumber - The customer phone number to validate * @throws {ValidationError} If phone number format is invalid */ validatePhoneNumber(phoneNumber) { if (!phoneNumber.match(/^251[7-9][0-9]{8}$/)) { throw new ErrorHandlers_1.ValidationError('Phone number must start with 251 and be 12 digits long', 'phoneNumber'); } } /** * Validates that the transaction amount is positive. * * @private * @param {number} amount - The transaction amount to validate * @throws {ValidationError} If amount is not greater than 0 */ validateAmount(amount) { if (amount <= 0) { throw new ErrorHandlers_1.ValidationError('Amount must be greater than 0', 'amount'); } } /** * Validates that the callback URL uses HTTPS protocol. * * @private * @param {string} callbackUrl - The callback URL to validate * @throws {ValidationError} If URL does not use HTTPS protocol */ validateCallbackUrl(callbackUrl) { if (!callbackUrl.startsWith('https://')) { throw new ErrorHandlers_1.ValidationError('Callback URL must use HTTPS protocol', 'callbackUrl'); } } /** * Validates that the account reference does not exceed maximum length. * * @private * @param {string} accountReference - The account reference to validate * @throws {ValidationError} If account reference exceeds 12 characters */ validateAccountReference(accountReference) { if (accountReference.length > 12) { throw new ErrorHandlers_1.ValidationError('Account reference must not exceed 12 characters', 'accountReference'); } } /** * Validates that the transaction description does not exceed maximum length. * * @private * @param {string} transactionDesc - The transaction description to validate * @throws {ValidationError} If transaction description exceeds 13 characters */ validateTransactionDesc(transactionDesc) { if (transactionDesc.length > 13) { throw new ErrorHandlers_1.ValidationError('Transaction description must not exceed 13 characters', 'transactionDesc'); } } /** * Validates all input parameters for an STK Push request. * * @private * @param {string} businessShortCode - The business short code * @param {string} passkey - The passkey for generating the password * @param {number} amount - The transaction amount * @param {string} phoneNumber - The customer's phone number * @param {string} callbackUrl - The callback URL for transaction result * @param {string} accountReference - The account reference * @param {string} transactionDesc - The transaction description * @throws {ValidationError} If any parameter fails validation */ validateInputs(businessShortCode, passkey, amount, phoneNumber, callbackUrl, accountReference, transactionDesc) { if (!businessShortCode || !passkey) { throw new ErrorHandlers_1.ValidationError('Business shortcode and passkey are required', 'credentials'); } this.validateAmount(amount); this.validatePhoneNumber(phoneNumber); this.validateCallbackUrl(callbackUrl); this.validateAccountReference(accountReference); this.validateTransactionDesc(transactionDesc); } /** * Generates the secure password required for STK Push API requests. * * The password is generated by concatenating the business short code, passkey, and timestamp, * then applying SHA-256 hashing and Base64 encoding. * * @private * @param {string} businessShortCode - The business short code * @param {string} passkey - The passkey provided by M-Pesa * @param {string} timestamp - The transaction timestamp * @returns {string} The generated Base64-encoded password */ generatePassword(businessShortCode, passkey, timestamp) { const password = `${businessShortCode}${passkey}${timestamp}`; const hashedPassword = crypto_1.default .createHash('sha256') .update(password) .digest(); return base_64_1.default.encode(hashedPassword.toString()); } /** * Initiates an STK Push transaction to request payment from a customer. * * This method handles the entire STK Push process: * - Validates all input parameters * - Generates the required security credentials * - Obtains an access token for the M-Pesa API * - Creates and sends the API request with proper rate limiting * - Handles success and error responses * * The method uses environment variables for optional parameters if not explicitly provided. * * @param {number} amount - The amount to be paid * @param {string} transactionDesc - Description of the transaction (max 13 chars) * @param {string} accountReference - Reference for the transaction (max 12 chars) * @param {string} [businessShortCode] - The business short code (defaults to env var) * @param {string} [passkey] - The passkey for password generation (defaults to env var) * @param {string} [phoneNumber] - The customer's phone number (defaults to env var) * @param {string} [callbackUrl] - The callback URL for results (defaults to env var) * @returns {Promise<StkPushResponse>} A promise resolving to the M-Pesa API response * @throws {ValidationError} If any input parameter is invalid * @throws {NetworkError} If there are connectivity issues * @throws {StkPushError} If the M-Pesa API returns an error * @throws {MpesaError} For other failures during the process */ sendStkPush(amount_1, transactionDesc_1, accountReference_1) { return __awaiter(this, arguments, void 0, function* (amount, transactionDesc, accountReference, businessShortCode = (0, env_1.getEnvVar)('MPESA_BUSINESS_SHORTCODE', ''), passkey = (0, env_1.getEnvVar)('MPESA_PASSKEY', ''), phoneNumber = (0, env_1.getEnvVar)('MPESA_PHONE_NUMBER', ''), callbackUrl = (0, env_1.getEnvVar)('MPESA_CALLBACK_URL', '')) { return this.rateLimiter.execute(() => __awaiter(this, void 0, void 0, function* () { var _a, _b; try { this.validateInputs(businessShortCode, passkey, amount, phoneNumber, callbackUrl, accountReference, transactionDesc); const timestamp = new Date() .toISOString() .replace(/[^0-9]/g, '') .slice(0, 14); const password = this.generatePassword(businessShortCode, passkey, timestamp); const { token: accessToken } = yield this.auth.generateToken(); const payload = { MerchantRequestID: crypto_1.default.randomUUID().replace(/-/g, '').slice(0, 20), BusinessShortCode: businessShortCode, Password: password, Timestamp: timestamp, TransactionType: "CustomerPayBillOnline", Amount: Math.round(amount), PartyA: phoneNumber.replace(/^0/, '251'), PartyB: businessShortCode, PhoneNumber: phoneNumber.replace(/^0/, '251'), CallBackURL: callbackUrl, AccountReference: accountReference.slice(0, 12), TransactionDesc: transactionDesc.slice(0, 13) }; const response = yield axios_1.default.post(this.baseUrl, payload, { headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' } }); if (((_a = response.data) === null || _a === void 0 ? void 0 : _a.ResponseCode) === '0') { return response.data; } ErrorHandlers_1.StkPushErrorHandler.handle(response.data); } catch (error) { if (error instanceof ErrorHandlers_1.ValidationError) { throw error; } if (error instanceof axios_1.AxiosError) { if ((_b = error.response) === null || _b === void 0 ? void 0 : _b.data) { ErrorHandlers_1.StkPushErrorHandler.handle(error.response.data); } if (error.request || error.code === 'ECONNABORTED') { throw new ErrorHandlers_1.NetworkError('No response received from the API. Please check your network connection.'); } throw new ErrorHandlers_1.MpesaError(`Failed to send STK Push request: ${error.message}`); } if (error instanceof ErrorHandlers_1.StkPushError) { throw error; } throw new ErrorHandlers_1.MpesaError(`Failed to send STK Push request: ${error instanceof Error ? error.message : 'Unknown error occurred'}`); } })); }); } } exports.StkPush = StkPush; //# sourceMappingURL=stkpush.js.map