UNPKG

@johntad/m-pesa

Version:

A TypeScript SDK for integrating M-Pesa mobile payment services into applications, enabling seamless money transfers and transactions.

182 lines (181 loc) 9.21 kB
"use strict"; 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()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.MPesa = void 0; const APIClient_1 = require("./APIClient"); const MPesaError_1 = require("./errors/MPesaError"); const AuthResponse_1 = require("./models/AuthResponse"); const StkPushResponse_1 = require("./models/StkPushResponse"); const B2CPaymentResponse_1 = require("./models/B2CPaymentResponse"); const C2BUrlResponse_1 = require("./models/C2BUrlResponse"); const Logger_1 = require("./Logger"); class MPesa { constructor(config) { var _a; this.accessToken = null; this.tokenExpiry = null; this.config = config; const defaultLogLevel = config.environment === 'sandbox' ? Logger_1.LogLevel.Debug : Logger_1.LogLevel.Error; const logLevel = (_a = config.logLevel) !== null && _a !== void 0 ? _a : defaultLogLevel; this.logger = new Logger_1.Logger(logLevel); this.logger.logInfo('Logger initialized', { environment: config.environment, logLevel }); const baseURL = config.environment === 'sandbox' ? 'https://apisandbox.safaricom.et' : 'https://api.safaricom.et'; this.apiClient = new APIClient_1.APIClient({ baseURL, timeout: config.timeout, retries: config.retries, }); this.logger.logInfo('APIClient initialized', { baseURL, timeout: config.timeout, retries: config.retries }); } static getInstance(config) { if (!MPesa.instance) { MPesa.instance = new MPesa(config); } return MPesa.instance; } authenticate() { return __awaiter(this, void 0, void 0, function* () { if (this.accessToken && this.tokenExpiry && Date.now() < this.tokenExpiry) { return; // Token is still valid } const credentials = Buffer.from(`${this.config.apiKey}:${this.config.secretKey}`).toString('base64'); const headers = { Authorization: `Basic ${credentials}`, }; try { const response = yield this.apiClient.requestWithRetry('GET', '/v1/token/generate?grant_type=client_credentials', undefined, { headers }); const authResponse = AuthResponse_1.AuthResponse.fromApiResponse(response.data); this.accessToken = authResponse.accessToken; this.tokenExpiry = Date.now() + authResponse.expiresIn * 1000; this.logger.logInfo('Authentication successful', { tokenExpiry: this.tokenExpiry }); } catch (error) { this.logger.logError('Authentication failed', { error }); if (typeof error === 'object' && error !== null && 'data' in error) { const errorData = error.data; const { resultCode, resultDesc } = errorData; throw new MPesaError_1.AuthenticationError(resultDesc, resultCode); } // Fallback for unexpected errors throw new MPesaError_1.AuthenticationError('Unexpected error during authentication', 'UNKNOWN_ERROR'); } }); } makeAuthorizedRequest(method, endpoint, data) { return __awaiter(this, void 0, void 0, function* () { this.logger.logDebug('Making authorized request', { method, endpoint, data }); yield this.authenticate(); const headers = { Authorization: `Bearer ${this.accessToken}`, }; try { const res = yield this.apiClient.requestWithRetry(method, endpoint, data, { headers }); this.logger.logInfo('Request successful', { method, endpoint }); return res.data; } catch (error) { this.logger.logError('Request failed', { method, endpoint, error }); throw error; } }); } /** * Sends an STK Push request to the M-Pesa API. * @param payload - The payload for the STK Push request. * @returns A StkPushResponse instance representing the response. * @throws StkPushError if the API returns an error response. */ stkPush(payload) { return __awaiter(this, void 0, void 0, function* () { this.logger.logInfo('Initiating STK Push', { payload }); try { const response = yield this.makeAuthorizedRequest('POST', '/mpesa/stkpush/v3/processrequest', payload); const stkPushResponse = StkPushResponse_1.StkPushResponse.fromApiResponse(response); if (!stkPushResponse.isSuccess()) { this.logger.logWarning('STK Push request failed', { response }); throw new MPesaError_1.StkPushError('STK Push request failed', response); } return stkPushResponse; } catch (error) { if (error instanceof MPesaError_1.StkPushError) { this.logger.logError('STK Push-specific error occurred', { error }); throw error; } this.logger.logCritical('Unexpected error occurred during STK Push', { error }); throw new Error(`Unexpected error occurred during STK Push ${error}`); } }); } /** * Registers validation and confirmation URLs with the M-Pesa API. * @param payload - The payload for the Register URL request. * @returns A RegisterUrlResponse instance representing the response. * @throws RegisterUrlError if the API returns an error response. */ registerC2BUrl(payload) { return __awaiter(this, void 0, void 0, function* () { this.logger.logInfo('Initiating Register C2B Url', { payload }); try { const response = yield this.makeAuthorizedRequest('POST', `/v1/c2b-register-url/register?apikey=${this.config.apiKey}`, payload); const registerResponse = C2BUrlResponse_1.RegisterUrlResponse.fromApiResponse(response); if (!registerResponse.isSuccess()) { this.logger.logWarning('Register C2B Url failed', { response }); throw new MPesaError_1.RegisterUrlError('Register URL request failed', response); } return registerResponse; } catch (error) { if (error instanceof MPesaError_1.RegisterUrlError) { this.logger.logError('Regiter URL-specific error occured', { error }); throw error; } this.logger.logCritical('Unexpected error occurred during Register URL', { error }); throw new Error('Unexpected error occurred during Register URL'); } }); } /** * Sends a B2C Pay Out request to the M-Pesa API. * @param payload - The payload for the B2C Pay Out request. * @returns A B2CResponse instance representing the response. * @throws B2CError if the API returns an error response. */ b2cPayment(payload) { return __awaiter(this, void 0, void 0, function* () { this.logger.logInfo('Initiating B2C Payment', { payload }); try { const response = yield this.makeAuthorizedRequest('POST', '/mpesa/b2c/v2/paymentrequest', payload); const b2cResponse = B2CPaymentResponse_1.B2CResponse.fromApiResponse(response); if (!b2cResponse.isSuccess()) { this.logger.logWarning('B2C Payment failed', { response }); throw new MPesaError_1.B2CError('B2C Pay Out request failed', { requestId: '--', // To-Do: modify error response type (`requestID` is known) errorCode: b2cResponse.ResponseCode, errorMessage: b2cResponse.ResponseDescription, }); } return b2cResponse; } catch (error) { if (error instanceof MPesaError_1.B2CError) { this.logger.logError('B2C Payment-related error occurred', { error }); throw error; } this.logger.logCritical('Unexpected error occurred during B2C Payment', { error }); throw new Error('Unexpected error occurred during B2C Pay Out'); } }); } } exports.MPesa = MPesa;