From ed75d9d39d5d7ff4aea7d1d7bb50a1fcfdba0a45 Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 19:22:19 +0430 Subject: [PATCH 01/31] Create `MonopayError` abstract class --- src/exceptions.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/exceptions.ts b/src/exceptions.ts index 58c4bf6..690a9a3 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -1,3 +1,24 @@ +type MonoPayErrorConfig = { + isIPGError: boolean; + isSafeToDisplay: boolean; + message?: string; +}; +export abstract class MonopayError extends Error { + /** + * Determines whether the error was thrown by the IPG or by the application itself + */ + readonly isIPGError: boolean; + /** + * Determines whether the error exposes sensitive information or not + */ + readonly isSafeToDisplay: boolean; + constructor(options: MonoPayErrorConfig) { + super(options.message); + this.isIPGError = options.isIPGError; + this.isSafeToDisplay = options.isSafeToDisplay; + } +} + export class BasePaymentException extends Error { constructor(message?: string) { super(message); @@ -40,9 +61,8 @@ export class VerificationException extends BasePaymentException { /** * Error when the configuration has problems */ -export class BadConfigException extends BasePaymentException { - constructor(errors: string[]) { - super(errors.join(',\n')); - Object.setPrototypeOf(this, BadConfigException.prototype); +export class BadConfigError extends MonopayError { + constructor(message: string, isIPGError: boolean) { + super({ isIPGError, isSafeToDisplay: false, message }); } } From 0a2bafb22b5c33b03195ea2696389c6f8fc7ac1c Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 20:10:38 +0430 Subject: [PATCH 02/31] Throw `BadConfigError` on wrong schemas --- src/driver.spec.ts | 38 ++++++++++++++++++++++++++++++++++---- src/driver.ts | 8 ++++---- src/utils/safeParse.ts | 9 +++++++++ 3 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/utils/safeParse.ts diff --git a/src/driver.spec.ts b/src/driver.spec.ts index 0eb0067..6502534 100644 --- a/src/driver.spec.ts +++ b/src/driver.spec.ts @@ -1,11 +1,12 @@ +import { BadConfigError } from './exceptions'; import { z } from 'zod'; import { defineDriver } from './driver'; const createTestDriver = defineDriver({ schema: { - config: z.object({}), - request: z.object({}), - verify: z.object({}), + config: z.object({ test: z.string() }), + request: z.object({ test: z.string() }), + verify: z.object({ test: z.string() }), }, defaultConfig: {}, request: async () => { @@ -27,8 +28,9 @@ const createTestDriver = defineDriver({ describe('Driver', () => { it('creates javascript raw script for form submission on request()', async () => { - const driver = createTestDriver({}); + const driver = createTestDriver({ test: 'test' }); const paymentInfo = await driver.request({ + test: 'test', amount: 1000, callbackUrl: 'https://callback.url/', description: 'testin', @@ -37,4 +39,32 @@ describe('Driver', () => { expect(typeof paymentInfo.getScript()).toBe('string'); expect(paymentInfo.getScript()).toContain('.submit()'); }); + it('Throws badConfigError when wrong config is passed', () => { + // @ts-ignore + expect(() => createTestDriver({})).toThrow(BadConfigError); + }); + it('Throws badConfigError when wrong request params are passed', async () => { + const driver = createTestDriver({ test: 'test' }); + await expect( + async () => + // @ts-ignore + await driver.request({ + amount: 1000, + callbackUrl: 'https://callback.url/', + description: 'testin', + }), + ).rejects.toThrow(BadConfigError); + }); + it('Throws badConfigError when wrong verify params are passed', async () => { + const driver = createTestDriver({ test: 'test' }); + await expect( + async () => + // @ts-ignore + await driver.verify({ + amount: 1000, + callbackUrl: 'https://callback.url/', + description: 'testin', + }), + ).rejects.toThrow(BadConfigError); + }); }); diff --git a/src/driver.ts b/src/driver.ts index 40f1ea6..55c9459 100644 --- a/src/driver.ts +++ b/src/driver.ts @@ -1,5 +1,6 @@ import { z, ZodSchema } from 'zod'; import { buildRedirectScript } from './utils/buildRedirectScript'; +import { safeParse } from './utils/safeParse'; interface IPaymentInfo { referenceId: string | number; @@ -52,10 +53,9 @@ export const defineDriver = < verify: (arg: { ctx: IConfig; options: IVerify; params: Record }) => Promise; }) => { return (config: Omit & Partial) => { - const ctx: IConfig = schema.config.parse({ ...defaultConfig, ...config }); - + const ctx = safeParse(schema.config, { ...defaultConfig, ...config }) as IConfig; const requestPayment = async (options: Parameters['0']['options']) => { - options = schema.request.parse(options); + options = safeParse(schema.request, options) as IRequest; const paymentInfo = await request({ ctx, options }); return { ...paymentInfo, @@ -67,7 +67,7 @@ export const defineDriver = < options: Parameters['0']['options'], params: Parameters['0']['params'], ) => { - options = schema.verify.parse(options); + options = safeParse(schema.verify, options) as IVerify; return verify({ ctx, options, params }); }; diff --git a/src/utils/safeParse.ts b/src/utils/safeParse.ts new file mode 100644 index 0000000..34fac75 --- /dev/null +++ b/src/utils/safeParse.ts @@ -0,0 +1,9 @@ +import { BadConfigError } from '../exceptions'; +import { ZodSchema } from 'zod'; + +export const safeParse = (parser: ZodSchema, data: T): T => { + const parsed = parser.safeParse(data); + if (parsed.success) return parsed.data; + const message = parsed.error.errors.map((err) => `${err.path[0]}: ${err.message}`).join('\n'); + throw new BadConfigError(message, false); +}; From 3346419e2ed7b23762bf8894698882d402033229 Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 20:59:32 +0430 Subject: [PATCH 03/31] resolves #41: Fix Behpardakht error codes bug --- src/drivers/behpardakht/behpardakht.spec.ts | 3 +-- src/drivers/behpardakht/behpardakht.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/drivers/behpardakht/behpardakht.spec.ts b/src/drivers/behpardakht/behpardakht.spec.ts index 45da4d8..1e82111 100644 --- a/src/drivers/behpardakht/behpardakht.spec.ts +++ b/src/drivers/behpardakht/behpardakht.spec.ts @@ -35,8 +35,7 @@ describe('Behpardakht Driver', () => { }); it('throws payment errors accordingly', async () => { - const serverResponse: API.RequestPaymentRes = '100'; - + const serverResponse: API.RequestPaymentRes = '11'; mockSoapClient.bpPayRequest = () => serverResponse; await expect( diff --git a/src/drivers/behpardakht/behpardakht.ts b/src/drivers/behpardakht/behpardakht.ts index b954dbe..93a5aa3 100644 --- a/src/drivers/behpardakht/behpardakht.ts +++ b/src/drivers/behpardakht/behpardakht.ts @@ -73,7 +73,7 @@ export const createBehpardakhtDriver = defineDriver({ const RefId = splittedResponse[1]; if (ResCode.toString() !== '0') { - throw new RequestException(API.errors[response[0]]); + throw new RequestException(API.errors[ResCode]); } return { From 1253dfa15d4dc1069777567c39c320dea31fff0c Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 21:13:42 +0430 Subject: [PATCH 04/31] Throw `BadConfigError` for Behpardakht IPG --- src/drivers/behpardakht/api.ts | 26 +++++++++++++++++++++ src/drivers/behpardakht/behpardakht.spec.ts | 14 ++++++++++- src/drivers/behpardakht/behpardakht.ts | 10 +++++++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/drivers/behpardakht/api.ts b/src/drivers/behpardakht/api.ts index 7424abb..08bbb6d 100644 --- a/src/drivers/behpardakht/api.ts +++ b/src/drivers/behpardakht/api.ts @@ -190,3 +190,29 @@ export const errors: Record = { '55': 'است نامعتبر تراكنش', '61': 'واريز در خطا', }; + +export const IPGConfigErrors = [ + '21', + '24', + '25', + '31', + '32', + '33', + '35', + '41', + '42', + '43', + '44', + '45', + '46', + '47', + '48', + '49', + '412', + '413', + '414', + '417', + '51', + '54', + '55', +]; diff --git a/src/drivers/behpardakht/behpardakht.spec.ts b/src/drivers/behpardakht/behpardakht.spec.ts index 1e82111..2410274 100644 --- a/src/drivers/behpardakht/behpardakht.spec.ts +++ b/src/drivers/behpardakht/behpardakht.spec.ts @@ -1,5 +1,5 @@ import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { BehpardakhtDriver, createBehpardakhtDriver } from './behpardakht'; @@ -47,6 +47,18 @@ describe('Behpardakht Driver', () => { ).rejects.toThrow(RequestException); }); + it('throws bad config error for payment accordingly', async () => { + const serverResponse: API.RequestPaymentRes = '24'; + mockSoapClient.bpPayRequest = () => serverResponse; + await expect( + async () => + await driver.request({ + amount: 20000, + callbackUrl: 'https://mysite.com/callback', + }), + ).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = '0'; const callbackParams: API.CallbackParams = { diff --git a/src/drivers/behpardakht/behpardakht.ts b/src/drivers/behpardakht/behpardakht.ts index 93a5aa3..144cf7e 100644 --- a/src/drivers/behpardakht/behpardakht.ts +++ b/src/drivers/behpardakht/behpardakht.ts @@ -1,7 +1,7 @@ import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import { generateId } from '../../utils/generateId'; import * as API from './api'; @@ -25,6 +25,10 @@ const timeFormat = (date = new Date()) => { return hh.toString() + mm.toString() + ss.toString(); }; +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); +}; + export const createBehpardakhtDriver = defineDriver({ schema: { config: z.object({ @@ -73,6 +77,7 @@ export const createBehpardakhtDriver = defineDriver({ const RefId = splittedResponse[1]; if (ResCode.toString() !== '0') { + throwOnIPGBadConfigError(ResCode); throw new RequestException(API.errors[ResCode]); } @@ -90,6 +95,7 @@ export const createBehpardakhtDriver = defineDriver({ const { terminalId, username, password, links } = ctx; if (ResCode !== '0') { + throwOnIPGBadConfigError(ResCode); throw new PaymentException(API.errors[ResCode]); } @@ -111,6 +117,7 @@ export const createBehpardakhtDriver = defineDriver({ if (verifyResponse.toString() !== '43') { soapClient.bpReversalRequest(requestFields); } + throwOnIPGBadConfigError(ResCode); throw new VerificationException(API.errors[verifyResponse]); } @@ -120,6 +127,7 @@ export const createBehpardakhtDriver = defineDriver({ if (settleResponse.toString() !== '45' && settleResponse.toString() !== '48') { soapClient.bpReversalRequest(requestFields); } + throwOnIPGBadConfigError(ResCode); throw new VerificationException(API.errors[verifyResponse]); } From d58cafafe15bf3e694e5bf2b6f9f415a89591f0b Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 21:27:57 +0430 Subject: [PATCH 05/31] Throw `BadConfigError` for IdPay IPG --- src/drivers/idpay/api.ts | 22 ++++++++++++++++++++++ src/drivers/idpay/idpay.spec.ts | 13 ++++++++++++- src/drivers/idpay/idpay.ts | 21 +++++++++++++++------ 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/src/drivers/idpay/api.ts b/src/drivers/idpay/api.ts index 13b0703..8ebf3d6 100644 --- a/src/drivers/idpay/api.ts +++ b/src/drivers/idpay/api.ts @@ -271,3 +271,25 @@ export const errors: Record = { // 405 '54': 'مدت زمان تایید پرداخت سپری شده است.', }; + +export const IPGConfigErrors = [ + '11', + '12', + '14', + '21', + '22', + '23', + '24', + '31', + '32', + '33', + '34', + '35', + '36', + '37', + '38', + '41', + '42', + '43', + '54', +]; diff --git a/src/drivers/idpay/idpay.spec.ts b/src/drivers/idpay/idpay.spec.ts index 2a12d5a..c4aa5b0 100644 --- a/src/drivers/idpay/idpay.spec.ts +++ b/src/drivers/idpay/idpay.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createIdpayDriver, IdpayDriver } from './idpay'; @@ -38,6 +38,17 @@ describe('IdPay Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); }); + it('throws payment bad config error accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + error_code: 23, + error_message: 'Some error happened', + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { status: 200, diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index 422d013..f61d1ba 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import { generateUuid } from '../../utils/generateUuid'; import * as API from './api'; @@ -10,6 +10,10 @@ const getHeaders = (apiKey: string, sandbox: boolean) => ({ 'X-API-KEY': apiKey, }); +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); +}; + export const createIdpayDriver = defineDriver({ schema: { config: z.object({ @@ -55,7 +59,9 @@ export const createIdpayDriver = defineDriver({ if ('error_message' in response.data) { const error = response.data as API.RequestPaymentRes_Failed; - throw new RequestException(API.errors[error.error_code.toString()]); + const errorCode = error.error_code.toString(); + throwOnIPGBadConfigError(errorCode); + throw new RequestException(API.errors[errorCode]); } return { method: 'GET', @@ -66,9 +72,10 @@ export const createIdpayDriver = defineDriver({ verify: async ({ ctx, params }) => { const { apiKey, links, sandbox } = ctx; const { id, order_id, status } = params; - - if (status.toString() !== '200') { - throw new PaymentException(API.callbackErrors[status.toString()]); + const statusCode = status.toString(); + if (statusCode !== '200') { + throwOnIPGBadConfigError(statusCode); + throw new PaymentException(API.callbackErrors[statusCode]); } const response = await axios.post( @@ -83,7 +90,9 @@ export const createIdpayDriver = defineDriver({ ); if ('error_message' in response.data) { - throw new VerificationException(API.callbackErrors[response.data.error_code.toString()]); + const errorCode = response.data.error_code.toString(); + throwOnIPGBadConfigError(errorCode); + throw new VerificationException(API.callbackErrors[errorCode]); } return { From f32c622fcff599f5252b7bcc53eb4be6c25e3de2 Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 21:39:12 +0430 Subject: [PATCH 06/31] Add `callBackErrors` to idPay IPG config errors --- src/drivers/idpay/idpay.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index f61d1ba..908f0fb 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -11,7 +11,8 @@ const getHeaders = (apiKey: string, sandbox: boolean) => ({ }); const throwOnIPGBadConfigError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); + if (API.IPGConfigErrors.includes(errorCode)) + throw new BadConfigError(API.errors[errorCode] ?? API.callbackErrors[errorCode], true); }; export const createIdpayDriver = defineDriver({ From 8f2d4be559e8809742535acfed0a91f44654b425 Mon Sep 17 00:00:00 2001 From: Keivan Date: Thu, 15 Sep 2022 21:50:26 +0430 Subject: [PATCH 07/31] Throw `BadConfigError` for Nextpay IPG --- src/drivers/nextpay/api.ts | 48 +++++++++++++++++++++++++++++ src/drivers/nextpay/nextpay.spec.ts | 13 +++++++- src/drivers/nextpay/nextpay.ts | 20 +++++++++--- 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/src/drivers/nextpay/api.ts b/src/drivers/nextpay/api.ts index 0bd2f8e..105fdbd 100644 --- a/src/drivers/nextpay/api.ts +++ b/src/drivers/nextpay/api.ts @@ -238,3 +238,51 @@ export const errors: Record = { '-93': 'موجودی صندوق کاربری برای بازگشت مبلغ کافی نیست', '-94': 'کلید بازگشت مبلغ یافت نشد', }; + +export const IPGConfigErrors = [ + '-20', + '-21', + '-22', + '-23', + '-24', + '-25', + '-26', + '-27', + '-28', + '-29', + '-30', + '-31', + '-32', + '-33', + '-34', + '-35', + '-36', + '-37', + '-38', + '-39', + '-40', + '-41', + '-44', + '-46', + '-47', + '-48', + '-49', + '-50', + '-51', + '-52', + '-60', + '-61', + '-62', + '-63', + '-64', + '-65', + '-66', + '-67', + '-68', + '-69', + '-70', + '-71', + '-73', + '-93', + '-94', +]; diff --git a/src/drivers/nextpay/nextpay.spec.ts b/src/drivers/nextpay/nextpay.spec.ts index 5695c0b..8d9835f 100644 --- a/src/drivers/nextpay/nextpay.spec.ts +++ b/src/drivers/nextpay/nextpay.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createNextpayDriver, NextpayDriver } from './nextpay'; @@ -38,6 +38,17 @@ describe('NextPay Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); }); + it('throws payment bad config errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + trans_id: '1234', + code: -61, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { Shaparak_Ref_Id: '123123123', diff --git a/src/drivers/nextpay/nextpay.ts b/src/drivers/nextpay/nextpay.ts index 5a37100..6f9e071 100644 --- a/src/drivers/nextpay/nextpay.ts +++ b/src/drivers/nextpay/nextpay.ts @@ -3,9 +3,13 @@ import { z } from 'zod'; import { generateUuid } from '../../utils/generateUuid'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); +}; + export const createNextpayDriver = defineDriver({ schema: { config: z.object({ @@ -44,8 +48,11 @@ export const createNextpayDriver = defineDriver({ const { code, trans_id } = response.data; - if (code.toString() !== '0') { - throw new RequestException(API.errors[code.toString()]); + const responseCode = code.toString(); + + if (responseCode !== '0') { + throwOnIPGBadConfigError(responseCode); + throw new RequestException(API.errors[responseCode]); } return { @@ -70,8 +77,11 @@ export const createNextpayDriver = defineDriver({ const { Shaparak_Ref_Id, code, card_holder } = response.data; - if (code.toString() !== '0') { - throw new VerificationException(API.errors[code.toString()]); + const responseCode = code.toString(); + + if (responseCode !== '0') { + throwOnIPGBadConfigError(responseCode); + throw new VerificationException(API.errors[responseCode]); } return { From 781ce146d48c960ff1bb7517a0105fd3267cb508 Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 11:20:06 +0430 Subject: [PATCH 08/31] Add bad IP error code to `IPGConfigErrors` --- src/drivers/idpay/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/drivers/idpay/api.ts b/src/drivers/idpay/api.ts index 8ebf3d6..47e5735 100644 --- a/src/drivers/idpay/api.ts +++ b/src/drivers/idpay/api.ts @@ -275,6 +275,7 @@ export const errors: Record = { export const IPGConfigErrors = [ '11', '12', + '13', '14', '21', '22', From 265b57919594c6a4954f903078cfd7f859b63b4a Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 11:36:19 +0430 Subject: [PATCH 09/31] Throw `BadConfigError` for Payir IPG --- src/drivers/payir/api.ts | 28 ++++++++++++++++++++++++++++ src/drivers/payir/payir.spec.ts | 14 +++++++++++++- src/drivers/payir/payir.ts | 26 +++++++++++++++++--------- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/drivers/payir/api.ts b/src/drivers/payir/api.ts index 9e2f54c..ff3823b 100644 --- a/src/drivers/payir/api.ts +++ b/src/drivers/payir/api.ts @@ -117,3 +117,31 @@ export const errors: Record = { '-25': 'امکان استفاده از سرویس در کشور مبدا شما وجود نداره', '-26': 'امکان انجام تراکنش برای این درگاه وجود ندارد', }; + +export const IPGConfigErrors = [ + '-1', + '-2', + '-3', + '-4', + '-5', + '-6', + '-7', + '-8', + '-9', + '-10', + '-11', + '-12', + '-13', + '-14', + '-15', + '-16', + '-17', + '-18', + '-19', + '-20', + '-21', + '-22', + '-24', + '-25', + '-26', +]; diff --git a/src/drivers/payir/payir.spec.ts b/src/drivers/payir/payir.spec.ts index dae00c3..baf5f51 100644 --- a/src/drivers/payir/payir.spec.ts +++ b/src/drivers/payir/payir.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createPayirDriver, PayirDriver } from './payir'; @@ -41,6 +41,18 @@ describe('Payir Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); }); + it('throws payment bad config errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + status: -10, + errorMessage: 'some error', + token: '1234', + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { status: 1, diff --git a/src/drivers/payir/payir.ts b/src/drivers/payir/payir.ts index d1fe037..d079f49 100644 --- a/src/drivers/payir/payir.ts +++ b/src/drivers/payir/payir.ts @@ -1,11 +1,15 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; const getApiKey = (apiKey: string, sandbox: boolean) => (sandbox ? 'test' : apiKey); +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); +}; + export const createPayirDriver = defineDriver({ schema: { config: z.object({ @@ -45,10 +49,11 @@ export const createPayirDriver = defineDriver({ validCardNumber, }); - const { status } = response.data; + const statusCode = response.data.status.toString(); - if (status.toString() !== '1') { - throw new RequestException(API.errors[status.toString()]); + if (statusCode !== '1') { + throwOnIPGBadConfigError(statusCode); + throw new RequestException(API.errors[statusCode]); } response.data = response.data as API.RequestPaymentRes_Success; @@ -63,8 +68,10 @@ export const createPayirDriver = defineDriver({ const { status, token } = params; const { apiKey, sandbox, links } = ctx; - if (status.toString() !== '1') { - throw new PaymentException(API.errors[status.toString()]); + const statusCode = status.toString(); + if (statusCode !== '1') { + throwOnIPGBadConfigError(statusCode); + throw new PaymentException(API.errors[statusCode]); } const response = await axios.post(links.verify, { @@ -72,10 +79,11 @@ export const createPayirDriver = defineDriver({ token, }); - const verifyStatus = response.data.status; + const verifyStatus = response.data.status.toString(); - if (verifyStatus.toString() !== '1') { - throw new VerificationException(API.errors[verifyStatus.toString()]); + if (verifyStatus !== '1') { + throwOnIPGBadConfigError(verifyStatus); + throw new VerificationException(API.errors[verifyStatus]); } response.data = response.data as API.VerifyPaymentRes_Success; From 67fbc688ce13d3bf3bd9c0974670e99975e6d6fd Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 12:03:58 +0430 Subject: [PATCH 10/31] Throw `BadConfigError` for Sadad IPG --- src/drivers/sadad/api.ts | 36 +++++++++++++++++++++++++++++++++ src/drivers/sadad/sadad.spec.ts | 23 +++++++++++++++++++-- src/drivers/sadad/sadad.ts | 16 ++++++++++++--- 3 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/drivers/sadad/api.ts b/src/drivers/sadad/api.ts index e2b0739..4fdfeae 100644 --- a/src/drivers/sadad/api.ts +++ b/src/drivers/sadad/api.ts @@ -231,3 +231,39 @@ export const verifyErrors: Record = { '-1': 'پارامترهای ارسالی صحیح نیست و يا تراکنش در سیستم وجود ندارد', '101': 'مهلت ارسال تراکنش به پايان رسیده است', }; + +export const IPGConfigErrors = [ + '3', + '23', + '58', + '61', + '1000', + '1001', + '1003', + '1004', + '1011', + '1012', + '1017', + '1018', + '1019', + '1020', + '1023', + '1024', + '1025', + '1026', + '1027', + '1028', + '1029', + '1030', + '1033', + '1036', + '1037', + '1053', + '1055', + '1101', + '1103', + '1104', + '1105', + '-1', + '101', +]; diff --git a/src/drivers/sadad/sadad.spec.ts b/src/drivers/sadad/sadad.spec.ts index f7659f2..7b2eb90 100644 --- a/src/drivers/sadad/sadad.spec.ts +++ b/src/drivers/sadad/sadad.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createSadadDriver, SadadDriver } from './sadad'; @@ -41,7 +41,7 @@ describe('Sadad Driver', () => { it('throws payment errors accordingly', async () => { const serverResponse: API.RequestPaymentRes = { Token: 'some-token', - ResCode: 3, + ResCode: 1068, Description: 'description', }; @@ -57,6 +57,25 @@ describe('Sadad Driver', () => { ).rejects.toThrow(RequestException); }); + it('throws payment bad config errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + Token: 'some-token', + ResCode: 1026, + Description: 'description', + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect( + async () => + await driver.request({ + amount: 20000, + callbackUrl: 'https://callback.url/', + mobile: '09120000000', + }), + ).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { Amount: 10000, diff --git a/src/drivers/sadad/sadad.ts b/src/drivers/sadad/sadad.ts index 6b91d18..0379190 100644 --- a/src/drivers/sadad/sadad.ts +++ b/src/drivers/sadad/sadad.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import * as CryptoJS from 'crypto-js'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import { generateId } from '../../utils/generateId'; import * as API from './api'; @@ -16,6 +16,11 @@ const signData = (message: string, key: string): string => { return encrypted.toString(); }; +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) + throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); +}; + export const createSadadDriver = defineDriver({ schema: { config: z.object({ @@ -61,7 +66,9 @@ export const createSadadDriver = defineDriver({ }); if (response.data.ResCode !== 0) { - throw new RequestException(API.requestErrors[response.data.ResCode.toString()]); + const resCode = response.data.ResCode.toString(); + throwOnIPGBadConfigError(resCode); + throw new RequestException(API.requestErrors[resCode]); } return { @@ -78,6 +85,7 @@ export const createSadadDriver = defineDriver({ const { terminalKey, links } = ctx; if (ResCode !== 0) { + throwOnIPGBadConfigError(ResCode.toString()); throw new PaymentException('تراکنش توسط کاربر لغو شد.'); } @@ -89,7 +97,9 @@ export const createSadadDriver = defineDriver({ const { ResCode: verificationResCode, SystemTraceNo } = response.data; if (verificationResCode !== 0) { - throw new VerificationException(API.verifyErrors[verificationResCode.toString()]); + const resCode = verificationResCode.toString(); + throwOnIPGBadConfigError(resCode); + throw new VerificationException(API.verifyErrors[resCode]); } return { From e94332b03ad8081752d6afa2c4f6a9e93e51b214 Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 17:14:31 +0430 Subject: [PATCH 11/31] Throw `BadConfigError` for Saman IPG --- src/drivers/saman/api.ts | 23 +++++++++++++++++++++++ src/drivers/saman/saman.spec.ts | 20 +++++++++++++++++++- src/drivers/saman/saman.ts | 16 +++++++++++++--- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/drivers/saman/api.ts b/src/drivers/saman/api.ts index 1f6a848..04e086a 100644 --- a/src/drivers/saman/api.ts +++ b/src/drivers/saman/api.ts @@ -111,3 +111,26 @@ export const callbackErrors: Record = { // export interface VerifyPaymentReq {} export type VerifyPaymentRes = number; + +export const IPGConfigErrors = [ + '-1', + '-3', + '-4', + '-6', + '-7', + '-8', + '-9', + '-10', + '-11', + '-12', + '-13', + '-14', + '-15', + '-17', + '-18', + '5', + '8', + '10', + '11', + '12', +]; diff --git a/src/drivers/saman/saman.spec.ts b/src/drivers/saman/saman.spec.ts index f918e2a..7ef0ebe 100644 --- a/src/drivers/saman/saman.spec.ts +++ b/src/drivers/saman/saman.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createSamanDriver, SamanDriver } from './saman'; @@ -59,6 +59,24 @@ describe('Saman Driver', () => { ).rejects.toThrow(RequestException); }); + it('throws payment bad config errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + errorCode: 5, + status: -1, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect( + async () => + await driver.request({ + amount: 20000, + callbackUrl: 'https://mysite.com/callback', + mobile: '09120000000', + }), + ).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = 10000; const callbackParams: API.CallbackParams = { diff --git a/src/drivers/saman/saman.ts b/src/drivers/saman/saman.ts index 1238dd8..1309d58 100644 --- a/src/drivers/saman/saman.ts +++ b/src/drivers/saman/saman.ts @@ -2,9 +2,14 @@ import axios from 'axios'; import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) + throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode], true); +}; + export const createSamanDriver = defineDriver({ schema: { config: z.object({ @@ -41,7 +46,9 @@ export const createSamanDriver = defineDriver({ }); if (response.data.status !== 1 && response.data.errorCode !== undefined) { - throw new RequestException(API.purchaseErrors[response.data.errorCode.toString()]); + const errorCode = response.data.errorCode.toString(); + throwOnIPGBadConfigError(errorCode); + throw new RequestException(API.purchaseErrors[errorCode]); } if (!response.data.token) { @@ -62,7 +69,9 @@ export const createSamanDriver = defineDriver({ const { RefNum: referenceId, TraceNo: transactionId, Status: status } = params; const { merchantId, links } = ctx; if (!referenceId) { - throw new PaymentException(API.purchaseErrors[status.toString()]); + const resCode = status.toString(); + throwOnIPGBadConfigError(resCode); + throw new PaymentException(API.purchaseErrors[resCode]); } const soapClient = await soap.createClientAsync(links.verify); @@ -70,6 +79,7 @@ export const createSamanDriver = defineDriver({ const responseStatus = +(await soapClient.verifyTransaction(referenceId, merchantId)); if (responseStatus < 0) { + throwOnIPGBadConfigError(responseStatus.toString()); throw new VerificationException(API.purchaseErrors[responseStatus]); } From df12b2241af6c10a6d8472b52b9045dc78abcb68 Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 17:54:59 +0430 Subject: [PATCH 12/31] Throw `BadConfigError` for Zarinpal IPG --- src/drivers/zarinpal/api.ts | 2 ++ src/drivers/zarinpal/zarinpal.spec.ts | 15 +++++++++++++-- src/drivers/zarinpal/zarinpal.ts | 17 ++++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/drivers/zarinpal/api.ts b/src/drivers/zarinpal/api.ts index d4a29ea..a5d8720 100644 --- a/src/drivers/zarinpal/api.ts +++ b/src/drivers/zarinpal/api.ts @@ -125,3 +125,5 @@ export const verifyErrors: Record = { '-54': 'اتوریتی نامعتبر است.', '101': 'تراکنش قبلا یک بار تایید شده است.', }; + +export const IPGConfigErrors = ['-9', '-10', '-11', '-12', '-15', '-16', '-50', '-53', '-54', '101']; diff --git a/src/drivers/zarinpal/zarinpal.spec.ts b/src/drivers/zarinpal/zarinpal.spec.ts index 6033547..8e12083 100644 --- a/src/drivers/zarinpal/zarinpal.spec.ts +++ b/src/drivers/zarinpal/zarinpal.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createZarinpalDriver, ZarinpalDriver } from './zarinpal'; @@ -30,7 +30,7 @@ describe('Zarinpal Driver', () => { it('throws payment errors accordingly', async () => { const serverResponse: API.RequestPaymentRes = { data: [], - errors: { code: -11, message: 'Some error happened from zarinpal', validations: [] }, + errors: { code: -111, message: 'Some error happened from zarinpal', validations: [] }, }; mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); @@ -38,6 +38,17 @@ describe('Zarinpal Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); }); + it('throws payment bad config errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + data: [], + errors: { code: -16, message: 'Some error happened from zarinpal', validations: [] }, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { data: { diff --git a/src/drivers/zarinpal/zarinpal.ts b/src/drivers/zarinpal/zarinpal.ts index 683fb0c..f01bc7c 100644 --- a/src/drivers/zarinpal/zarinpal.ts +++ b/src/drivers/zarinpal/zarinpal.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; const getLinks = (links: { request: string; verify: string; payment: string }, sandbox: boolean) => @@ -13,6 +13,11 @@ const getLinks = (links: { request: string; verify: string; payment: string }, s } : links; +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) + throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); +}; + export const createZarinpalDriver = defineDriver({ schema: { config: z.object({ @@ -59,8 +64,9 @@ export const createZarinpalDriver = defineDriver({ if (!Array.isArray(errors)) { // There are errors (`errors` is an object) - const { code } = errors; - throw new RequestException(API.requestErrors[code.toString()]); + const errorCode = errors.code.toString(); + throwOnIPGBadConfigError(errorCode); + throw new RequestException(API.requestErrors[errorCode]); } throw new RequestException(); }, @@ -96,8 +102,9 @@ export const createZarinpalDriver = defineDriver({ if (!Array.isArray(errors)) { // There are errors (`errors` is an object) - const { code } = errors; - throw new VerificationException(API.verifyErrors[code.toString()]); + const errorCode = errors.code.toString(); + throwOnIPGBadConfigError(errorCode); + throw new VerificationException(API.verifyErrors[errorCode]); } throw new VerificationException(); From e4b180a80523ed6837fc1e9d04df1a67710a6485 Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 18:20:27 +0430 Subject: [PATCH 13/31] Throw `BadConfigError` for Zibal IPG --- src/drivers/zibal/api.ts | 2 ++ src/drivers/zibal/zibal.spec.ts | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/drivers/zibal/api.ts b/src/drivers/zibal/api.ts index 8a480ec..efc3c66 100644 --- a/src/drivers/zibal/api.ts +++ b/src/drivers/zibal/api.ts @@ -231,3 +231,5 @@ export const verifyErrors: Record = { '202': 'سفارش پرداخت نشده یا ناموفق بوده است. جهت اطلاعات بیشتر جدول وضعیت‌ها را مطالعه کنید.', '203': 'trackId نامعتبر می‌باشد.', }; + +export const IPGConfigErrors = ['102', '103', '104', '201', '105', '106', '113', '203']; diff --git a/src/drivers/zibal/zibal.spec.ts b/src/drivers/zibal/zibal.spec.ts index 36358d5..6e083a6 100644 --- a/src/drivers/zibal/zibal.spec.ts +++ b/src/drivers/zibal/zibal.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { BadConfigError, RequestException } from '../../exceptions'; import * as API from './api'; import { createZibalDriver, ZibalDriver } from './zibal'; @@ -32,7 +32,7 @@ describe('Zibal Driver', () => { it('throws payment errors accordingly', async () => { const serverResponse: API.RequestPaymentRes = { - result: 102, + result: 1000, message: 'some error', trackId: 1234, }; @@ -42,6 +42,18 @@ describe('Zibal Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); }); + it('throws payment bad config errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + result: 102, + message: 'some error', + trackId: 1234, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { paidAt: '2018-03-25T23:43:01.053000', From a71201e1286517ddfc0dd7362a691b49ae945973 Mon Sep 17 00:00:00 2001 From: Keivan Date: Fri, 16 Sep 2022 18:21:05 +0430 Subject: [PATCH 14/31] Fix `BadConfigError` for Zibal IPG --- src/drivers/zibal/zibal.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/drivers/zibal/zibal.ts b/src/drivers/zibal/zibal.ts index a77fd1d..970cd17 100644 --- a/src/drivers/zibal/zibal.ts +++ b/src/drivers/zibal/zibal.ts @@ -1,11 +1,16 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; const getMerchantId = (merchantId: string, sandbox: boolean) => (sandbox ? 'zibal' : merchantId); +const throwOnIPGBadConfigError = (errorCode: string) => { + if (API.IPGConfigErrors.includes(errorCode)) + throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors, true); +}; + export const createZibalDriver = defineDriver({ schema: { config: z.object({ @@ -48,7 +53,9 @@ export const createZibalDriver = defineDriver({ const { result, trackId } = response.data; if (result !== 100) { - throw new RequestException(API.purchaseErrors[result.toString()]); + const resCode = result.toString(); + throwOnIPGBadConfigError(resCode); + throw new RequestException(API.purchaseErrors[resCode]); } return { @@ -62,7 +69,9 @@ export const createZibalDriver = defineDriver({ const { merchantId, sandbox, links } = ctx; if (success.toString() === '0') { - throw new PaymentException(API.callbackErrors[status]); + const resCode = status.toString(); + throwOnIPGBadConfigError(resCode); + throw new PaymentException(API.callbackErrors[resCode]); } const response = await axios.post(links.verify, { @@ -73,7 +82,9 @@ export const createZibalDriver = defineDriver({ const { result } = response.data; if (result !== 100) { - throw new VerificationException(API.verifyErrors[result.toString()]); + const resCode = result.toString(); + throwOnIPGBadConfigError(resCode); + throw new VerificationException(API.verifyErrors[resCode]); } return { From a16bc8f9b6a23613d0f988640c6d18e5969f120b Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:05:06 +0430 Subject: [PATCH 15/31] Add `UserError` to exceptions --- src/exceptions.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/exceptions.ts b/src/exceptions.ts index 690a9a3..005ce4a 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -66,3 +66,9 @@ export class BadConfigError extends MonopayError { super({ isIPGError, isSafeToDisplay: false, message }); } } + +export class UserError extends MonopayError { + constructor(message: string) { + super({ message, isIPGError: true, isSafeToDisplay: true }); + } +} From b13ca2aa940b412a38883ef0fe3cd133eefbdbaa Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:17:28 +0430 Subject: [PATCH 16/31] Throw `UserError` for Behpardakht IPG --- src/drivers/behpardakht/api.ts | 4 ++++ src/drivers/behpardakht/behpardakht.spec.ts | 16 ++++++++++++++-- src/drivers/behpardakht/behpardakht.ts | 13 +++++++------ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/drivers/behpardakht/api.ts b/src/drivers/behpardakht/api.ts index 08bbb6d..5535b0e 100644 --- a/src/drivers/behpardakht/api.ts +++ b/src/drivers/behpardakht/api.ts @@ -216,3 +216,7 @@ export const IPGConfigErrors = [ '54', '55', ]; + +export const IPGUserErrors = ['11', '12', '13', '14', '15', '16', '17', '18', '19', '111', '112', '113', '114']; + +export const IPGFailureErrors = ['23', '34', '416', '61']; diff --git a/src/drivers/behpardakht/behpardakht.spec.ts b/src/drivers/behpardakht/behpardakht.spec.ts index 2410274..d3abeeb 100644 --- a/src/drivers/behpardakht/behpardakht.spec.ts +++ b/src/drivers/behpardakht/behpardakht.spec.ts @@ -1,5 +1,5 @@ import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { BehpardakhtDriver, createBehpardakhtDriver } from './behpardakht'; @@ -35,7 +35,7 @@ describe('Behpardakht Driver', () => { }); it('throws payment errors accordingly', async () => { - const serverResponse: API.RequestPaymentRes = '11'; + const serverResponse: API.RequestPaymentRes = '34'; mockSoapClient.bpPayRequest = () => serverResponse; await expect( @@ -59,6 +59,18 @@ describe('Behpardakht Driver', () => { ).rejects.toThrow(BadConfigError); }); + it('throws user error for payment accordingly', async () => { + const serverResponse: API.RequestPaymentRes = '19'; + mockSoapClient.bpPayRequest = () => serverResponse; + await expect( + async () => + await driver.request({ + amount: 20000, + callbackUrl: 'https://mysite.com/callback', + }), + ).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = '0'; const callbackParams: API.CallbackParams = { diff --git a/src/drivers/behpardakht/behpardakht.ts b/src/drivers/behpardakht/behpardakht.ts index 144cf7e..9058ab2 100644 --- a/src/drivers/behpardakht/behpardakht.ts +++ b/src/drivers/behpardakht/behpardakht.ts @@ -1,7 +1,7 @@ import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import { generateId } from '../../utils/generateId'; import * as API from './api'; @@ -25,8 +25,9 @@ const timeFormat = (date = new Date()) => { return hh.toString() + mm.toString() + ss.toString(); }; -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); }; export const createBehpardakhtDriver = defineDriver({ @@ -77,7 +78,7 @@ export const createBehpardakhtDriver = defineDriver({ const RefId = splittedResponse[1]; if (ResCode.toString() !== '0') { - throwOnIPGBadConfigError(ResCode); + throwError(ResCode); throw new RequestException(API.errors[ResCode]); } @@ -95,7 +96,7 @@ export const createBehpardakhtDriver = defineDriver({ const { terminalId, username, password, links } = ctx; if (ResCode !== '0') { - throwOnIPGBadConfigError(ResCode); + throwError(ResCode); throw new PaymentException(API.errors[ResCode]); } @@ -117,7 +118,7 @@ export const createBehpardakhtDriver = defineDriver({ if (verifyResponse.toString() !== '43') { soapClient.bpReversalRequest(requestFields); } - throwOnIPGBadConfigError(ResCode); + throwError(ResCode); throw new VerificationException(API.errors[verifyResponse]); } @@ -127,7 +128,7 @@ export const createBehpardakhtDriver = defineDriver({ if (settleResponse.toString() !== '45' && settleResponse.toString() !== '48') { soapClient.bpReversalRequest(requestFields); } - throwOnIPGBadConfigError(ResCode); + throwError(ResCode); throw new VerificationException(API.errors[verifyResponse]); } From f2d0d308957fa6073da4da5496f8cbd3f0dc2873 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:17:53 +0430 Subject: [PATCH 17/31] Throw `UserError` for Idpay IPG --- src/drivers/idpay/api.ts | 2 ++ src/drivers/idpay/idpay.spec.ts | 13 ++++++++++++- src/drivers/idpay/idpay.ts | 12 +++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/drivers/idpay/api.ts b/src/drivers/idpay/api.ts index 47e5735..d6fc71a 100644 --- a/src/drivers/idpay/api.ts +++ b/src/drivers/idpay/api.ts @@ -294,3 +294,5 @@ export const IPGConfigErrors = [ '43', '54', ]; + +export const IPGUserErrors = ['1', '2', '7']; diff --git a/src/drivers/idpay/idpay.spec.ts b/src/drivers/idpay/idpay.spec.ts index c4aa5b0..20c2ccc 100644 --- a/src/drivers/idpay/idpay.spec.ts +++ b/src/drivers/idpay/idpay.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { createIdpayDriver, IdpayDriver } from './idpay'; @@ -49,6 +49,17 @@ describe('IdPay Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); }); + it('throws payment user error accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + error_code: 7, + error_message: 'Some error happened', + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { status: 200, diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index 908f0fb..83095c8 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import { generateUuid } from '../../utils/generateUuid'; import * as API from './api'; @@ -10,9 +10,11 @@ const getHeaders = (apiKey: string, sandbox: boolean) => ({ 'X-API-KEY': apiKey, }); -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode] ?? API.callbackErrors[errorCode], true); + if (API.IPGUserErrors.includes(errorCode)) + throw new UserError(API.errors[errorCode] ?? API.callbackErrors[errorCode]); }; export const createIdpayDriver = defineDriver({ @@ -61,7 +63,7 @@ export const createIdpayDriver = defineDriver({ if ('error_message' in response.data) { const error = response.data as API.RequestPaymentRes_Failed; const errorCode = error.error_code.toString(); - throwOnIPGBadConfigError(errorCode); + throwError(errorCode); throw new RequestException(API.errors[errorCode]); } return { @@ -75,7 +77,7 @@ export const createIdpayDriver = defineDriver({ const { id, order_id, status } = params; const statusCode = status.toString(); if (statusCode !== '200') { - throwOnIPGBadConfigError(statusCode); + throwError(statusCode); throw new PaymentException(API.callbackErrors[statusCode]); } @@ -92,7 +94,7 @@ export const createIdpayDriver = defineDriver({ if ('error_message' in response.data) { const errorCode = response.data.error_code.toString(); - throwOnIPGBadConfigError(errorCode); + throwError(errorCode); throw new VerificationException(API.callbackErrors[errorCode]); } From a3b308d47019ce12f011086bd3dc3c19f0cb701b Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:27:26 +0430 Subject: [PATCH 18/31] Throw `UserError` for Nextpay IPG --- src/drivers/nextpay/api.ts | 2 ++ src/drivers/nextpay/nextpay.spec.ts | 13 ++++++++++++- src/drivers/nextpay/nextpay.ts | 9 +++++---- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/drivers/nextpay/api.ts b/src/drivers/nextpay/api.ts index 105fdbd..42efcda 100644 --- a/src/drivers/nextpay/api.ts +++ b/src/drivers/nextpay/api.ts @@ -286,3 +286,5 @@ export const IPGConfigErrors = [ '-93', '-94', ]; + +export const IPGUserErrors = ['-2', '-4']; diff --git a/src/drivers/nextpay/nextpay.spec.ts b/src/drivers/nextpay/nextpay.spec.ts index 8d9835f..208a05a 100644 --- a/src/drivers/nextpay/nextpay.spec.ts +++ b/src/drivers/nextpay/nextpay.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { createNextpayDriver, NextpayDriver } from './nextpay'; @@ -49,6 +49,17 @@ describe('NextPay Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); }); + it('throws payment user errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + trans_id: '1234', + code: -4, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { Shaparak_Ref_Id: '123123123', diff --git a/src/drivers/nextpay/nextpay.ts b/src/drivers/nextpay/nextpay.ts index 6f9e071..a9e2c49 100644 --- a/src/drivers/nextpay/nextpay.ts +++ b/src/drivers/nextpay/nextpay.ts @@ -3,11 +3,12 @@ import { z } from 'zod'; import { generateUuid } from '../../utils/generateUuid'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import * as API from './api'; -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); }; export const createNextpayDriver = defineDriver({ @@ -51,7 +52,7 @@ export const createNextpayDriver = defineDriver({ const responseCode = code.toString(); if (responseCode !== '0') { - throwOnIPGBadConfigError(responseCode); + throwError(responseCode); throw new RequestException(API.errors[responseCode]); } @@ -80,7 +81,7 @@ export const createNextpayDriver = defineDriver({ const responseCode = code.toString(); if (responseCode !== '0') { - throwOnIPGBadConfigError(responseCode); + throwError(responseCode); throw new VerificationException(API.errors[responseCode]); } From 6300f44cbc1cbbfa9f1f21979033774a732b80e6 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:32:05 +0430 Subject: [PATCH 19/31] Remove unrelated codes from payir `IPGConfigErros` --- src/drivers/payir/api.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/drivers/payir/api.ts b/src/drivers/payir/api.ts index ff3823b..7631a9e 100644 --- a/src/drivers/payir/api.ts +++ b/src/drivers/payir/api.ts @@ -123,7 +123,6 @@ export const IPGConfigErrors = [ '-2', '-3', '-4', - '-5', '-6', '-7', '-8', @@ -133,7 +132,6 @@ export const IPGConfigErrors = [ '-12', '-13', '-14', - '-15', '-16', '-17', '-18', From 91ec21c2d365e6e1b9daa83a248d8b578a3a1b39 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:38:24 +0430 Subject: [PATCH 20/31] Throw `UserError` for Payir IPG --- src/drivers/payir/api.ts | 2 ++ src/drivers/payir/payir.spec.ts | 14 +++++++++++++- src/drivers/payir/payir.ts | 11 ++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/drivers/payir/api.ts b/src/drivers/payir/api.ts index 7631a9e..1c9493b 100644 --- a/src/drivers/payir/api.ts +++ b/src/drivers/payir/api.ts @@ -143,3 +143,5 @@ export const IPGConfigErrors = [ '-25', '-26', ]; + +export const IPGUserErrors = ['-5', '-15']; diff --git a/src/drivers/payir/payir.spec.ts b/src/drivers/payir/payir.spec.ts index baf5f51..f76dd88 100644 --- a/src/drivers/payir/payir.spec.ts +++ b/src/drivers/payir/payir.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { createPayirDriver, PayirDriver } from './payir'; @@ -53,6 +53,18 @@ describe('Payir Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); }); + it('throws payment user errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + status: -5, + errorMessage: 'some error', + token: '1234', + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { status: 1, diff --git a/src/drivers/payir/payir.ts b/src/drivers/payir/payir.ts index d079f49..9c04a8b 100644 --- a/src/drivers/payir/payir.ts +++ b/src/drivers/payir/payir.ts @@ -1,13 +1,14 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import * as API from './api'; const getApiKey = (apiKey: string, sandbox: boolean) => (sandbox ? 'test' : apiKey); -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); }; export const createPayirDriver = defineDriver({ @@ -52,7 +53,7 @@ export const createPayirDriver = defineDriver({ const statusCode = response.data.status.toString(); if (statusCode !== '1') { - throwOnIPGBadConfigError(statusCode); + throwError(statusCode); throw new RequestException(API.errors[statusCode]); } @@ -70,7 +71,7 @@ export const createPayirDriver = defineDriver({ const statusCode = status.toString(); if (statusCode !== '1') { - throwOnIPGBadConfigError(statusCode); + throwError(statusCode); throw new PaymentException(API.errors[statusCode]); } @@ -82,7 +83,7 @@ export const createPayirDriver = defineDriver({ const verifyStatus = response.data.status.toString(); if (verifyStatus !== '1') { - throwOnIPGBadConfigError(verifyStatus); + throwError(verifyStatus); throw new VerificationException(API.errors[verifyStatus]); } From f3c7016fe8c01ac0b8b3b6ee8b2f82d11d3a54c5 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 10:47:00 +0430 Subject: [PATCH 21/31] Throw `UserError` for Saman IPG --- src/drivers/saman/api.ts | 2 ++ src/drivers/saman/saman.spec.ts | 20 +++++++++++++++++++- src/drivers/saman/saman.ts | 12 +++++++----- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/drivers/saman/api.ts b/src/drivers/saman/api.ts index 04e086a..7001cd9 100644 --- a/src/drivers/saman/api.ts +++ b/src/drivers/saman/api.ts @@ -134,3 +134,5 @@ export const IPGConfigErrors = [ '11', '12', ]; + +export const IPGUserErrors = ['1', '4']; diff --git a/src/drivers/saman/saman.spec.ts b/src/drivers/saman/saman.spec.ts index 7ef0ebe..38ff7d9 100644 --- a/src/drivers/saman/saman.spec.ts +++ b/src/drivers/saman/saman.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { createSamanDriver, SamanDriver } from './saman'; @@ -77,6 +77,24 @@ describe('Saman Driver', () => { ).rejects.toThrow(BadConfigError); }); + it('throws payment user errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + errorCode: 1, + status: -1, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect( + async () => + await driver.request({ + amount: 20000, + callbackUrl: 'https://mysite.com/callback', + mobile: '09120000000', + }), + ).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = 10000; const callbackParams: API.CallbackParams = { diff --git a/src/drivers/saman/saman.ts b/src/drivers/saman/saman.ts index 1309d58..21d12d1 100644 --- a/src/drivers/saman/saman.ts +++ b/src/drivers/saman/saman.ts @@ -2,12 +2,14 @@ import axios from 'axios'; import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import * as API from './api'; -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode], true); + if (API.IPGUserErrors.includes(errorCode)) + throw new UserError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]); }; export const createSamanDriver = defineDriver({ @@ -47,7 +49,7 @@ export const createSamanDriver = defineDriver({ if (response.data.status !== 1 && response.data.errorCode !== undefined) { const errorCode = response.data.errorCode.toString(); - throwOnIPGBadConfigError(errorCode); + throwError(errorCode); throw new RequestException(API.purchaseErrors[errorCode]); } @@ -70,7 +72,7 @@ export const createSamanDriver = defineDriver({ const { merchantId, links } = ctx; if (!referenceId) { const resCode = status.toString(); - throwOnIPGBadConfigError(resCode); + throwError(resCode); throw new PaymentException(API.purchaseErrors[resCode]); } @@ -79,7 +81,7 @@ export const createSamanDriver = defineDriver({ const responseStatus = +(await soapClient.verifyTransaction(referenceId, merchantId)); if (responseStatus < 0) { - throwOnIPGBadConfigError(responseStatus.toString()); + throwError(responseStatus.toString()); throw new VerificationException(API.purchaseErrors[responseStatus]); } From 6d2e8dfdd69eb65dfaeb05fb50d7d102cc82f2e5 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 11:04:58 +0430 Subject: [PATCH 22/31] Throw `UserError` for Zarinpal IPG --- src/drivers/zarinpal/api.ts | 2 ++ src/drivers/zarinpal/zarinpal.spec.ts | 13 ++++++++++++- src/drivers/zarinpal/zarinpal.ts | 10 ++++++---- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/drivers/zarinpal/api.ts b/src/drivers/zarinpal/api.ts index a5d8720..5743e08 100644 --- a/src/drivers/zarinpal/api.ts +++ b/src/drivers/zarinpal/api.ts @@ -127,3 +127,5 @@ export const verifyErrors: Record = { }; export const IPGConfigErrors = ['-9', '-10', '-11', '-12', '-15', '-16', '-50', '-53', '-54', '101']; + +export const IPGUserErrors = ['-51']; diff --git a/src/drivers/zarinpal/zarinpal.spec.ts b/src/drivers/zarinpal/zarinpal.spec.ts index 8e12083..f06f817 100644 --- a/src/drivers/zarinpal/zarinpal.spec.ts +++ b/src/drivers/zarinpal/zarinpal.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { createZarinpalDriver, ZarinpalDriver } from './zarinpal'; @@ -49,6 +49,17 @@ describe('Zarinpal Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); }); + it('throws payment user errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + data: [], + errors: { code: -51, message: 'Some error happened from zarinpal', validations: [] }, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { data: { diff --git a/src/drivers/zarinpal/zarinpal.ts b/src/drivers/zarinpal/zarinpal.ts index f01bc7c..7303a32 100644 --- a/src/drivers/zarinpal/zarinpal.ts +++ b/src/drivers/zarinpal/zarinpal.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import * as API from './api'; const getLinks = (links: { request: string; verify: string; payment: string }, sandbox: boolean) => @@ -13,9 +13,11 @@ const getLinks = (links: { request: string; verify: string; payment: string }, s } : links; -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); + if (API.IPGUserErrors.includes(errorCode)) + throw new UserError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); }; export const createZarinpalDriver = defineDriver({ @@ -65,7 +67,7 @@ export const createZarinpalDriver = defineDriver({ if (!Array.isArray(errors)) { // There are errors (`errors` is an object) const errorCode = errors.code.toString(); - throwOnIPGBadConfigError(errorCode); + throwError(errorCode); throw new RequestException(API.requestErrors[errorCode]); } throw new RequestException(); @@ -103,7 +105,7 @@ export const createZarinpalDriver = defineDriver({ if (!Array.isArray(errors)) { // There are errors (`errors` is an object) const errorCode = errors.code.toString(); - throwOnIPGBadConfigError(errorCode); + throwError(errorCode); throw new VerificationException(API.verifyErrors[errorCode]); } From 17054fea2a7fcc1d71e7f01150ea42ba7e4ff991 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 11:14:34 +0430 Subject: [PATCH 23/31] Throw `UserError` for Zibal IPG --- src/drivers/zibal/api.ts | 1 + src/drivers/zibal/zibal.spec.ts | 14 +++++++++++++- src/drivers/zibal/zibal.ts | 12 +++++++----- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/drivers/zibal/api.ts b/src/drivers/zibal/api.ts index efc3c66..4150251 100644 --- a/src/drivers/zibal/api.ts +++ b/src/drivers/zibal/api.ts @@ -233,3 +233,4 @@ export const verifyErrors: Record = { }; export const IPGConfigErrors = ['102', '103', '104', '201', '105', '106', '113', '203']; +export const IPGUserErrors = ['202', '3', '4', '5', '6', '7', '8', '9', '10', '12']; diff --git a/src/drivers/zibal/zibal.spec.ts b/src/drivers/zibal/zibal.spec.ts index 6e083a6..1f7c5ac 100644 --- a/src/drivers/zibal/zibal.spec.ts +++ b/src/drivers/zibal/zibal.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, RequestException, UserError } from '../../exceptions'; import * as API from './api'; import { createZibalDriver, ZibalDriver } from './zibal'; @@ -54,6 +54,18 @@ describe('Zibal Driver', () => { await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(BadConfigError); }); + it('throws payment user errors accordingly', async () => { + const serverResponse: API.RequestPaymentRes = { + result: 6, + message: 'some error', + trackId: 1234, + }; + + mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(UserError); + }); + it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { paidAt: '2018-03-25T23:43:01.053000', diff --git a/src/drivers/zibal/zibal.ts b/src/drivers/zibal/zibal.ts index 970cd17..dbc1396 100644 --- a/src/drivers/zibal/zibal.ts +++ b/src/drivers/zibal/zibal.ts @@ -1,14 +1,16 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; import * as API from './api'; const getMerchantId = (merchantId: string, sandbox: boolean) => (sandbox ? 'zibal' : merchantId); -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors, true); + if (API.IPGUserErrors.includes(errorCode)) + throw new UserError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors); }; export const createZibalDriver = defineDriver({ @@ -54,7 +56,7 @@ export const createZibalDriver = defineDriver({ if (result !== 100) { const resCode = result.toString(); - throwOnIPGBadConfigError(resCode); + throwError(resCode); throw new RequestException(API.purchaseErrors[resCode]); } @@ -70,7 +72,7 @@ export const createZibalDriver = defineDriver({ if (success.toString() === '0') { const resCode = status.toString(); - throwOnIPGBadConfigError(resCode); + throwError(resCode); throw new PaymentException(API.callbackErrors[resCode]); } @@ -83,7 +85,7 @@ export const createZibalDriver = defineDriver({ if (result !== 100) { const resCode = result.toString(); - throwOnIPGBadConfigError(resCode); + throwError(resCode); throw new VerificationException(API.verifyErrors[resCode]); } From a155fcc651a6c4d925aa5fc81d49112ec2299be4 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 11:28:47 +0430 Subject: [PATCH 24/31] Throw `BadConfigError` for signing failure --- src/drivers/pasargad/pasargad.spec.ts | 20 +++++++++++++++++--- src/drivers/pasargad/pasargad.ts | 18 +++++++++++------- src/drivers/sadad/sadad.ts | 16 ++++++++++------ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/drivers/pasargad/pasargad.spec.ts b/src/drivers/pasargad/pasargad.spec.ts index 53dcf8c..ecea73c 100644 --- a/src/drivers/pasargad/pasargad.spec.ts +++ b/src/drivers/pasargad/pasargad.spec.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import * as fs from 'fs/promises'; import { Receipt } from '../../driver'; import { getPaymentDriver } from '../../drivers'; -import { RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; import { createPasargadDriver, PasargadDriver } from './pasargad'; @@ -22,8 +22,6 @@ const mockKey = ` io7Xyef97NzU5qg0ULDKzBEo+BolEotN0799aNtfRZTzZ08kPGTMF7X0ZSmvcNqfTu4+7wKNRNH/fq47pj0ESNsWVt1FkQu/upp6uTzdiFF2xjcouA8NCLhdV1/VJjtINJq3M8AUT8Qa5VvDTbzL5bxyvWfIqxZVWU0k7XGEVak= `; -mockedFs.readFile.mockResolvedValue(Buffer.from(mockKey)); - describe('Pasargad', () => { let driver: PasargadDriver; @@ -42,6 +40,7 @@ describe('Pasargad', () => { Token: 'PAYMENT_TOKEN', }; mockedAxios.post.mockResolvedValueOnce({ data: getTokenResponse }); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from(mockKey)); expect( typeof ( @@ -62,6 +61,7 @@ describe('Pasargad', () => { Message: 'تراکنش ارسالی معتبر نیست', }; mockedAxios.post.mockResolvedValueOnce({ data: getTokenResponse }); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from(mockKey)); await expect( async () => @@ -89,6 +89,7 @@ describe('Pasargad', () => { }; mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from(mockKey)); expect( await driver.verify({ amount: 2000 }, { iD: new Date().toISOString(), iN: '123', tref: '123456' }), @@ -101,6 +102,8 @@ describe('Pasargad', () => { Message: 'تراکنش ارسالی معتبر نیست', }; mockedAxios.post.mockResolvedValueOnce({ data: verifyResponse }); + mockedFs.readFile.mockResolvedValueOnce(Buffer.from(mockKey)); + const driver = getPaymentDriver('pasargad')({ privateKeyXMLFile: './something.xml', merchantId: '123', @@ -110,4 +113,15 @@ describe('Pasargad', () => { driver.verify({ amount: 2000 }, { iD: new Date().toISOString(), iN: '123', tref: '1234' }), ).rejects.toThrow(VerificationException); }); + it('throws Bad config error on invalid key', async () => { + mockedFs.readFile.mockResolvedValueOnce(Buffer.from('--wrong rsa-xml--')); + const driver = getPaymentDriver('pasargad')({ + privateKeyXMLFile: './something.xml', + merchantId: '123', + terminalId: '123', + }); + await expect( + driver.verify({ amount: 2000 }, { iD: new Date().toISOString(), iN: '123', tref: '1234' }), + ).rejects.toThrow(BadConfigError); + }); }); diff --git a/src/drivers/pasargad/pasargad.ts b/src/drivers/pasargad/pasargad.ts index 18c8eca..0ec2849 100644 --- a/src/drivers/pasargad/pasargad.ts +++ b/src/drivers/pasargad/pasargad.ts @@ -4,7 +4,7 @@ import * as crypto from 'crypto'; import * as fs from 'fs/promises'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, RequestException, VerificationException } from '../../exceptions'; import * as API from './api'; const getCurrentTimestamp = (): string => { @@ -13,12 +13,16 @@ const getCurrentTimestamp = (): string => { }; const signData = async (data: unknown, privateKeyXMLFile: string): Promise => { - const sign = crypto.createSign('SHA1'); - sign.write(JSON.stringify(data)); - sign.end(); - const pemKey = await convertXmlToPemKey(privateKeyXMLFile); - const signedData = sign.sign(Buffer.from(pemKey), 'base64'); - return signedData; + try { + const sign = crypto.createSign('SHA1'); + sign.write(JSON.stringify(data)); + sign.end(); + const pemKey = await convertXmlToPemKey(privateKeyXMLFile); + const signedData = sign.sign(Buffer.from(pemKey), 'base64'); + return signedData; + } catch (err) { + throw new BadConfigError('The signing process has failed. details: ' + err, false); + } }; const convertXmlToPemKey = async (xmlFilePath: string): Promise => { diff --git a/src/drivers/sadad/sadad.ts b/src/drivers/sadad/sadad.ts index 0379190..000c6a5 100644 --- a/src/drivers/sadad/sadad.ts +++ b/src/drivers/sadad/sadad.ts @@ -7,13 +7,17 @@ import { generateId } from '../../utils/generateId'; import * as API from './api'; const signData = (message: string, key: string): string => { - const keyHex = CryptoJS.enc.Utf8.parse(key); - const encrypted = CryptoJS.DES.encrypt(message, keyHex, { - mode: CryptoJS.mode.ECB, - padding: CryptoJS.pad.Pkcs7, - }); + try { + const keyHex = CryptoJS.enc.Utf8.parse(key); + const encrypted = CryptoJS.DES.encrypt(message, keyHex, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }); - return encrypted.toString(); + return encrypted.toString(); + } catch (err) { + throw new BadConfigError('The signing process has failed. details: ' + err, false); + } }; const throwOnIPGBadConfigError = (errorCode: string) => { From abc66587bb4790e30930df1d23fd149c627525b4 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 11:41:34 +0430 Subject: [PATCH 25/31] Add `GatewayFailureError` to exceptions --- src/exceptions.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/exceptions.ts b/src/exceptions.ts index 005ce4a..30dfeab 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -59,7 +59,7 @@ export class VerificationException extends BasePaymentException { } /** - * Error when the configuration has problems + * Denotes an error caused by developer configuration */ export class BadConfigError extends MonopayError { constructor(message: string, isIPGError: boolean) { @@ -67,8 +67,20 @@ export class BadConfigError extends MonopayError { } } +/** + * Denotes an error caused by end user + */ export class UserError extends MonopayError { constructor(message: string) { super({ message, isIPGError: true, isSafeToDisplay: true }); } } + +/** + * Denotes an error either caused by a failure from gateway or an unrecognizable reason + */ +export class GatewayFailureError extends MonopayError { + constructor(message: string) { + super({ message, isIPGError: true, isSafeToDisplay: false }); + } +} From 6e87197f1badd616b3e8cb1b858e6f82a282615b Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 12:22:42 +0430 Subject: [PATCH 26/31] Make `GatewayFailureError` message optional --- src/exceptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exceptions.ts b/src/exceptions.ts index 30dfeab..239b3e8 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -80,7 +80,7 @@ export class UserError extends MonopayError { * Denotes an error either caused by a failure from gateway or an unrecognizable reason */ export class GatewayFailureError extends MonopayError { - constructor(message: string) { + constructor(message?: string) { super({ message, isIPGError: true, isSafeToDisplay: false }); } } From 747a8a5abc75a2bd7e57720284f05fab57fed20c Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 12:29:57 +0430 Subject: [PATCH 27/31] Throw `GatewayFailureError` in all drivers --- src/drivers/behpardakht/behpardakht.spec.ts | 6 +++--- src/drivers/behpardakht/behpardakht.ts | 7 ++----- src/drivers/idpay/idpay.spec.ts | 4 ++-- src/drivers/idpay/idpay.ts | 10 +++++----- src/drivers/nextpay/nextpay.spec.ts | 4 ++-- src/drivers/nextpay/nextpay.ts | 7 +++---- src/drivers/parsian/parsian.spec.ts | 4 ++-- src/drivers/parsian/parsian.ts | 10 +++++----- src/drivers/pasargad/pasargad.spec.ts | 6 +++--- src/drivers/pasargad/pasargad.ts | 6 +++--- src/drivers/payir/payir.spec.ts | 4 ++-- src/drivers/payir/payir.ts | 6 ++---- src/drivers/payping/payping.spec.ts | 4 ++-- src/drivers/payping/payping.ts | 6 +++--- src/drivers/sadad/sadad.spec.ts | 4 ++-- src/drivers/sadad/sadad.ts | 14 ++++++------- src/drivers/saman/saman.spec.ts | 4 ++-- src/drivers/saman/saman.ts | 8 +++----- src/drivers/vandar/vandar.spec.ts | 9 ++++----- src/drivers/vandar/vandar.ts | 22 ++++++--------------- src/drivers/zarinpal/zarinpal.spec.ts | 4 ++-- src/drivers/zarinpal/zarinpal.ts | 19 +++++++----------- src/drivers/zibal/zibal.spec.ts | 4 ++-- src/drivers/zibal/zibal.ts | 6 ++---- 24 files changed, 75 insertions(+), 103 deletions(-) diff --git a/src/drivers/behpardakht/behpardakht.spec.ts b/src/drivers/behpardakht/behpardakht.spec.ts index d3abeeb..aa1cfb3 100644 --- a/src/drivers/behpardakht/behpardakht.spec.ts +++ b/src/drivers/behpardakht/behpardakht.spec.ts @@ -1,5 +1,5 @@ import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { BehpardakhtDriver, createBehpardakhtDriver } from './behpardakht'; @@ -34,7 +34,7 @@ describe('Behpardakht Driver', () => { ).toBe('string'); }); - it('throws payment errors accordingly', async () => { + it('throws payment failure accordingly', async () => { const serverResponse: API.RequestPaymentRes = '34'; mockSoapClient.bpPayRequest = () => serverResponse; @@ -44,7 +44,7 @@ describe('Behpardakht Driver', () => { amount: 20000, callbackUrl: 'https://mysite.com/callback', }), - ).rejects.toThrow(RequestException); + ).rejects.toThrow(GatewayFailureError); }); it('throws bad config error for payment accordingly', async () => { diff --git a/src/drivers/behpardakht/behpardakht.ts b/src/drivers/behpardakht/behpardakht.ts index 9058ab2..935d97c 100644 --- a/src/drivers/behpardakht/behpardakht.ts +++ b/src/drivers/behpardakht/behpardakht.ts @@ -1,7 +1,7 @@ import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import { generateId } from '../../utils/generateId'; import * as API from './api'; @@ -28,6 +28,7 @@ const timeFormat = (date = new Date()) => { const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); + throw new GatewayFailureError(API.errors[errorCode]); }; export const createBehpardakhtDriver = defineDriver({ @@ -79,7 +80,6 @@ export const createBehpardakhtDriver = defineDriver({ if (ResCode.toString() !== '0') { throwError(ResCode); - throw new RequestException(API.errors[ResCode]); } return { @@ -97,7 +97,6 @@ export const createBehpardakhtDriver = defineDriver({ if (ResCode !== '0') { throwError(ResCode); - throw new PaymentException(API.errors[ResCode]); } const soapClient = await soap.createClientAsync(links.verify); @@ -119,7 +118,6 @@ export const createBehpardakhtDriver = defineDriver({ soapClient.bpReversalRequest(requestFields); } throwError(ResCode); - throw new VerificationException(API.errors[verifyResponse]); } // 2. Settle @@ -129,7 +127,6 @@ export const createBehpardakhtDriver = defineDriver({ soapClient.bpReversalRequest(requestFields); } throwError(ResCode); - throw new VerificationException(API.errors[verifyResponse]); } return { diff --git a/src/drivers/idpay/idpay.spec.ts b/src/drivers/idpay/idpay.spec.ts index 20c2ccc..eb1c2a7 100644 --- a/src/drivers/idpay/idpay.spec.ts +++ b/src/drivers/idpay/idpay.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { createIdpayDriver, IdpayDriver } from './idpay'; @@ -35,7 +35,7 @@ describe('IdPay Driver', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config error accordingly', async () => { diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index 83095c8..471d9e1 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -1,9 +1,10 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import { generateUuid } from '../../utils/generateUuid'; import * as API from './api'; +import { RequestPaymenRes_Successful, VerifyPaymentRes_Successful } from './api'; const getHeaders = (apiKey: string, sandbox: boolean) => ({ 'X-SANDBOX': sandbox ? '1' : '0', @@ -15,6 +16,7 @@ const throwError = (errorCode: string) => { throw new BadConfigError(API.errors[errorCode] ?? API.callbackErrors[errorCode], true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode] ?? API.callbackErrors[errorCode]); + throw new GatewayFailureError(API.errors[errorCode]); }; export const createIdpayDriver = defineDriver({ @@ -64,8 +66,8 @@ export const createIdpayDriver = defineDriver({ const error = response.data as API.RequestPaymentRes_Failed; const errorCode = error.error_code.toString(); throwError(errorCode); - throw new RequestException(API.errors[errorCode]); } + response.data = response.data as RequestPaymenRes_Successful; return { method: 'GET', referenceId: response.data.id, @@ -78,7 +80,6 @@ export const createIdpayDriver = defineDriver({ const statusCode = status.toString(); if (statusCode !== '200') { throwError(statusCode); - throw new PaymentException(API.callbackErrors[statusCode]); } const response = await axios.post( @@ -95,9 +96,8 @@ export const createIdpayDriver = defineDriver({ if ('error_message' in response.data) { const errorCode = response.data.error_code.toString(); throwError(errorCode); - throw new VerificationException(API.callbackErrors[errorCode]); } - + response.data = response.data as VerifyPaymentRes_Successful; return { transactionId: response.data.track_id, cardPan: response.data.payment.card_no, diff --git a/src/drivers/nextpay/nextpay.spec.ts b/src/drivers/nextpay/nextpay.spec.ts index 208a05a..cb64ecb 100644 --- a/src/drivers/nextpay/nextpay.spec.ts +++ b/src/drivers/nextpay/nextpay.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { createNextpayDriver, NextpayDriver } from './nextpay'; @@ -35,7 +35,7 @@ describe('NextPay Driver', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config errors accordingly', async () => { diff --git a/src/drivers/nextpay/nextpay.ts b/src/drivers/nextpay/nextpay.ts index a9e2c49..fd8a35c 100644 --- a/src/drivers/nextpay/nextpay.ts +++ b/src/drivers/nextpay/nextpay.ts @@ -3,12 +3,13 @@ import { z } from 'zod'; import { generateUuid } from '../../utils/generateUuid'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); + throw new GatewayFailureError(API.errors[errorCode]); }; export const createNextpayDriver = defineDriver({ @@ -53,7 +54,6 @@ export const createNextpayDriver = defineDriver({ if (responseCode !== '0') { throwError(responseCode); - throw new RequestException(API.errors[responseCode]); } return { @@ -67,7 +67,7 @@ export const createNextpayDriver = defineDriver({ const { apiKey, links } = ctx; if (!trans_id) { - throw new PaymentException('تراکنش توسط کاربر لغو شد.'); + throw new UserError('تراکنش توسط کاربر لغو شد.'); } const response = await axios.post(links.verify, { @@ -82,7 +82,6 @@ export const createNextpayDriver = defineDriver({ if (responseCode !== '0') { throwError(responseCode); - throw new VerificationException(API.errors[responseCode]); } return { diff --git a/src/drivers/parsian/parsian.spec.ts b/src/drivers/parsian/parsian.spec.ts index e433f55..f954dbb 100644 --- a/src/drivers/parsian/parsian.spec.ts +++ b/src/drivers/parsian/parsian.spec.ts @@ -1,5 +1,5 @@ import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { GatewayFailureError } from '../../exceptions'; import * as API from './api'; import { createParsianDriver, ParsianDriver } from './parsian'; @@ -46,7 +46,7 @@ describe('Parsian Driver', () => { amount: 20000, callbackUrl: 'https://mysite.com/callback', }), - ).rejects.toThrow(RequestException); + ).rejects.toThrow(GatewayFailureError); }); it('verifies the purchase correctly', async () => { diff --git a/src/drivers/parsian/parsian.ts b/src/drivers/parsian/parsian.ts index cd98e3a..55ba6fb 100644 --- a/src/drivers/parsian/parsian.ts +++ b/src/drivers/parsian/parsian.ts @@ -1,7 +1,7 @@ import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { GatewayFailureError, UserError } from '../../exceptions'; import { generateId } from '../../utils/generateId'; import * as API from './api'; @@ -42,7 +42,7 @@ export const createParsianDriver = defineDriver({ const { Status, Token } = response; if (Status.toString() !== '0' || typeof Token === 'undefined') { - throw new RequestException('خطایی در درخواست پرداخت به‌وجود آمد'); + throw new GatewayFailureError('خطایی در درخواست پرداخت به‌وجود آمد'); } return { @@ -57,7 +57,7 @@ export const createParsianDriver = defineDriver({ const { merchantId, links } = ctx; if (status.toString() !== '0') { - throw new PaymentException('تراکنش توسط کاربر لغو شد.'); + throw new UserError('تراکنش توسط کاربر لغو شد.'); } const soapClient = await soap.createClientAsync(links.verify); @@ -75,9 +75,9 @@ export const createParsianDriver = defineDriver({ const reversalRequestFields: API.ReversalPaymentReq = requestFields; const reversalResponse: API.ReversalPaymentRes = soapClient.ReversalRequest(reversalRequestFields); if (reversalResponse.Status !== '0') { - throw new VerificationException('خطایی در تایید پرداخت به‌وجود آمد و مبلغ بازگشته نشد.'); + throw new GatewayFailureError('خطایی در تایید پرداخت به‌وجود آمد و مبلغ بازگشته نشد.'); } - throw new VerificationException('خطایی در تایید پرداخت به‌وجود آمد'); + throw new GatewayFailureError('خطایی در تایید پرداخت به‌وجود آمد'); } return { diff --git a/src/drivers/pasargad/pasargad.spec.ts b/src/drivers/pasargad/pasargad.spec.ts index ecea73c..e8180d2 100644 --- a/src/drivers/pasargad/pasargad.spec.ts +++ b/src/drivers/pasargad/pasargad.spec.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import * as fs from 'fs/promises'; import { Receipt } from '../../driver'; import { getPaymentDriver } from '../../drivers'; -import { BadConfigError, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError } from '../../exceptions'; import * as API from './api'; import { createPasargadDriver, PasargadDriver } from './pasargad'; @@ -71,7 +71,7 @@ describe('Pasargad', () => { invoiceDate: new Date().toISOString(), invoiceNumber: '12', }), - ).rejects.toThrow(RequestException); + ).rejects.toThrow(GatewayFailureError); }); it('verifies the purchase correctly', async () => { const serverResponse: API.VerifyPaymentRes = { @@ -111,7 +111,7 @@ describe('Pasargad', () => { }); await expect( driver.verify({ amount: 2000 }, { iD: new Date().toISOString(), iN: '123', tref: '1234' }), - ).rejects.toThrow(VerificationException); + ).rejects.toThrow(GatewayFailureError); }); it('throws Bad config error on invalid key', async () => { mockedFs.readFile.mockResolvedValueOnce(Buffer.from('--wrong rsa-xml--')); diff --git a/src/drivers/pasargad/pasargad.ts b/src/drivers/pasargad/pasargad.ts index 0ec2849..9416428 100644 --- a/src/drivers/pasargad/pasargad.ts +++ b/src/drivers/pasargad/pasargad.ts @@ -4,7 +4,7 @@ import * as crypto from 'crypto'; import * as fs from 'fs/promises'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError } from '../../exceptions'; import * as API from './api'; const getCurrentTimestamp = (): string => { @@ -87,7 +87,7 @@ export const createPasargadDriver = defineDriver({ }); if (!response.data?.IsSuccess) { - throw new RequestException(errorMessage); + throw new GatewayFailureError(errorMessage); } return { method: 'GET', @@ -115,7 +115,7 @@ export const createPasargadDriver = defineDriver({ Sign: await signData(data, privateKeyXMLFile), }, }); - if (!response.data?.IsSuccess) throw new VerificationException(errorMessage); + if (!response.data?.IsSuccess) throw new GatewayFailureError(errorMessage); return { raw: response.data, transactionId: tref, diff --git a/src/drivers/payir/payir.spec.ts b/src/drivers/payir/payir.spec.ts index f76dd88..cc367bc 100644 --- a/src/drivers/payir/payir.spec.ts +++ b/src/drivers/payir/payir.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { createPayirDriver, PayirDriver } from './payir'; @@ -38,7 +38,7 @@ describe('Payir Driver', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config errors accordingly', async () => { diff --git a/src/drivers/payir/payir.ts b/src/drivers/payir/payir.ts index 9c04a8b..60796d3 100644 --- a/src/drivers/payir/payir.ts +++ b/src/drivers/payir/payir.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; const getApiKey = (apiKey: string, sandbox: boolean) => (sandbox ? 'test' : apiKey); @@ -9,6 +9,7 @@ const getApiKey = (apiKey: string, sandbox: boolean) => (sandbox ? 'test' : apiK const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); + throw new GatewayFailureError(API.errors[errorCode]); }; export const createPayirDriver = defineDriver({ @@ -54,7 +55,6 @@ export const createPayirDriver = defineDriver({ if (statusCode !== '1') { throwError(statusCode); - throw new RequestException(API.errors[statusCode]); } response.data = response.data as API.RequestPaymentRes_Success; @@ -72,7 +72,6 @@ export const createPayirDriver = defineDriver({ const statusCode = status.toString(); if (statusCode !== '1') { throwError(statusCode); - throw new PaymentException(API.errors[statusCode]); } const response = await axios.post(links.verify, { @@ -84,7 +83,6 @@ export const createPayirDriver = defineDriver({ if (verifyStatus !== '1') { throwError(verifyStatus); - throw new VerificationException(API.errors[verifyStatus]); } response.data = response.data as API.VerifyPaymentRes_Success; diff --git a/src/drivers/payping/payping.spec.ts b/src/drivers/payping/payping.spec.ts index e891fb5..d82dd85 100644 --- a/src/drivers/payping/payping.spec.ts +++ b/src/drivers/payping/payping.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { GatewayFailureError } from '../../exceptions'; import * as API from './api'; import { createPaypingDriver, PaypingDriver } from './payping'; @@ -31,7 +31,7 @@ describe('PayPing Driver', () => { // mockedAxios.post.mockRejectedValueOnce({ response: { status: 401 } }); mockedAxios.post.mockReturnValueOnce(Promise.reject({ response: { status: 401 } })); - expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); + expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(GatewayFailureError); }); it('verifies the purchase correctly', async () => { diff --git a/src/drivers/payping/payping.ts b/src/drivers/payping/payping.ts index 9095256..7aeb6c1 100644 --- a/src/drivers/payping/payping.ts +++ b/src/drivers/payping/payping.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { RequestException, VerificationException } from '../../exceptions'; +import { GatewayFailureError } from '../../exceptions'; import * as API from './api'; const statusToMessage = (status = 500) => { @@ -64,7 +64,7 @@ export const createPaypingDriver = defineDriver({ }, ); } catch (error) { - throw new RequestException(statusToMessage((error as any).response.status)); + throw new GatewayFailureError(statusToMessage((error as any).response.status)); } const { code } = response.data; @@ -95,7 +95,7 @@ export const createPaypingDriver = defineDriver({ }, ); } catch (error) { - throw new VerificationException(statusToMessage((error as any).response.status)); + throw new GatewayFailureError(statusToMessage((error as any).response.status)); } const { cardNumber } = response.data; diff --git a/src/drivers/sadad/sadad.spec.ts b/src/drivers/sadad/sadad.spec.ts index 7b2eb90..7d396dc 100644 --- a/src/drivers/sadad/sadad.spec.ts +++ b/src/drivers/sadad/sadad.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError } from '../../exceptions'; import * as API from './api'; import { createSadadDriver, SadadDriver } from './sadad'; @@ -54,7 +54,7 @@ describe('Sadad Driver', () => { callbackUrl: 'https://callback.url/', mobile: '09120000000', }), - ).rejects.toThrow(RequestException); + ).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config errors accordingly', async () => { diff --git a/src/drivers/sadad/sadad.ts b/src/drivers/sadad/sadad.ts index 000c6a5..bf8e8cb 100644 --- a/src/drivers/sadad/sadad.ts +++ b/src/drivers/sadad/sadad.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import * as CryptoJS from 'crypto-js'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError } from '../../exceptions'; import { generateId } from '../../utils/generateId'; import * as API from './api'; @@ -20,9 +20,10 @@ const signData = (message: string, key: string): string => { } }; -const throwOnIPGBadConfigError = (errorCode: string) => { +const throwError = (errorCode: string) => { if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); + throw new GatewayFailureError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); }; export const createSadadDriver = defineDriver({ @@ -71,8 +72,7 @@ export const createSadadDriver = defineDriver({ if (response.data.ResCode !== 0) { const resCode = response.data.ResCode.toString(); - throwOnIPGBadConfigError(resCode); - throw new RequestException(API.requestErrors[resCode]); + throwError(resCode); } return { @@ -89,8 +89,7 @@ export const createSadadDriver = defineDriver({ const { terminalKey, links } = ctx; if (ResCode !== 0) { - throwOnIPGBadConfigError(ResCode.toString()); - throw new PaymentException('تراکنش توسط کاربر لغو شد.'); + throwError(ResCode.toString()); } const response = await axios.post(links.verify, { @@ -102,8 +101,7 @@ export const createSadadDriver = defineDriver({ if (verificationResCode !== 0) { const resCode = verificationResCode.toString(); - throwOnIPGBadConfigError(resCode); - throw new VerificationException(API.verifyErrors[resCode]); + throwError(resCode); } return { diff --git a/src/drivers/saman/saman.spec.ts b/src/drivers/saman/saman.spec.ts index 38ff7d9..5ce910c 100644 --- a/src/drivers/saman/saman.spec.ts +++ b/src/drivers/saman/saman.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { createSamanDriver, SamanDriver } from './saman'; @@ -56,7 +56,7 @@ describe('Saman Driver', () => { callbackUrl: 'https://mysite.com/callback', mobile: '09120000000', }), - ).rejects.toThrow(RequestException); + ).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config errors accordingly', async () => { diff --git a/src/drivers/saman/saman.ts b/src/drivers/saman/saman.ts index 21d12d1..b8a91ae 100644 --- a/src/drivers/saman/saman.ts +++ b/src/drivers/saman/saman.ts @@ -2,7 +2,7 @@ import axios from 'axios'; import * as soap from 'soap'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; const throwError = (errorCode: string) => { @@ -10,6 +10,7 @@ const throwError = (errorCode: string) => { throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode], true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]); + throw new GatewayFailureError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]); }; export const createSamanDriver = defineDriver({ @@ -50,11 +51,10 @@ export const createSamanDriver = defineDriver({ if (response.data.status !== 1 && response.data.errorCode !== undefined) { const errorCode = response.data.errorCode.toString(); throwError(errorCode); - throw new RequestException(API.purchaseErrors[errorCode]); } if (!response.data.token) { - throw new RequestException(); + throw new GatewayFailureError(); } return { @@ -73,7 +73,6 @@ export const createSamanDriver = defineDriver({ if (!referenceId) { const resCode = status.toString(); throwError(resCode); - throw new PaymentException(API.purchaseErrors[resCode]); } const soapClient = await soap.createClientAsync(links.verify); @@ -82,7 +81,6 @@ export const createSamanDriver = defineDriver({ if (responseStatus < 0) { throwError(responseStatus.toString()); - throw new VerificationException(API.purchaseErrors[responseStatus]); } return { diff --git a/src/drivers/vandar/vandar.spec.ts b/src/drivers/vandar/vandar.spec.ts index 2ab464e..795d0f3 100644 --- a/src/drivers/vandar/vandar.spec.ts +++ b/src/drivers/vandar/vandar.spec.ts @@ -1,7 +1,6 @@ import axios from 'axios'; -import { PaymentException, VerificationException } from '../..'; import { Receipt } from '../../driver'; -import { RequestException } from '../../exceptions'; +import { GatewayFailureError } from '../../exceptions'; import * as API from './api'; import { createVandarDriver, VandarDriver } from './vandar'; @@ -39,7 +38,7 @@ describe('Vandar Driver', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - expect(driver.request({ amount: 2000, callbackUrl: 'https://example.com' })).rejects.toThrow(RequestException); + expect(driver.request({ amount: 2000, callbackUrl: 'https://example.com' })).rejects.toThrow(GatewayFailureError); }); it('should verify the purchase', async () => { @@ -77,7 +76,7 @@ describe('Vandar Driver', () => { const payment_status = 'NOK'; // Not documented! const amount = 2000; - expect(driver.verify({ amount }, { token, payment_status })).rejects.toThrow(PaymentException); + expect(driver.verify({ amount }, { token, payment_status })).rejects.toThrow(GatewayFailureError); }); it('should throw payment error', async () => { @@ -92,6 +91,6 @@ describe('Vandar Driver', () => { const payment_status = 'OK'; const amount = 2000; - expect(driver.verify({ amount }, { token, payment_status })).rejects.toThrow(VerificationException); + expect(driver.verify({ amount }, { token, payment_status })).rejects.toThrow(GatewayFailureError); }); }); diff --git a/src/drivers/vandar/vandar.ts b/src/drivers/vandar/vandar.ts index f175179..61c9869 100644 --- a/src/drivers/vandar/vandar.ts +++ b/src/drivers/vandar/vandar.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { PaymentException, RequestException, VerificationException } from '../../exceptions'; +import { GatewayFailureError } from '../../exceptions'; import * as API from './api'; export const createVandarDriver = defineDriver({ @@ -62,12 +62,9 @@ export const createVandarDriver = defineDriver({ ); const { errors, token } = response.data; - if (errors?.length) { - throw new RequestException(errors.join('\n')); - } + if (errors?.length) throw new GatewayFailureError(errors.join('\n')); - // TODO: Throw an approperiate error here - if (!token) throw Error('No token provided'); + if (!token) throw new GatewayFailureError('No token was provided by the IPG'); return { method: 'GET', @@ -79,9 +76,7 @@ export const createVandarDriver = defineDriver({ const { token, payment_status } = params; const { api_key, links } = ctx; - if (payment_status !== 'OK') { - throw new PaymentException(); - } + if (payment_status !== 'OK') throw new GatewayFailureError(); const response = await axios.post( links.verify, @@ -96,14 +91,9 @@ export const createVandarDriver = defineDriver({ ); const { errors, transId, cardNumber } = response.data; - if (errors?.length) { - throw new VerificationException(errors.join('\n')); - } + if (errors?.length) throw new GatewayFailureError(errors.join('\n')); - // TODO: Throw an approperiate error here - if (typeof transId === 'undefined') { - throw Error('No transaction ID provided'); - } + if (transId === undefined) throw new GatewayFailureError('No transaction ID was provided by the IPG'); return { transactionId: transId, diff --git a/src/drivers/zarinpal/zarinpal.spec.ts b/src/drivers/zarinpal/zarinpal.spec.ts index f06f817..5073be4 100644 --- a/src/drivers/zarinpal/zarinpal.spec.ts +++ b/src/drivers/zarinpal/zarinpal.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { createZarinpalDriver, ZarinpalDriver } from './zarinpal'; @@ -35,7 +35,7 @@ describe('Zarinpal Driver', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config errors accordingly', async () => { diff --git a/src/drivers/zarinpal/zarinpal.ts b/src/drivers/zarinpal/zarinpal.ts index 7303a32..4a7ef63 100644 --- a/src/drivers/zarinpal/zarinpal.ts +++ b/src/drivers/zarinpal/zarinpal.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; const getLinks = (links: { request: string; verify: string; payment: string }, sandbox: boolean) => @@ -18,6 +18,7 @@ const throwError = (errorCode: string) => { throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); + throw new GatewayFailureError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); }; export const createZarinpalDriver = defineDriver({ @@ -66,11 +67,9 @@ export const createZarinpalDriver = defineDriver({ if (!Array.isArray(errors)) { // There are errors (`errors` is an object) - const errorCode = errors.code.toString(); - throwError(errorCode); - throw new RequestException(API.requestErrors[errorCode]); + throwError(errors.code.toString()); } - throw new RequestException(); + throw new GatewayFailureError(); }, verify: async ({ ctx, options, params }) => { const { Authority: authority, Status: status } = params; @@ -78,9 +77,7 @@ export const createZarinpalDriver = defineDriver({ const { merchantId, sandbox } = ctx; const links = getLinks(ctx.links, sandbox ?? false); - if (status !== 'OK') { - throw new PaymentException(); - } + if (status !== 'OK') throw new GatewayFailureError(); const response = await axios.post( links.verify, @@ -104,12 +101,10 @@ export const createZarinpalDriver = defineDriver({ if (!Array.isArray(errors)) { // There are errors (`errors` is an object) - const errorCode = errors.code.toString(); - throwError(errorCode); - throw new VerificationException(API.verifyErrors[errorCode]); + throwError(errors.code.toString()); } - throw new VerificationException(); + throw new GatewayFailureError(); }, }); diff --git a/src/drivers/zibal/zibal.spec.ts b/src/drivers/zibal/zibal.spec.ts index 1f7c5ac..cd87939 100644 --- a/src/drivers/zibal/zibal.spec.ts +++ b/src/drivers/zibal/zibal.spec.ts @@ -1,6 +1,6 @@ import axios from 'axios'; import { Receipt } from '../../driver'; -import { BadConfigError, RequestException, UserError } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; import { createZibalDriver, ZibalDriver } from './zibal'; @@ -39,7 +39,7 @@ describe('Zibal Driver', () => { mockedAxios.post.mockResolvedValueOnce({ data: serverResponse }); - await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(RequestException); + await expect(driver.request({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(GatewayFailureError); }); it('throws payment bad config errors accordingly', async () => { diff --git a/src/drivers/zibal/zibal.ts b/src/drivers/zibal/zibal.ts index dbc1396..95516be 100644 --- a/src/drivers/zibal/zibal.ts +++ b/src/drivers/zibal/zibal.ts @@ -1,7 +1,7 @@ import axios from 'axios'; import { z } from 'zod'; import { defineDriver } from '../../driver'; -import { BadConfigError, PaymentException, RequestException, UserError, VerificationException } from '../../exceptions'; +import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import * as API from './api'; const getMerchantId = (merchantId: string, sandbox: boolean) => (sandbox ? 'zibal' : merchantId); @@ -11,6 +11,7 @@ const throwError = (errorCode: string) => { throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors, true); if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors); + throw new GatewayFailureError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors); }; export const createZibalDriver = defineDriver({ @@ -57,7 +58,6 @@ export const createZibalDriver = defineDriver({ if (result !== 100) { const resCode = result.toString(); throwError(resCode); - throw new RequestException(API.purchaseErrors[resCode]); } return { @@ -73,7 +73,6 @@ export const createZibalDriver = defineDriver({ if (success.toString() === '0') { const resCode = status.toString(); throwError(resCode); - throw new PaymentException(API.callbackErrors[resCode]); } const response = await axios.post(links.verify, { @@ -86,7 +85,6 @@ export const createZibalDriver = defineDriver({ if (result !== 100) { const resCode = result.toString(); throwError(resCode); - throw new VerificationException(API.verifyErrors[resCode]); } return { From cf31a95e7db355b382359931d07976903b298cb7 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 12:31:55 +0430 Subject: [PATCH 28/31] typo: fix `RequestPaymentRes_Successful` --- src/drivers/idpay/api.ts | 4 ++-- src/drivers/idpay/idpay.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/drivers/idpay/api.ts b/src/drivers/idpay/api.ts index d6fc71a..c1200d7 100644 --- a/src/drivers/idpay/api.ts +++ b/src/drivers/idpay/api.ts @@ -48,7 +48,7 @@ export interface RequestPaymentReq { callback: string; } -export interface RequestPaymenRes_Successful { +export interface RequestPaymentRes_Successful { /** * کلید منحصر بفرد تراکنش */ @@ -65,7 +65,7 @@ export interface RequestPaymentRes_Failed { error_message: string; } -export type RequestPaymentRes = RequestPaymenRes_Successful | RequestPaymentRes_Failed; +export type RequestPaymentRes = RequestPaymentRes_Successful | RequestPaymentRes_Failed; export interface CallbackParams_POST { /** diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index 471d9e1..0da2835 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -4,7 +4,7 @@ import { defineDriver } from '../../driver'; import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions'; import { generateUuid } from '../../utils/generateUuid'; import * as API from './api'; -import { RequestPaymenRes_Successful, VerifyPaymentRes_Successful } from './api'; +import { RequestPaymentRes_Successful, VerifyPaymentRes_Successful } from './api'; const getHeaders = (apiKey: string, sandbox: boolean) => ({ 'X-SANDBOX': sandbox ? '1' : '0', @@ -67,7 +67,7 @@ export const createIdpayDriver = defineDriver({ const errorCode = error.error_code.toString(); throwError(errorCode); } - response.data = response.data as RequestPaymenRes_Successful; + response.data = response.data as RequestPaymentRes_Successful; return { method: 'GET', referenceId: response.data.id, From 55d8fbe4eed18fd6b0786913e13b824009492a0d Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 12:42:43 +0430 Subject: [PATCH 29/31] refactor: Avoid long constructor calls in `throwError` --- src/drivers/behpardakht/behpardakht.ts | 7 ++++--- src/drivers/idpay/idpay.ts | 15 ++++++--------- src/drivers/nextpay/nextpay.ts | 7 ++++--- src/drivers/payir/payir.ts | 7 ++++--- src/drivers/sadad/sadad.ts | 12 +++++------- src/drivers/saman/saman.ts | 15 ++++++--------- src/drivers/zarinpal/zarinpal.ts | 9 ++++----- src/drivers/zibal/zibal.ts | 18 +++++++----------- 8 files changed, 40 insertions(+), 50 deletions(-) diff --git a/src/drivers/behpardakht/behpardakht.ts b/src/drivers/behpardakht/behpardakht.ts index 935d97c..2c22144 100644 --- a/src/drivers/behpardakht/behpardakht.ts +++ b/src/drivers/behpardakht/behpardakht.ts @@ -26,9 +26,10 @@ const timeFormat = (date = new Date()) => { }; const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); - throw new GatewayFailureError(API.errors[errorCode]); + const message = API.errors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createBehpardakhtDriver = defineDriver({ diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index 0da2835..e7d44c3 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -12,11 +12,10 @@ const getHeaders = (apiKey: string, sandbox: boolean) => ({ }); const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) - throw new BadConfigError(API.errors[errorCode] ?? API.callbackErrors[errorCode], true); - if (API.IPGUserErrors.includes(errorCode)) - throw new UserError(API.errors[errorCode] ?? API.callbackErrors[errorCode]); - throw new GatewayFailureError(API.errors[errorCode]); + const message = API.errors[errorCode] ?? API.callbackErrors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createIdpayDriver = defineDriver({ @@ -64,8 +63,7 @@ export const createIdpayDriver = defineDriver({ if ('error_message' in response.data) { const error = response.data as API.RequestPaymentRes_Failed; - const errorCode = error.error_code.toString(); - throwError(errorCode); + throwError(error.error_code.toString()); } response.data = response.data as RequestPaymentRes_Successful; return { @@ -94,8 +92,7 @@ export const createIdpayDriver = defineDriver({ ); if ('error_message' in response.data) { - const errorCode = response.data.error_code.toString(); - throwError(errorCode); + throwError(response.data.error_code.toString()); } response.data = response.data as VerifyPaymentRes_Successful; return { diff --git a/src/drivers/nextpay/nextpay.ts b/src/drivers/nextpay/nextpay.ts index fd8a35c..c345c26 100644 --- a/src/drivers/nextpay/nextpay.ts +++ b/src/drivers/nextpay/nextpay.ts @@ -7,9 +7,10 @@ import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions import * as API from './api'; const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); - throw new GatewayFailureError(API.errors[errorCode]); + const message = API.errors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createNextpayDriver = defineDriver({ diff --git a/src/drivers/payir/payir.ts b/src/drivers/payir/payir.ts index 60796d3..ea63aef 100644 --- a/src/drivers/payir/payir.ts +++ b/src/drivers/payir/payir.ts @@ -7,9 +7,10 @@ import * as API from './api'; const getApiKey = (apiKey: string, sandbox: boolean) => (sandbox ? 'test' : apiKey); const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(API.errors[errorCode], true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(API.errors[errorCode]); - throw new GatewayFailureError(API.errors[errorCode]); + const message = API.errors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createPayirDriver = defineDriver({ diff --git a/src/drivers/sadad/sadad.ts b/src/drivers/sadad/sadad.ts index bf8e8cb..99053cd 100644 --- a/src/drivers/sadad/sadad.ts +++ b/src/drivers/sadad/sadad.ts @@ -21,9 +21,9 @@ const signData = (message: string, key: string): string => { }; const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) - throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); - throw new GatewayFailureError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); + const message = API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + throw new GatewayFailureError(message); }; export const createSadadDriver = defineDriver({ @@ -71,8 +71,7 @@ export const createSadadDriver = defineDriver({ }); if (response.data.ResCode !== 0) { - const resCode = response.data.ResCode.toString(); - throwError(resCode); + throwError(response.data.ResCode.toString()); } return { @@ -100,8 +99,7 @@ export const createSadadDriver = defineDriver({ const { ResCode: verificationResCode, SystemTraceNo } = response.data; if (verificationResCode !== 0) { - const resCode = verificationResCode.toString(); - throwError(resCode); + throwError(verificationResCode.toString()); } return { diff --git a/src/drivers/saman/saman.ts b/src/drivers/saman/saman.ts index b8a91ae..8b26e18 100644 --- a/src/drivers/saman/saman.ts +++ b/src/drivers/saman/saman.ts @@ -6,11 +6,10 @@ import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions import * as API from './api'; const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) - throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode], true); - if (API.IPGUserErrors.includes(errorCode)) - throw new UserError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]); - throw new GatewayFailureError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]); + const message = API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createSamanDriver = defineDriver({ @@ -49,8 +48,7 @@ export const createSamanDriver = defineDriver({ }); if (response.data.status !== 1 && response.data.errorCode !== undefined) { - const errorCode = response.data.errorCode.toString(); - throwError(errorCode); + throwError(response.data.errorCode.toString()); } if (!response.data.token) { @@ -71,8 +69,7 @@ export const createSamanDriver = defineDriver({ const { RefNum: referenceId, TraceNo: transactionId, Status: status } = params; const { merchantId, links } = ctx; if (!referenceId) { - const resCode = status.toString(); - throwError(resCode); + throwError(status.toString()); } const soapClient = await soap.createClientAsync(links.verify); diff --git a/src/drivers/zarinpal/zarinpal.ts b/src/drivers/zarinpal/zarinpal.ts index 4a7ef63..2819d49 100644 --- a/src/drivers/zarinpal/zarinpal.ts +++ b/src/drivers/zarinpal/zarinpal.ts @@ -14,11 +14,10 @@ const getLinks = (links: { request: string; verify: string; payment: string }, s : links; const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) - throw new BadConfigError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode], true); - if (API.IPGUserErrors.includes(errorCode)) - throw new UserError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); - throw new GatewayFailureError(API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]); + const message = API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createZarinpalDriver = defineDriver({ diff --git a/src/drivers/zibal/zibal.ts b/src/drivers/zibal/zibal.ts index 95516be..9a32c63 100644 --- a/src/drivers/zibal/zibal.ts +++ b/src/drivers/zibal/zibal.ts @@ -7,11 +7,10 @@ import * as API from './api'; const getMerchantId = (merchantId: string, sandbox: boolean) => (sandbox ? 'zibal' : merchantId); const throwError = (errorCode: string) => { - if (API.IPGConfigErrors.includes(errorCode)) - throw new BadConfigError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors, true); - if (API.IPGUserErrors.includes(errorCode)) - throw new UserError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors); - throw new GatewayFailureError(API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors); + const message = API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors[errorCode]; + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); + throw new GatewayFailureError(message); }; export const createZibalDriver = defineDriver({ @@ -56,8 +55,7 @@ export const createZibalDriver = defineDriver({ const { result, trackId } = response.data; if (result !== 100) { - const resCode = result.toString(); - throwError(resCode); + throwError(result.toString()); } return { @@ -71,8 +69,7 @@ export const createZibalDriver = defineDriver({ const { merchantId, sandbox, links } = ctx; if (success.toString() === '0') { - const resCode = status.toString(); - throwError(resCode); + throwError(status.toString()); } const response = await axios.post(links.verify, { @@ -83,8 +80,7 @@ export const createZibalDriver = defineDriver({ const { result } = response.data; if (result !== 100) { - const resCode = result.toString(); - throwError(resCode); + throwError(result.toString()); } return { From bda6c7cfe588d0549f838c1988df662c7fbce8a0 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sat, 17 Sep 2022 12:44:55 +0430 Subject: [PATCH 30/31] Remove old error handling classes --- src/exceptions.ts | 40 +--------------------------------------- 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/src/exceptions.ts b/src/exceptions.ts index 239b3e8..95a3633 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -3,6 +3,7 @@ type MonoPayErrorConfig = { isSafeToDisplay: boolean; message?: string; }; + export abstract class MonopayError extends Error { /** * Determines whether the error was thrown by the IPG or by the application itself @@ -19,45 +20,6 @@ export abstract class MonopayError extends Error { } } -export class BasePaymentException extends Error { - constructor(message?: string) { - super(message); - Object.setPrototypeOf(this, BasePaymentException.prototype); - } -} - -/** - * Error in the requesting stage - */ -export class RequestException extends BasePaymentException { - constructor(message?: string) { - super(message); - Object.setPrototypeOf(this, RequestException.prototype); - } -} - -/** - * Error in the paying stage - * - * You can show this error message to your end user - */ -export class PaymentException extends BasePaymentException { - constructor(message?: string) { - super(message); - Object.setPrototypeOf(this, PaymentException.prototype); - } -} - -/** - * Error in the verification stage - */ -export class VerificationException extends BasePaymentException { - constructor(message?: string) { - super(message); - Object.setPrototypeOf(this, VerificationException.prototype); - } -} - /** * Denotes an error caused by developer configuration */ From 3f4264d5587ea5ad6017064c84ab3ee0670b16c1 Mon Sep 17 00:00:00 2001 From: Keivan Date: Sun, 18 Sep 2022 11:20:53 +0430 Subject: [PATCH 31/31] Include error code in `MonopayError` All error constructors now use an object instead of multiple parameters. This has been done in order to simplify the constructor calls --- src/drivers/behpardakht/behpardakht.ts | 6 +++--- src/drivers/idpay/idpay.ts | 6 +++--- src/drivers/nextpay/nextpay.ts | 8 ++++---- src/drivers/parsian/parsian.ts | 8 ++++---- src/drivers/pasargad/pasargad.ts | 6 +++--- src/drivers/payir/payir.ts | 6 +++--- src/drivers/payping/payping.ts | 4 ++-- src/drivers/sadad/sadad.ts | 6 +++--- src/drivers/saman/saman.ts | 6 +++--- src/drivers/vandar/vandar.ts | 8 ++++---- src/drivers/zarinpal/zarinpal.ts | 6 +++--- src/drivers/zibal/zibal.ts | 6 +++--- src/exceptions.ts | 21 +++++++++++++++------ src/utils/safeParse.ts | 2 +- 14 files changed, 54 insertions(+), 45 deletions(-) diff --git a/src/drivers/behpardakht/behpardakht.ts b/src/drivers/behpardakht/behpardakht.ts index 2c22144..2a9dc6c 100644 --- a/src/drivers/behpardakht/behpardakht.ts +++ b/src/drivers/behpardakht/behpardakht.ts @@ -27,9 +27,9 @@ const timeFormat = (date = new Date()) => { const throwError = (errorCode: string) => { const message = API.errors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createBehpardakhtDriver = defineDriver({ diff --git a/src/drivers/idpay/idpay.ts b/src/drivers/idpay/idpay.ts index e7d44c3..9dfa68a 100644 --- a/src/drivers/idpay/idpay.ts +++ b/src/drivers/idpay/idpay.ts @@ -13,9 +13,9 @@ const getHeaders = (apiKey: string, sandbox: boolean) => ({ const throwError = (errorCode: string) => { const message = API.errors[errorCode] ?? API.callbackErrors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createIdpayDriver = defineDriver({ diff --git a/src/drivers/nextpay/nextpay.ts b/src/drivers/nextpay/nextpay.ts index c345c26..87163dc 100644 --- a/src/drivers/nextpay/nextpay.ts +++ b/src/drivers/nextpay/nextpay.ts @@ -8,9 +8,9 @@ import * as API from './api'; const throwError = (errorCode: string) => { const message = API.errors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createNextpayDriver = defineDriver({ @@ -68,7 +68,7 @@ export const createNextpayDriver = defineDriver({ const { apiKey, links } = ctx; if (!trans_id) { - throw new UserError('تراکنش توسط کاربر لغو شد.'); + throw new UserError({ message: 'تراکنش توسط کاربر لغو شد.' }); } const response = await axios.post(links.verify, { diff --git a/src/drivers/parsian/parsian.ts b/src/drivers/parsian/parsian.ts index 55ba6fb..d589703 100644 --- a/src/drivers/parsian/parsian.ts +++ b/src/drivers/parsian/parsian.ts @@ -42,7 +42,7 @@ export const createParsianDriver = defineDriver({ const { Status, Token } = response; if (Status.toString() !== '0' || typeof Token === 'undefined') { - throw new GatewayFailureError('خطایی در درخواست پرداخت به‌وجود آمد'); + throw new GatewayFailureError({ message: 'خطایی در درخواست پرداخت به‌وجود آمد' }); } return { @@ -57,7 +57,7 @@ export const createParsianDriver = defineDriver({ const { merchantId, links } = ctx; if (status.toString() !== '0') { - throw new UserError('تراکنش توسط کاربر لغو شد.'); + throw new UserError({ message: 'تراکنش توسط کاربر لغو شد.' }); } const soapClient = await soap.createClientAsync(links.verify); @@ -75,9 +75,9 @@ export const createParsianDriver = defineDriver({ const reversalRequestFields: API.ReversalPaymentReq = requestFields; const reversalResponse: API.ReversalPaymentRes = soapClient.ReversalRequest(reversalRequestFields); if (reversalResponse.Status !== '0') { - throw new GatewayFailureError('خطایی در تایید پرداخت به‌وجود آمد و مبلغ بازگشته نشد.'); + throw new GatewayFailureError({ message: 'خطایی در تایید پرداخت به‌وجود آمد و مبلغ بازگشته نشد.' }); } - throw new GatewayFailureError('خطایی در تایید پرداخت به‌وجود آمد'); + throw new GatewayFailureError({ message: 'خطایی در تایید پرداخت به‌وجود آمد' }); } return { diff --git a/src/drivers/pasargad/pasargad.ts b/src/drivers/pasargad/pasargad.ts index 9416428..5cfc41e 100644 --- a/src/drivers/pasargad/pasargad.ts +++ b/src/drivers/pasargad/pasargad.ts @@ -21,7 +21,7 @@ const signData = async (data: unknown, privateKeyXMLFile: string): Promise (sandbox ? 'test' : apiK const throwError = (errorCode: string) => { const message = API.errors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createPayirDriver = defineDriver({ diff --git a/src/drivers/payping/payping.ts b/src/drivers/payping/payping.ts index 7aeb6c1..9e8f7ed 100644 --- a/src/drivers/payping/payping.ts +++ b/src/drivers/payping/payping.ts @@ -64,7 +64,7 @@ export const createPaypingDriver = defineDriver({ }, ); } catch (error) { - throw new GatewayFailureError(statusToMessage((error as any).response.status)); + throw new GatewayFailureError({ message: statusToMessage((error as any).response.status) }); } const { code } = response.data; @@ -95,7 +95,7 @@ export const createPaypingDriver = defineDriver({ }, ); } catch (error) { - throw new GatewayFailureError(statusToMessage((error as any).response.status)); + throw new GatewayFailureError({ message: statusToMessage((error as any).response.status) }); } const { cardNumber } = response.data; diff --git a/src/drivers/sadad/sadad.ts b/src/drivers/sadad/sadad.ts index 99053cd..12348a3 100644 --- a/src/drivers/sadad/sadad.ts +++ b/src/drivers/sadad/sadad.ts @@ -16,14 +16,14 @@ const signData = (message: string, key: string): string => { return encrypted.toString(); } catch (err) { - throw new BadConfigError('The signing process has failed. details: ' + err, false); + throw new BadConfigError({ message: 'The signing process has failed. Error: ' + err, isIPGError: false }); } }; const throwError = (errorCode: string) => { const message = API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createSadadDriver = defineDriver({ diff --git a/src/drivers/saman/saman.ts b/src/drivers/saman/saman.ts index 8b26e18..48910b6 100644 --- a/src/drivers/saman/saman.ts +++ b/src/drivers/saman/saman.ts @@ -7,9 +7,9 @@ import * as API from './api'; const throwError = (errorCode: string) => { const message = API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createSamanDriver = defineDriver({ diff --git a/src/drivers/vandar/vandar.ts b/src/drivers/vandar/vandar.ts index 61c9869..9a01cc5 100644 --- a/src/drivers/vandar/vandar.ts +++ b/src/drivers/vandar/vandar.ts @@ -62,9 +62,9 @@ export const createVandarDriver = defineDriver({ ); const { errors, token } = response.data; - if (errors?.length) throw new GatewayFailureError(errors.join('\n')); + if (errors?.length) throw new GatewayFailureError({ message: errors.join('\n') }); - if (!token) throw new GatewayFailureError('No token was provided by the IPG'); + if (!token) throw new GatewayFailureError({ message: 'No token was provided by the IPG' }); return { method: 'GET', @@ -91,9 +91,9 @@ export const createVandarDriver = defineDriver({ ); const { errors, transId, cardNumber } = response.data; - if (errors?.length) throw new GatewayFailureError(errors.join('\n')); + if (errors?.length) throw new GatewayFailureError({ message: errors.join('\n') }); - if (transId === undefined) throw new GatewayFailureError('No transaction ID was provided by the IPG'); + if (transId === undefined) throw new GatewayFailureError({ message: 'No transaction ID was provided by the IPG' }); return { transactionId: transId, diff --git a/src/drivers/zarinpal/zarinpal.ts b/src/drivers/zarinpal/zarinpal.ts index 2819d49..378f4a5 100644 --- a/src/drivers/zarinpal/zarinpal.ts +++ b/src/drivers/zarinpal/zarinpal.ts @@ -15,9 +15,9 @@ const getLinks = (links: { request: string; verify: string; payment: string }, s const throwError = (errorCode: string) => { const message = API.requestErrors[errorCode] ?? API.verifyErrors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createZarinpalDriver = defineDriver({ diff --git a/src/drivers/zibal/zibal.ts b/src/drivers/zibal/zibal.ts index 9a32c63..1511818 100644 --- a/src/drivers/zibal/zibal.ts +++ b/src/drivers/zibal/zibal.ts @@ -8,9 +8,9 @@ const getMerchantId = (merchantId: string, sandbox: boolean) => (sandbox ? 'ziba const throwError = (errorCode: string) => { const message = API.purchaseErrors[errorCode] ?? API.callbackErrors[errorCode] ?? API.verifyErrors[errorCode]; - if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError(message, true); - if (API.IPGUserErrors.includes(errorCode)) throw new UserError(message); - throw new GatewayFailureError(message); + if (API.IPGConfigErrors.includes(errorCode)) throw new BadConfigError({ message, isIPGError: true, code: errorCode }); + if (API.IPGUserErrors.includes(errorCode)) throw new UserError({ message, code: errorCode }); + throw new GatewayFailureError({ message, code: errorCode }); }; export const createZibalDriver = defineDriver({ diff --git a/src/exceptions.ts b/src/exceptions.ts index 95a3633..675457a 100644 --- a/src/exceptions.ts +++ b/src/exceptions.ts @@ -2,6 +2,7 @@ type MonoPayErrorConfig = { isIPGError: boolean; isSafeToDisplay: boolean; message?: string; + code?: string; }; export abstract class MonopayError extends Error { @@ -13,10 +14,15 @@ export abstract class MonopayError extends Error { * Determines whether the error exposes sensitive information or not */ readonly isSafeToDisplay: boolean; + /** + * Contains the IPG error code (if IPG provides any) + */ + readonly code?: string; constructor(options: MonoPayErrorConfig) { super(options.message); this.isIPGError = options.isIPGError; this.isSafeToDisplay = options.isSafeToDisplay; + this.code = options.code; } } @@ -24,8 +30,9 @@ export abstract class MonopayError extends Error { * Denotes an error caused by developer configuration */ export class BadConfigError extends MonopayError { - constructor(message: string, isIPGError: boolean) { - super({ isIPGError, isSafeToDisplay: false, message }); + constructor(details: { message: string; code?: string; isIPGError: boolean }) { + const { message, code, isIPGError } = details; + super({ message, code, isIPGError, isSafeToDisplay: false }); } } @@ -33,8 +40,9 @@ export class BadConfigError extends MonopayError { * Denotes an error caused by end user */ export class UserError extends MonopayError { - constructor(message: string) { - super({ message, isIPGError: true, isSafeToDisplay: true }); + constructor(details?: { message?: string; code?: string }) { + const { message, code } = details ?? {}; + super({ message, code, isIPGError: true, isSafeToDisplay: true }); } } @@ -42,7 +50,8 @@ export class UserError extends MonopayError { * Denotes an error either caused by a failure from gateway or an unrecognizable reason */ export class GatewayFailureError extends MonopayError { - constructor(message?: string) { - super({ message, isIPGError: true, isSafeToDisplay: false }); + constructor(details?: { message?: string; code?: string }) { + const { message, code } = details ?? {}; + super({ message, code, isIPGError: true, isSafeToDisplay: false }); } } diff --git a/src/utils/safeParse.ts b/src/utils/safeParse.ts index 34fac75..31f4e2c 100644 --- a/src/utils/safeParse.ts +++ b/src/utils/safeParse.ts @@ -5,5 +5,5 @@ export const safeParse = (parser: ZodSchema, data: T): T => { const parsed = parser.safeParse(data); if (parsed.success) return parsed.data; const message = parsed.error.errors.map((err) => `${err.path[0]}: ${err.message}`).join('\n'); - throw new BadConfigError(message, false); + throw new BadConfigError({ message, isIPGError: false }); };