Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New error handling method #42

Merged
merged 31 commits into from
Sep 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ed75d9d
Create `MonopayError` abstract class
Keivan-sf Sep 15, 2022
0a2bafb
Throw `BadConfigError` on wrong schemas
Keivan-sf Sep 15, 2022
3346419
resolves #41: Fix Behpardakht error codes bug
Keivan-sf Sep 15, 2022
1253dfa
Throw `BadConfigError` for Behpardakht IPG
Keivan-sf Sep 15, 2022
d58cafa
Throw `BadConfigError` for IdPay IPG
Keivan-sf Sep 15, 2022
f32c622
Add `callBackErrors` to idPay IPG config errors
Keivan-sf Sep 15, 2022
8f2d4be
Throw `BadConfigError` for Nextpay IPG
Keivan-sf Sep 15, 2022
781ce14
Add bad IP error code to `IPGConfigErrors`
Keivan-sf Sep 16, 2022
265b579
Throw `BadConfigError` for Payir IPG
Keivan-sf Sep 16, 2022
67fbc68
Throw `BadConfigError` for Sadad IPG
Keivan-sf Sep 16, 2022
e94332b
Throw `BadConfigError` for Saman IPG
Keivan-sf Sep 16, 2022
df12b22
Throw `BadConfigError` for Zarinpal IPG
Keivan-sf Sep 16, 2022
e4b180a
Throw `BadConfigError` for Zibal IPG
Keivan-sf Sep 16, 2022
a71201e
Fix `BadConfigError` for Zibal IPG
Keivan-sf Sep 16, 2022
a16bc8f
Add `UserError` to exceptions
Keivan-sf Sep 17, 2022
b13ca2a
Throw `UserError` for Behpardakht IPG
Keivan-sf Sep 17, 2022
f2d0d30
Throw `UserError` for Idpay IPG
Keivan-sf Sep 17, 2022
a3b308d
Throw `UserError` for Nextpay IPG
Keivan-sf Sep 17, 2022
6300f44
Remove unrelated codes from payir `IPGConfigErros`
Keivan-sf Sep 17, 2022
91ec21c
Throw `UserError` for Payir IPG
Keivan-sf Sep 17, 2022
f3c7016
Throw `UserError` for Saman IPG
Keivan-sf Sep 17, 2022
6d2e8df
Throw `UserError` for Zarinpal IPG
Keivan-sf Sep 17, 2022
17054fe
Throw `UserError` for Zibal IPG
Keivan-sf Sep 17, 2022
a155fcc
Throw `BadConfigError` for signing failure
Keivan-sf Sep 17, 2022
abc6658
Add `GatewayFailureError` to exceptions
Keivan-sf Sep 17, 2022
6e87197
Make `GatewayFailureError` message optional
Keivan-sf Sep 17, 2022
747a8a5
Throw `GatewayFailureError` in all drivers
Keivan-sf Sep 17, 2022
cf31a95
typo: fix `RequestPaymentRes_Successful`
Keivan-sf Sep 17, 2022
55d8fbe
refactor: Avoid long constructor calls in `throwError`
Keivan-sf Sep 17, 2022
bda6c7c
Remove old error handling classes
Keivan-sf Sep 17, 2022
3f4264d
Include error code in `MonopayError`
Keivan-sf Sep 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 34 additions & 4 deletions src/driver.spec.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
Expand All @@ -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',
Expand All @@ -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);
});
});
8 changes: 4 additions & 4 deletions src/driver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z, ZodSchema } from 'zod';
import { buildRedirectScript } from './utils/buildRedirectScript';
import { safeParse } from './utils/safeParse';

