UNPKG

mpesalib

Version:

A robust Node.js library for Safaricom's Daraja API with complete TypeScript support and modern async/await patterns

183 lines 6.73 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MpesaUtils = void 0; const crypto = __importStar(require("crypto")); const fs = __importStar(require("fs")); const moment_1 = __importDefault(require("moment")); class MpesaUtils { /** * Generate timestamp in the format YYYYMMDDHHMMSS */ static generateTimestamp() { return (0, moment_1.default)().format('YYYYMMDDHHmmss'); } /** * Generate password for STK Push */ static generatePassword(shortCode, passkey, timestamp) { const data = shortCode + passkey + timestamp; return Buffer.from(data).toString('base64'); } /** * Generate security credential by encrypting the initiator password with the certificate */ static generateSecurityCredential(initiatorPassword, certificatePath) { try { const certificate = fs.readFileSync(certificatePath, 'utf8'); const buffer = Buffer.from(initiatorPassword); const encrypted = crypto.publicEncrypt({ key: certificate, padding: crypto.constants.RSA_PKCS1_PADDING, }, buffer); return encrypted.toString('base64'); } catch (error) { throw new Error(`Error generating security credential: ${error.message}`); } } /** * Format phone number to international format (254XXXXXXXX) */ static formatPhoneNumber(phoneNumber) { // Remove any spaces, dashes, or plus signs let cleaned = phoneNumber.replace(/[\s\-+]/g, ''); // Handle different formats if (cleaned.startsWith('0')) { // Convert 07XXXXXXXX to 254XXXXXXXX cleaned = '254' + cleaned.substring(1); } else if (cleaned.startsWith('7')) { // Convert 7XXXXXXXX to 254XXXXXXXX cleaned = '254' + cleaned; } else if (cleaned.startsWith('2547')) { // Already in correct format return cleaned; } else if (cleaned.startsWith('254')) { // Already in correct format return cleaned; } // Validate the final format if (!/^254[0-9]{9}$/.test(cleaned)) { throw new Error('Invalid phone number format. Expected format: 254XXXXXXXX'); } return cleaned; } /** * Get base URL for the environment */ static getBaseUrl(environment) { return environment === 'production' ? 'https://api.safaricom.co.ke' : 'https://sandbox.safaricom.co.ke'; } /** * Validate configuration */ static validateConfig(config) { const required = ['consumerKey', 'consumerSecret', 'environment', 'shortCode']; const missing = required.filter(key => !config[key]); if (missing.length > 0) { throw new Error(`Missing required configuration: ${missing.join(', ')}`); } if (!['sandbox', 'production'].includes(config.environment)) { throw new Error('Environment must be either "sandbox" or "production"'); } } /** * Generate transaction reference */ static generateTransactionRef(prefix = 'TXN') { const timestamp = Date.now().toString(); const random = Math.random().toString(36).substring(2, 8).toUpperCase(); return `${prefix}_${timestamp}_${random}`; } /** * Sanitize callback data */ static sanitizeCallbackData(data) { // Remove sensitive information from callback data before logging const sanitized = JSON.parse(JSON.stringify(data)); // Remove or mask sensitive fields if (sanitized.Body?.stkCallback?.CallbackMetadata?.Item) { sanitized.Body.stkCallback.CallbackMetadata.Item = sanitized.Body.stkCallback.CallbackMetadata.Item.map((item) => { if (item.Name === 'PhoneNumber') { return { ...item, Value: this.maskPhoneNumber(item.Value) }; } return item; }); } return sanitized; } /** * Mask phone number for logging */ static maskPhoneNumber(phoneNumber) { if (typeof phoneNumber !== 'string' || phoneNumber.length < 4) { return phoneNumber; } const visible = phoneNumber.slice(-4); const masked = '*'.repeat(phoneNumber.length - 4); return masked + visible; } /** * Retry mechanism for API calls */ static async retry(operation, maxRetries = 3, delay = 1000) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt === maxRetries) { throw lastError; } // Wait before retrying await new Promise(resolve => setTimeout(resolve, delay * attempt)); } } throw lastError; } } exports.MpesaUtils = MpesaUtils; //# sourceMappingURL=index.js.map