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