interface IPaymentInfo {
referenceId: string | number;
Expand Down Expand Up @@ -52,10 +53,9 @@ export const defineDriver = <
verify: (arg: { ctx: IConfig; options: IVerify; params: Record<string, any> }) => Promise<Receipt>;
}) => {
return (config: Omit<IConfig, keyof DefaultConfig> & Partial<DefaultConfig>) => {
const ctx: IConfig = schema.config.parse({ ...defaultConfig, ...config });

const ctx = safeParse(schema.config, { ...defaultConfig, ...config }) as IConfig;
const requestPayment = async (options: Parameters<typeof request>['0']['options']) => {
options = schema.request.parse(options);
options = safeParse(schema.request, options) as IRequest;
const paymentInfo = await request({ ctx, options });
return {
...paymentInfo,
Expand All @@ -67,7 +67,7 @@ export const defineDriver = <
options: Parameters<typeof verify>['0']['options'],
params: Parameters<typeof verify>['0']['params'],
) => {
options = schema.verify.parse(options);
options = safeParse(schema.verify, options) as IVerify;
return verify({ ctx, options, params });
};

Expand Down
30 changes: 30 additions & 0 deletions src/drivers/behpardakht/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,33 @@ export const errors: Record<string, string> = {
'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',
];

export const IPGUserErrors = ['11', '12', '13', '14', '15', '16', '17', '18', '19', '111', '112', '113', '114'];

export const IPGFailureErrors = ['23', '34', '416', '61'];
31 changes: 27 additions & 4 deletions src/drivers/behpardakht/behpardakht.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Receipt } from '../../driver';
import { RequestException } from '../../exceptions';
import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions';
import * as API from './api';
import { BehpardakhtDriver, createBehpardakhtDriver } from './behpardakht';

Expand Down Expand Up @@ -34,18 +34,41 @@ describe('Behpardakht Driver', () => {
).toBe('string');
});

it('throws payment errors accordingly', async () => {
const serverResponse: API.RequestPaymentRes = '100';
it('throws payment failure accordingly', async () => {
const serverResponse: API.RequestPaymentRes = '34';
mockSoapClient.bpPayRequest = () => serverResponse;

await expect(
async () =>
await driver.request({
amount: 20000,
callbackUrl: 'https://mysite.com/callback',
}),
).rejects.toThrow(GatewayFailureError);
});

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('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(RequestException);
).rejects.toThrow(UserError);
});

it('verifies the purchase correctly', async () => {
Expand Down
17 changes: 12 additions & 5 deletions src/drivers/behpardakht/behpardakht.ts
Original file line number Diff line number Diff line change
@@ -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, GatewayFailureError, UserError } from '../../exceptions';
import { generateId } from '../../utils/generateId';
import * as API from './api';

Expand All @@ -25,6 +25,13 @@ const timeFormat = (date = new Date()) => {
return hh.toString() + mm.toString() + ss.toString();
};

const throwError = (errorCode: string) => {
const message = API.errors[errorCode];
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({
schema: {
config: z.object({
Expand Down Expand Up @@ -73,7 +80,7 @@ export const createBehpardakhtDriver = defineDriver({
const RefId = splittedResponse[1];

if (ResCode.toString() !== '0') {
throw new RequestException(API.errors[response[0]]);
throwError(ResCode);
}

return {
Expand All @@ -90,7 +97,7 @@ export const createBehpardakhtDriver = defineDriver({
const { terminalId, username, password, links } = ctx;

if (ResCode !== '0') {
throw new PaymentException(API.errors[ResCode]);
throwError(ResCode);
}

const soapClient = await soap.createClientAsync(links.verify);
Expand All @@ -111,7 +118,7 @@ export const createBehpardakhtDriver = defineDriver({
if (verifyResponse.toString() !== '43') {
soapClient.bpReversalRequest(requestFields);
}
throw new VerificationException(API.errors[verifyResponse]);
throwError(ResCode);
}

// 2. Settle
Expand All @@ -120,7 +127,7 @@ export const createBehpardakhtDriver = defineDriver({
if (settleResponse.toString() !== '45' && settleResponse.toString() !== '48') {
soapClient.bpReversalRequest(requestFields);
}
throw new VerificationException(API.errors[verifyResponse]);
throwError(ResCode);
}

return {
Expand Down
29 changes: 27 additions & 2 deletions src/drivers/idpay/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export interface RequestPaymentReq {
callback: string;
}

export interface RequestPaymenRes_Successful {
export interface RequestPaymentRes_Successful {
/**
* کلید منحصر بفرد تراکنش
*/
Expand All @@ -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 {
/**
Expand Down Expand Up @@ -271,3 +271,28 @@ export const errors: Record<string, string> = {
// 405
'54': 'مدت زمان تایید پرداخت سپری شده است.',
};

export const IPGConfigErrors = [
'11',
'12',
'13',
'14',
'21',
'22',
'23',
'24',
'31',
'32',
'33',
'34',
'35',
'36',
'37',
'38',
'41',
'42',
'43',
'54',
];

export const IPGUserErrors = ['1', '2', '7'];
26 changes: 24 additions & 2 deletions src/drivers/idpay/idpay.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios';
import { Receipt } from '../../driver';
import { RequestException } from '../../exceptions';
import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions';
import * as API from './api';
import { createIdpayDriver, IdpayDriver } from './idpay';

Expand Down Expand Up @@ -35,7 +35,29 @@ 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 () => {
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('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 () => {
Expand Down
23 changes: 16 additions & 7 deletions src/drivers/idpay/idpay.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import axios from 'axios';
import { z } from 'zod';
import { defineDriver } from '../../driver';
import { PaymentException, RequestException, VerificationException } from '../../exceptions';
import { BadConfigError, GatewayFailureError, UserError } from '../../exceptions';
import { generateUuid } from '../../utils/generateUuid';
import * as API from './api';
import { RequestPaymentRes_Successful, VerifyPaymentRes_Successful } from './api';

const getHeaders = (apiKey: string, sandbox: boolean) => ({
'X-SANDBOX': sandbox ? '1' : '0',
'X-API-KEY': apiKey,
});

const throwError = (errorCode: string) => {
const message = API.errors[errorCode] ?? API.callbackErrors[errorCode];
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({
schema: {
config: z.object({
Expand Down Expand Up @@ -55,8 +63,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()]);
throwError(error.error_code.toString());
}
response.data = response.data as RequestPaymentRes_Successful;
return {
method: 'GET',
referenceId: response.data.id,
Expand All @@ -66,9 +75,9 @@ 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') {
throwError(statusCode);
}

const response = await axios.post<API.VerifyPaymentReq, { data: API.VerifyPaymentRes }>(
Expand All @@ -83,9 +92,9 @@ export const createIdpayDriver = defineDriver({
);

if ('error_message' in response.data) {
throw new VerificationException(API.callbackErrors[response.data.error_code.toString()]);
throwError(response.data.error_code.toString());
}

response.data = response.data as VerifyPaymentRes_Successful;
return {
transactionId: response.data.track_id,
cardPan: response.data.payment.card_no,
Expand Down