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
JavaScript
;
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