From 51f1ec0c6f217da1429eb1f9c5782640732eb481 Mon Sep 17 00:00:00 2001 From: iamshabell <91321698+iamshabell@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:28:26 +0300 Subject: [PATCH 1/6] Add new vendor exceptions --- packages/marupay/src/handlers/exeptions.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/marupay/src/handlers/exeptions.ts b/packages/marupay/src/handlers/exeptions.ts index ff8a61e..5b84f29 100644 --- a/packages/marupay/src/handlers/exeptions.ts +++ b/packages/marupay/src/handlers/exeptions.ts @@ -15,4 +15,18 @@ class VendorErrorException extends MaruPayException { } } -export { MaruPayException, VendorErrorException }; +class VendorAccountNotFound extends VendorErrorException { + constructor(public message: string) { + super('ACCOUNT-NOT-FOUND', message || 'Vendor account not found'); + this.name = 'VendorAccountNotFound'; + } +} + +class VendorInsufficientBalance extends VendorErrorException { + constructor(public code: string, public message: string) { + super(code, message || 'Vendor insufficient balance'); + this.name = 'VendorInsufficientBalance'; + } +} + +export { MaruPayException, VendorErrorException, VendorAccountNotFound, VendorInsufficientBalance }; From fc392acfd97d1d2be702e47aa4a452507ceb592e Mon Sep 17 00:00:00 2001 From: iamshabell <91321698+iamshabell@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:29:24 +0300 Subject: [PATCH 2/6] add vendor account not found exception handling --- packages/marupay/src/handlers/edahab/edahab.ts | 11 ++++++++++- packages/marupay/src/handlers/waafi/waafi.ts | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/marupay/src/handlers/edahab/edahab.ts b/packages/marupay/src/handlers/edahab/edahab.ts index 5330314..7a24464 100644 --- a/packages/marupay/src/handlers/edahab/edahab.ts +++ b/packages/marupay/src/handlers/edahab/edahab.ts @@ -8,7 +8,7 @@ import { PaymentCtx, PaymentOptions } from '../types'; import { prepareRequest } from './prepareRequest'; import { SO_ACCOUNT_NUMBER, soPurchaseNumber } from '../constants' import { safeParse } from '../../utils/safeParser'; -import { VendorErrorException } from '../../handlers/exeptions'; +import { VendorAccountNotFound, VendorErrorException } from '../../handlers/exeptions'; const edahabPurchase = z.object({ accountNumber: soPurchaseNumber, @@ -26,6 +26,14 @@ const edahabPurchase = z.object({ const purchaseFn = async (url: string, data: API.PurchasePaymentData, referenceId: string) => { const response = await axios.post(url, data); const { TransactionId, InvoiceStatus } = response.data; + + if (response.data.ValidationErrors) { + const { ErrorMessage, Property } = response.data.ValidationErrors[0]; + if (Property === 'EDahabNumber') { + throw new VendorAccountNotFound(ErrorMessage); + } + } + const responseCode = `${InvoiceStatus}`; if (responseCode !== 'Paid') { console.log(`${InvoiceStatus}`); @@ -51,6 +59,7 @@ const creditFn = async (url: string, data: API.CreditPaymentData, referenceId: s const response = await axios.post(url, data); const { TransactionId, TransactionMesage, TransactionStatus } = response.data; + console.log(`response: ${JSON.stringify(response.data)}`); const responseCode = `${TransactionStatus}`; if (responseCode !== 'Approved') { diff --git a/packages/marupay/src/handlers/waafi/waafi.ts b/packages/marupay/src/handlers/waafi/waafi.ts index 6fa48da..ec5c65a 100644 --- a/packages/marupay/src/handlers/waafi/waafi.ts +++ b/packages/marupay/src/handlers/waafi/waafi.ts @@ -6,7 +6,7 @@ import * as API from './api'; import { prepareRequest } from './prepareRequest'; import { safeParse } from '../../utils/safeParser'; import { soPurchaseNumber } from '../../handlers/constants'; -import { VendorErrorException } from '../../handlers/exeptions'; +import { VendorAccountNotFound, VendorErrorException } from '../../handlers/exeptions'; const waafiPurchase = z.object({ accountNumber: soPurchaseNumber, @@ -23,8 +23,11 @@ async function sendRequest(url: string, data: API.PurchaseData) { const response = await axios.post(url, data); const { responseCode, responseMsg, errorCode, params } = response.data; + if (responseMsg === 'RCS_NO_ROUTE_FOUND') { + throw new VendorAccountNotFound('Must be a valid phone number'); + } + if (responseCode !== '2001' || params == null) { - console.log(`WAAFI: API-RES: ${responseMsg} ERROR-CODE: ${errorCode}`); throw new VendorErrorException(errorCode, responseMsg); } From 04242999c9a6b375aaa3cd8e631f4df4e4169594 Mon Sep 17 00:00:00 2001 From: iamshabell <91321698+iamshabell@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:29:34 +0300 Subject: [PATCH 3/6] Add ValidationError interface to handle validation errors in PurchasePaymentRes --- packages/marupay/src/handlers/edahab/api.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/marupay/src/handlers/edahab/api.ts b/packages/marupay/src/handlers/edahab/api.ts index 8a33c39..7dec430 100644 --- a/packages/marupay/src/handlers/edahab/api.ts +++ b/packages/marupay/src/handlers/edahab/api.ts @@ -14,7 +14,12 @@ export interface PurchasePaymentRes { StatusCode: number; RequestId: number; StatusDescription: string; - ValidationErrors?: string; + ValidationErrors?: ValidationError[]; +} + +export interface ValidationError { + Property: string; + ErrorMessage: string; } export interface CreditPaymentReq { From 326915f6a65c09598c48fba00e52a9d6fb114e3f Mon Sep 17 00:00:00 2001 From: iamshabell <91321698+iamshabell@users.noreply.github.com> Date: Wed, 20 Dec 2023 11:43:41 +0300 Subject: [PATCH 4/6] Add VendorInsufficientBalance exception and handle it in edahab and waafi handlers --- .../marupay/src/handlers/edahab/edahab.ts | 27 ++++++++++--------- packages/marupay/src/handlers/exeptions.ts | 4 +-- packages/marupay/src/handlers/waafi/waafi.ts | 8 ++++-- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/marupay/src/handlers/edahab/edahab.ts b/packages/marupay/src/handlers/edahab/edahab.ts index 7a24464..f4ec181 100644 --- a/packages/marupay/src/handlers/edahab/edahab.ts +++ b/packages/marupay/src/handlers/edahab/edahab.ts @@ -8,7 +8,7 @@ import { PaymentCtx, PaymentOptions } from '../types'; import { prepareRequest } from './prepareRequest'; import { SO_ACCOUNT_NUMBER, soPurchaseNumber } from '../constants' import { safeParse } from '../../utils/safeParser'; -import { VendorAccountNotFound, VendorErrorException } from '../../handlers/exeptions'; +import { VendorAccountNotFound, VendorErrorException, VendorInsufficientBalance } from '../../handlers/exeptions'; const edahabPurchase = z.object({ accountNumber: soPurchaseNumber, @@ -25,19 +25,21 @@ const edahabPurchase = z.object({ */ const purchaseFn = async (url: string, data: API.PurchasePaymentData, referenceId: string) => { const response = await axios.post(url, data); - const { TransactionId, InvoiceStatus } = response.data; - + const { TransactionId, InvoiceStatus, StatusCode, StatusDescription } = response.data; + console.log(`response: ${JSON.stringify(response.data)}`); if (response.data.ValidationErrors) { const { ErrorMessage, Property } = response.data.ValidationErrors[0]; if (Property === 'EDahabNumber') { throw new VendorAccountNotFound(ErrorMessage); } } + + if (StatusCode === 5) { + throw new VendorInsufficientBalance(StatusDescription); + } - const responseCode = `${InvoiceStatus}`; - if (responseCode !== 'Paid') { - console.log(`${InvoiceStatus}`); - throw new VendorErrorException(responseCode, InvoiceStatus); + if (InvoiceStatus !== 'Paid' || StatusCode !== 0) { + throw new VendorErrorException(StatusCode.toString(), InvoiceStatus); } return { transactionId: TransactionId, @@ -59,12 +61,13 @@ const creditFn = async (url: string, data: API.CreditPaymentData, referenceId: s const response = await axios.post(url, data); const { TransactionId, TransactionMesage, TransactionStatus } = response.data; - console.log(`response: ${JSON.stringify(response.data)}`); - const responseCode = `${TransactionStatus}`; - if (responseCode !== 'Approved') { - console.log(`credit error: ${TransactionMesage}`); - throw new VendorErrorException(responseCode, 'EDAHAB-CREDIT-ERROR'); + if (TransactionMesage.includes('sufficient balance')) { + throw new VendorInsufficientBalance(TransactionMesage); + } + + if (TransactionStatus !== 'Approved') { + throw new VendorErrorException(TransactionStatus, 'EDAHAB-CREDIT-ERROR'); } return { diff --git a/packages/marupay/src/handlers/exeptions.ts b/packages/marupay/src/handlers/exeptions.ts index 5b84f29..18a010b 100644 --- a/packages/marupay/src/handlers/exeptions.ts +++ b/packages/marupay/src/handlers/exeptions.ts @@ -23,8 +23,8 @@ class VendorAccountNotFound extends VendorErrorException { } class VendorInsufficientBalance extends VendorErrorException { - constructor(public code: string, public message: string) { - super(code, message || 'Vendor insufficient balance'); + constructor(public message: string) { + super('INSUFFICIENT-BALANCE', message || 'Vendor insufficient balance'); this.name = 'VendorInsufficientBalance'; } } diff --git a/packages/marupay/src/handlers/waafi/waafi.ts b/packages/marupay/src/handlers/waafi/waafi.ts index ec5c65a..8c1bac8 100644 --- a/packages/marupay/src/handlers/waafi/waafi.ts +++ b/packages/marupay/src/handlers/waafi/waafi.ts @@ -6,7 +6,7 @@ import * as API from './api'; import { prepareRequest } from './prepareRequest'; import { safeParse } from '../../utils/safeParser'; import { soPurchaseNumber } from '../../handlers/constants'; -import { VendorAccountNotFound, VendorErrorException } from '../../handlers/exeptions'; +import { VendorAccountNotFound, VendorErrorException, VendorInsufficientBalance } from '../../handlers/exeptions'; const waafiPurchase = z.object({ accountNumber: soPurchaseNumber, @@ -22,11 +22,15 @@ const waafiPurchase = z.object({ async function sendRequest(url: string, data: API.PurchaseData) { const response = await axios.post(url, data); const { responseCode, responseMsg, errorCode, params } = response.data; - + if (responseMsg === 'RCS_NO_ROUTE_FOUND') { throw new VendorAccountNotFound('Must be a valid phone number'); } + if (errorCode === 'E101073') { + throw new VendorInsufficientBalance(responseMsg); + } + if (responseCode !== '2001' || params == null) { throw new VendorErrorException(errorCode, responseMsg); } From ba8483d2dfdd90dc15d78c5d3f0e6cb5c6e5894e Mon Sep 17 00:00:00 2001 From: iamshabell <91321698+iamshabell@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:06:27 +0300 Subject: [PATCH 5/6] Refactor error handling in edahab.ts --- packages/marupay/src/handlers/edahab/edahab.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/marupay/src/handlers/edahab/edahab.ts b/packages/marupay/src/handlers/edahab/edahab.ts index f4ec181..8098e5c 100644 --- a/packages/marupay/src/handlers/edahab/edahab.ts +++ b/packages/marupay/src/handlers/edahab/edahab.ts @@ -38,8 +38,8 @@ const purchaseFn = async (url: string, data: API.PurchasePaymentData, referenceI throw new VendorInsufficientBalance(StatusDescription); } - if (InvoiceStatus !== 'Paid' || StatusCode !== 0) { - throw new VendorErrorException(StatusCode.toString(), InvoiceStatus); + if (InvoiceStatus !== 'Paid') { + throw new VendorErrorException(`${StatusCode}`, InvoiceStatus); } return { transactionId: TransactionId, @@ -62,7 +62,7 @@ const creditFn = async (url: string, data: API.CreditPaymentData, referenceId: s const { TransactionId, TransactionMesage, TransactionStatus } = response.data; - if (TransactionMesage.includes('sufficient balance')) { + if (TransactionMesage === 'You do not have sufficient balance') { throw new VendorInsufficientBalance(TransactionMesage); } From 1643367f5f2cb000e0b209b280de46dbb57c10e1 Mon Sep 17 00:00:00 2001 From: iamshabell <91321698+iamshabell@users.noreply.github.com> Date: Wed, 20 Dec 2023 12:14:22 +0300 Subject: [PATCH 6/6] add unit tests for new vendor exceptions --- .../src/handlers/edahab/edahab.spec.ts | 49 ++++++++++++++++++- .../marupay/src/handlers/edahab/edahab.ts | 5 +- .../marupay/src/handlers/waafi/waafi.spec.ts | 25 ++++++++-- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/packages/marupay/src/handlers/edahab/edahab.spec.ts b/packages/marupay/src/handlers/edahab/edahab.spec.ts index 5a432fa..25d4cb8 100644 --- a/packages/marupay/src/handlers/edahab/edahab.spec.ts +++ b/packages/marupay/src/handlers/edahab/edahab.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { EdahabHandler, createEdahabHandler } from './edahab'; // Import your EdahabHandler import { Currency } from '../../handlers/enums'; -import { VendorErrorException } from '../../handlers/exeptions'; +import { VendorAccountNotFound, VendorErrorException, VendorInsufficientBalance } from '../../handlers/exeptions'; jest.mock('axios'); @@ -76,4 +76,51 @@ describe('Edahab Handler', () => { await expect(handler.credit(options)).rejects.toThrow(new VendorErrorException('Failed', 'EDAHAB-CREDIT-ERROR')); }); + it('throws vendor errors for purchase when account not found', async () => { + const serverResponse = { + StatusCode: 3, + RequestId: 2142, + StatusDescription: "Validation Error", + ValidationErrors: [ + { + Property: "EDahabNumber", + ErrorMessage: "Must be 9 digits and start with 65 or 66 or 62 or 64" + } + ] + } + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(handler.purchase(options)).rejects.toThrow( + new VendorAccountNotFound(serverResponse.ValidationErrors[0].ErrorMessage) + ); + }); + + it('throws vendor errors for purchase when customer insufficient balanace', async () => { + const serverResponse = { + StatusCode: 5, + RequestId: 2142, + StatusDescription: "Insufficient Customer Balance" + } + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(handler.purchase(options)).rejects.toThrow( + new VendorInsufficientBalance(serverResponse.StatusDescription) + ); + }); + + it('throws vendor errors for credit when insufficient balanace', async () => { + const serverResponse = { + TransactionStatus:"Rejected", + TransactionMesage: "You do not have sufficient balance." + } + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(handler.credit(options)).rejects.toThrow( + new VendorInsufficientBalance(serverResponse.TransactionMesage) + ); + }); + }); diff --git a/packages/marupay/src/handlers/edahab/edahab.ts b/packages/marupay/src/handlers/edahab/edahab.ts index 8098e5c..383e5dd 100644 --- a/packages/marupay/src/handlers/edahab/edahab.ts +++ b/packages/marupay/src/handlers/edahab/edahab.ts @@ -1,10 +1,9 @@ import axios from 'axios'; -import { ZodString, z } from 'zod'; +import { z } from 'zod'; import { generateUuid } from '../../utils/generateUuid'; import { defineHandler } from '../../handler'; import * as API from './api'; import { hashSecretKey } from './hash'; -import { PaymentCtx, PaymentOptions } from '../types'; import { prepareRequest } from './prepareRequest'; import { SO_ACCOUNT_NUMBER, soPurchaseNumber } from '../constants' import { safeParse } from '../../utils/safeParser'; @@ -62,7 +61,7 @@ const creditFn = async (url: string, data: API.CreditPaymentData, referenceId: s const { TransactionId, TransactionMesage, TransactionStatus } = response.data; - if (TransactionMesage === 'You do not have sufficient balance') { + if (TransactionMesage === 'You do not have sufficient balance.') { throw new VendorInsufficientBalance(TransactionMesage); } diff --git a/packages/marupay/src/handlers/waafi/waafi.spec.ts b/packages/marupay/src/handlers/waafi/waafi.spec.ts index b70fd68..03e0df6 100644 --- a/packages/marupay/src/handlers/waafi/waafi.spec.ts +++ b/packages/marupay/src/handlers/waafi/waafi.spec.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { WaafiHandler, createWaafiHandler } from './waafi'; // Import your WaafiHandler import { Currency } from '../../handlers/enums'; -import { VendorErrorException } from '../../handlers/exeptions'; +import { VendorAccountNotFound, VendorErrorException, VendorInsufficientBalance } from '../../handlers/exeptions'; jest.mock('axios'); @@ -75,7 +75,7 @@ describe('Waafi Handler', () => { expect(result.paymentStatus).toBe('APPROVED'); }); - it('throws vendor errors for credit when account not found', async () => { + it('throws vendor errors for when account not found', async () => { const serverResponse = { responseCode: '5001', responseMsg: 'RCS_NO_ROUTE_FOUND', @@ -85,8 +85,25 @@ describe('Waafi Handler', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - await expect(handler.credit(options)).rejects.toThrow( - new VendorErrorException('5001', 'RCS_NO_ROUTE_FOUND') + await expect(handler.purchase(options)).rejects.toThrow( + new VendorAccountNotFound('Must be a valid phone number') + ); + }); + + it('throws vendor errors when insufficient balance', async () => { + const serverResponse = { + responseCode: '5001', + responseMsg: 'There are not enough funds in your account to complete this transaction.', + errorCode: 'E101073', + params: null, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(handler.purchase(options)).rejects.toThrow( + new VendorInsufficientBalance(serverResponse.responseMsg) ); }); + + });