@johntad/m-pesa
Version:
A TypeScript SDK for integrating M-Pesa mobile payment services into applications, enabling seamless money transfers and transactions.
298 lines (297 loc) • 13.6 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 });
const MPesa_1 = require("../MPesa");
const APIClient_1 = require("../APIClient");
const Logger_1 = require("../Logger");
const MPesaError_1 = require("../errors/MPesaError");
jest.mock('../APIClient');
jest.mock('../Logger');
describe('MPesa', () => {
const mockConfig = {
environment: 'sandbox',
apiKey: 'testApiKey',
secretKey: 'testSecretKey',
timeout: 5000,
retries: 3,
logLevel: Logger_1.LogLevel.Debug,
};
let mpesa;
let mockApiClient;
let mockLogger;
beforeEach(() => {
APIClient_1.APIClient.mockImplementation(() => ({
requestWithRetry: jest.fn(),
}));
Logger_1.Logger.mockImplementation(() => ({
logInfo: jest.fn(),
logWarning: jest.fn(),
logError: jest.fn(),
logCritical: jest.fn(),
logDebug: jest.fn(),
}));
mpesa = MPesa_1.MPesa.getInstance(mockConfig);
mockApiClient = mpesa['apiClient'];
mockLogger = mpesa['logger'];
});
afterEach(() => {
jest.clearAllMocks();
});
describe('authenticate', () => {
it('should throw an AuthenticationError on failure', () => __awaiter(void 0, void 0, void 0, function* () {
mockApiClient.requestWithRetry.mockRejectedValueOnce({
data: {
"resultCode": "999991",
"resultDesc": "Invalid client id passed."
},
status: 400,
statusText: 'Bad request',
headers: {},
config: { headers: {} },
});
yield expect(mpesa['authenticate']()).rejects.toThrow(MPesaError_1.AuthenticationError);
expect(mockLogger.logError).toHaveBeenCalledWith('Authentication failed', expect.anything());
}));
it('should authenticate successfully and store access token', () => __awaiter(void 0, void 0, void 0, function* () {
const mockAuthResponse = {
access_token: 'testToken',
expires_in: 3600,
token_type: 'Bearer',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockAuthResponse,
status: 200,
statusText: 'OK',
headers: {},
config: { headers: {} },
});
yield mpesa['authenticate']();
expect(mockApiClient.requestWithRetry).toHaveBeenCalledWith('GET', '/v1/token/generate?grant_type=client_credentials', undefined, {
headers: {
Authorization: `Basic ${Buffer.from(`${mockConfig.apiKey}:${mockConfig.secretKey}`).toString('base64')}`,
},
});
expect(mpesa['accessToken']).toBe(mockAuthResponse.access_token);
expect(mpesa['tokenExpiry']).toBeGreaterThan(Date.now());
}));
});
describe('stkPush', () => {
it('should send STK Push request successfully', () => __awaiter(void 0, void 0, void 0, function* () {
const payload = {
"MerchantRequestID": "SFC-Testing-9146-4216-9455-e3947ac570fc",
"BusinessShortCode": "554433",
"Password": "123",
"Timestamp": "20160216165627",
"TransactionType": "CustomerPayBillOnline",
"Amount": 10.00,
"PartyA": 251700404789,
"PartyB": 554433,
"PhoneNumber": 251700404789,
"TransactionDesc": "Monthly Unlimited Package via Chatbot",
"CallBackURL": "https://apigee-listener.oat.mpesa.safaricomet.net/api/ussd-push/result",
"AccountReference": "DATA",
"ReferenceData": [
{
"Key": "BundleName",
"Value": "Monthly Unlimited Bundle"
},
{
"Key": "BundleType",
"Value": "Self"
},
{
"Key": "TINNumber",
"Value": "89234093223"
}
]
};
const mockResponse = {
MerchantRequestID: 'testRequestId',
CheckoutRequestID: 'testCheckoutId',
ResponseCode: '0',
ResponseDescription: 'Success',
CustomerMessage: 'Request accepted',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockResponse,
status: 200,
statusText: 'OK',
headers: {},
config: { headers: {} },
});
const response = yield mpesa.stkPush(payload);
expect(mockApiClient.requestWithRetry).toHaveBeenCalledWith('POST', '/mpesa/stkpush/v3/processrequest', payload, {
headers: {
Authorization: `Bearer testToken`,
},
});
expect(response).toEqual(expect.any(Object));
}));
it('should throw StkPushError on API failure', () => __awaiter(void 0, void 0, void 0, function* () {
const payload = {
"MerchantRequestID": "SFC-Testing-9146-4216-9455-e3947ac570fc",
"BusinessShortCode": "554433",
"Password": "123",
"Timestamp": "20160216165627",
"TransactionType": "CustomerPayBillOnline",
"Amount": 10.00,
"PartyA": 251700404789,
"PartyB": 554433,
"PhoneNumber": 251700404789,
"TransactionDesc": "Monthly Unlimited Package via Chatbot",
"CallBackURL": "https://apigee-listener.oat.mpesa.safaricomet.net/api/ussd-push/result",
"AccountReference": "DATA",
"ReferenceData": [
{
"Key": "BundleName",
"Value": "Monthly Unlimited Bundle"
},
{
"Key": "BundleType",
"Value": "Self"
},
{
"Key": "TINNumber",
"Value": "89234093223"
}
]
};
const mockErrorResponse = {
MerchantRequestID: '',
CheckoutRequestID: '',
ResponseCode: '1',
ResponseDescription: 'Failed',
CustomerMessage: 'Request failed',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockErrorResponse,
status: 400,
statusText: 'OK',
headers: {},
config: { headers: {} },
});
yield expect(mpesa.stkPush(payload)).rejects.toThrow(MPesaError_1.StkPushError);
expect(mockLogger.logWarning).toHaveBeenCalledWith('STK Push request failed', { response: mockErrorResponse });
}));
});
describe('registerC2BUrl', () => {
it('should register C2B URL successfully', () => __awaiter(void 0, void 0, void 0, function* () {
const payload = {
"ShortCode": 101010,
"ResponseType": "Completed",
"CommandID": "RegisterURL",
"ConfirmationURL": "http://mydomain.com/c2b/confirmation",
"ValidationURL": "http://mydomai.com/c2b/validation"
};
const mockResponse = {
responseCode: '200',
responseMessage: 'Request processed successfully',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockResponse,
status: 200,
statusText: 'OK',
headers: {},
config: { headers: {} },
});
const response = yield mpesa.registerC2BUrl(payload);
expect(mockApiClient.requestWithRetry).toHaveBeenCalledWith('POST', `/v1/c2b-register-url/register?apikey=${mockConfig.apiKey}`, payload, {
headers: {
Authorization: `Bearer testToken`,
},
});
expect(response).toEqual(expect.any(Object));
}));
it('should throw RegisterUrlError on API failure', () => __awaiter(void 0, void 0, void 0, function* () {
const payload = {
"ShortCode": 101010,
"ResponseType": "Completed",
"CommandID": "RegisterURL",
"ConfirmationURL": "http://mydomain.com/c2b/confirmation",
"ValidationURL": "http://mydomai.com/c2b/validation"
};
const mockErrorResponse = {
responseCode: '400',
responseDescription: 'Failed',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockErrorResponse,
status: 400,
statusText: 'OK',
headers: {},
config: { headers: {} },
});
yield expect(mpesa.registerC2BUrl(payload)).rejects.toThrow(MPesaError_1.RegisterUrlError);
expect(mockLogger.logWarning).toHaveBeenCalledWith('Register C2B Url failed', { response: mockErrorResponse });
}));
});
describe('b2cPayment', () => {
it('should make B2C payment successfully', () => __awaiter(void 0, void 0, void 0, function* () {
const payload = {
"InitiatorName": "testapi",
"SecurityCredential": "iSHJEgQYt3xidNVJ7lbXZqRXUlBqpM/ytL5incRQISaAYX/daObQopdHWiSVXJvexSoYCt9mmb6+TiikmTrGZm5fbaT1BeuPKDF9NFpOLG3n3hUZE2s5wNJvFxD3sM62cBdCQulFquFBc0CwHpq/K2cU1MN8lahvYp+vHnmGODogMBDk8/5Q+5CuRRFNRIt50xM0r10kUHVeWgUa71H6oK2RqXnog4EPTnanMlswz7N3J8jeIKzgGUwnJA8va5CvuNWu2B2L1cAm9g6pGribcgFZ2sgzByJpRWBkfntjGgzsYXh+K3fPZmxWyTQi7TscSvujH1EaS7JxvCIWMM3K0Q==",
"Occassion": "Disbursement",
"CommandID": "BusinessPayment",
"PartyA": 101010,
"PartyB": "251700100100",
"Remarks": "Test B2C",
"Amount": 12,
"QueueTimeOutURL": "https://mydomain.com/b2c/timeout",
"ResultURL": "https://mydomain.com/b2c/result"
};
const mockResponse = {
ResponseCode: '0',
ResponseDescription: 'Success',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockResponse,
status: 400,
statusText: 'OK',
headers: {},
config: { headers: {} },
});
const response = yield mpesa.b2cPayment(payload);
expect(mockApiClient.requestWithRetry).toHaveBeenCalledWith('POST', '/mpesa/b2c/v2/paymentrequest', payload, {
headers: {
Authorization: `Bearer testToken`,
},
});
expect(response).toEqual(expect.any(Object));
}));
it('should throw B2CError on API failure', () => __awaiter(void 0, void 0, void 0, function* () {
const payload = {
"InitiatorName": "testapi",
"SecurityCredential": "iSHJEgQYt3xidNVJ7lbXZqRXUlBqpM/ytL5incRQISaAYX/daObQopdHWiSVXJvexSoYCt9mmb6+TiikmTrGZm5fbaT1BeuPKDF9NFpOLG3n3hUZE2s5wNJvFxD3sM62cBdCQulFquFBc0CwHpq/K2cU1MN8lahvYp+vHnmGODogMBDk8/5Q+5CuRRFNRIt50xM0r10kUHVeWgUa71H6oK2RqXnog4EPTnanMlswz7N3J8jeIKzgGUwnJA8va5CvuNWu2B2L1cAm9g6pGribcgFZ2sgzByJpRWBkfntjGgzsYXh+K3fPZmxWyTQi7TscSvujH1EaS7JxvCIWMM3K0Q==",
"Occassion": "Disbursement",
"CommandID": "BusinessPayment",
"PartyA": 101010,
"PartyB": "251700100100",
"Remarks": "Test B2C",
"Amount": 12,
"QueueTimeOutURL": "https://mydomain.com/b2c/timeout",
"ResultURL": "https://mydomain.com/b2c/result"
};
const mockErrorResponse = {
ResponseCode: '1',
ResponseDescription: 'Failed',
};
mockApiClient.requestWithRetry.mockResolvedValueOnce({
data: mockErrorResponse,
status: 400,
statusText: 'Bad Request',
headers: {},
config: { headers: {} },
});
yield expect(mpesa.b2cPayment(payload)).rejects.toThrow(MPesaError_1.B2CError);
expect(mockLogger.logWarning).toHaveBeenCalledWith('B2C Payment failed', { response: mockErrorResponse });
}));
});
});