UNPKG

@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
"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 }); })); }); });