Skip to content

Commit

Permalink
Added payping + tests small tweak
Browse files Browse the repository at this point in the history
  • Loading branch information
alitnk committed Oct 15, 2021
1 parent ab1568d commit ff61c93
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 12 deletions.
5 changes: 5 additions & 0 deletions src/drivers.ts
Expand Up @@ -5,6 +5,8 @@ import { NextPay } from './drivers/nextpay';
import * as NextPayAPI from './drivers/nextpay/api';
import { Payir } from './drivers/payir';
import * as PayirAPI from './drivers/payir/api';
import { PayPing } from './drivers/payping';
import * as PayPingAPI from './drivers/payping/api';
import { Sadad } from './drivers/sadad';
import * as SadadAPI from './drivers/sadad/api';
import { Saman } from './drivers/saman';
Expand All @@ -16,6 +18,7 @@ import * as ZibalAPI from './drivers/zibal/api';

export { IdPay } from './drivers/idpay';
export { NextPay } from './drivers/nextpay';
export { PayPing } from './drivers/payping';
export { Sadad } from './drivers/sadad';
export { Saman } from './drivers/saman';
export { Zarinpal } from './drivers/zarinpal';
Expand All @@ -24,6 +27,7 @@ interface ConfigMap {
idpay: IdPayAPI.Config;
nextpay: NextPayAPI.Config;
payir: PayirAPI.Config;
payping: PayPingAPI.Config;
sadad: SadadAPI.Config;
saman: SamanAPI.Config;
zarinpal: ZarinpalAPI.Config;
Expand All @@ -38,6 +42,7 @@ const drivers = {
idpay: IdPay,
nextpay: NextPay,
payir: Payir,
payping: PayPing,
sadad: Sadad,
saman: Saman,
zarinpal: Zarinpal,
Expand Down
108 changes: 108 additions & 0 deletions src/drivers/payping/api.ts
@@ -0,0 +1,108 @@
import * as t from 'io-ts';
import { BaseReceipt, LinksObject, tBaseRequestOptions, tBaseVerifyOptions } from '../../types';

/*
* PayPing's API
* Currency: IRT
*/

export const links: LinksObject = {
default: {
REQUEST: 'https://api.payping.ir/v2/pay',
VERIFICATION: 'https://api.payping.ir/v2/pay/verify',
PAYMENT: 'https://api.payping.ir/v2/pay/gotoipg/',
},
};

export interface RequestPaymentReq {
/**
* integer <int32> [ 100 .. 50000000 ]
*/
amount: number;

/**
*شماره موبایل یا ایمیل پرداخت کننده
*/
payerIdentity?: string;
/**
*نام پرداخت کننده
*/
payerName?: string;
/**
*توضیحات
*/
description?: string;
/**
*آدرس صفحه برگشت
*/
returnUrl: string;

/**
*کد ارسالی توسط کاربر
*/
clientRefId?: string;
}

export interface RequestPaymentRes {
/**
* کد پرداخت
*/
code: string;
}

export interface CallbackParams {
code: string;
refid: string;
clientrefid: string;
cardnumber: string;
cardhashpan: string;
}

export interface VerifyPaymentReq {
/**
* شماره فاکتور
*/
refId: string;

/**
* مبلغ تراکنش
* integer <int32> [ 100 .. 50000000 ]
*/
amount: number;
}

export interface VerifyPaymentRes {
amount: number;
cardNumber: string;
cardHashPan: string;
}

/*
* Package's API
*/

export const tConfig = t.intersection([
t.partial({}),
t.interface({
apiKey: t.string,
}),
]);

export type Config = t.TypeOf<typeof tConfig>;

export const tRequestOptions = t.intersection([
t.partial({
mobile: t.string,
email: t.string,
name: t.string,
}),
tBaseRequestOptions,
]);

export type RequestOptions = t.TypeOf<typeof tRequestOptions>;

export const tVerifyOptions = t.intersection([t.interface({}), tBaseVerifyOptions]);

export type VerifyOptions = t.TypeOf<typeof tVerifyOptions>;

export type Receipt = BaseReceipt<VerifyPaymentRes>;
73 changes: 73 additions & 0 deletions src/drivers/payping/index.ts
@@ -0,0 +1,73 @@
import { Driver } from '../../driver';
import * as API from './api';
import axios from 'axios';
import { RequestException, VerificationException } from '../../exceptions';
import { ErrorList } from '../../types';

export class PayPing extends Driver<API.Config> {
constructor(config: API.Config) {
super(config, API.tConfig);
}

protected links = API.links;

requestPayment = async (options: API.RequestOptions) => {
options = this.getParsedData(options, API.tRequestOptions);

let { amount, callbackUrl, mobile, email, name, description } = options;
let response;

try {
response = await axios.post<API.RequestPaymentReq, { data: API.RequestPaymentRes }>(this.getLinks().REQUEST, {
amount: amount * 10,
returnUrl: callbackUrl,
description,
payerIdentity: mobile || email,
payerName: name,
});
} catch (error) {
throw new RequestException(this.statusToMessage((error as any).response.status));
}

const { code } = response.data;

return this.makeRequestInfo(code, 'GET', this.getLinks().PAYMENT + code);
};

verifyPayment = async (options: API.VerifyOptions, params: API.CallbackParams): Promise<API.Receipt> => {
const { code, refid } = params;
const { amount } = options;
let response;

try {
response = await axios.post<API.VerifyPaymentReq, { data: API.VerifyPaymentRes }>(this.getLinks().VERIFICATION, {
amount: amount * 10,
refId: code,
});
} catch (error) {
throw new VerificationException(this.statusToMessage((error as any).response.status));
}

const { cardNumber } = response.data;

return {
raw: response.data,
transactionId: refid,
cardPan: cardNumber,
};
};

statusToMessage(status = 500): string {
const map: ErrorList = {
'400': 'مشکلی در اطلاعات ارسالی وجود دارد.',
'401': 'شما به این آیتم دسترسی ندارید.',
'403': 'دسترسی شما غیر مجاز است.',
'404': 'یافت نشد.',
'500': 'مشکلی از طرف درگاه پرداخت رخ داده.',
'502': 'سرور پراکسی با خطا مواجه شده است.',
'503': 'سرور درگاه پرداخت در حال حاضر پاسخ‌گو نیست.',
};

return map[status.toString() || '500'];
}
}
6 changes: 3 additions & 3 deletions test/idpay.spec.ts
Expand Up @@ -18,9 +18,9 @@ describe('IdPay Driver', () => {

const driver = getPaymentDriver<IdPay>('idpay', { apiKey: '2134' });

expect(typeof (await driver.requestPayment({ callbackUrl: 'https://google.com', amount: 20000 })).url).toBe(
'string'
);
expect(
typeof (await driver.requestPayment({ callbackUrl: 'https://path.to/callback-url', amount: 20000 })).url
).toBe('string');
});

it('throws payment errors accordingly', async () => {
Expand Down
6 changes: 3 additions & 3 deletions test/nextpay.spec.ts
Expand Up @@ -18,9 +18,9 @@ describe('NextPay Driver', () => {

const driver = getPaymentDriver<NextPay>('nextpay', { apiKey: '1234' });

expect(typeof (await driver.requestPayment({ callbackUrl: 'https://google.com', amount: 20000 })).url).toBe(
'string'
);
expect(
typeof (await driver.requestPayment({ callbackUrl: 'https://path.to/callback-url', amount: 20000 })).url
).toBe('string');
});

it('throws payment errors accordingly', async () => {
Expand Down
6 changes: 3 additions & 3 deletions test/payir.spec.ts
Expand Up @@ -18,9 +18,9 @@ describe('Payir Driver', () => {

const driver = getPaymentDriver<Payir>('payir', { apiKey: '2134' });

expect(typeof (await driver.requestPayment({ callbackUrl: 'https://google.com', amount: 20000 })).url).toBe(
'string'
);
expect(
typeof (await driver.requestPayment({ callbackUrl: 'https://path.to/callback-url', amount: 20000 })).url
).toBe('string');
});

it('throws payment errors accordingly', async () => {
Expand Down
63 changes: 63 additions & 0 deletions test/payping.spec.ts
@@ -0,0 +1,63 @@
import axios from 'axios';
import { PayPing } from '../src/drivers/payping';
import * as API from '../src/drivers/payping/api';
import { RequestException } from '../src/exceptions';
import { getPaymentDriver } from '../src/drivers';

jest.mock('axios');

const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('PayPing Driver', () => {
it('returns the correct payment url', async () => {
const serverResponse: API.RequestPaymentRes = {
code: '1234',
};

mockedAxios.post.mockResolvedValueOnce({ data: serverResponse });

const driver = getPaymentDriver<PayPing>('payping', { apiKey: '2134' });

expect(
typeof (await driver.requestPayment({ callbackUrl: 'https://path.to/callback-url', amount: 20000 })).url
).toBe('string');
});

it('throws payment errors accordingly', async () => {
// mockedAxios.post.mockRejectedValueOnce({ response: { status: 401 } });
mockedAxios.post.mockReturnValueOnce(Promise.reject({ response: { status: 401 } }));

const driver = getPaymentDriver<PayPing>('payping', { apiKey: '2134' });

await expect(async () => await driver.requestPayment({ amount: 2000, callbackUrl: 'asd' })).rejects.toThrow(
RequestException
);
});

it('verifies the purchase correctly', async () => {
const serverResponse: API.VerifyPaymentRes = {
amount: 40000,
cardHashPan: 'hash',
cardNumber: '1234-****-****-1234',
};
const expectedResult: API.Receipt = { transactionId: '1234', raw: serverResponse };

mockedAxios.post.mockResolvedValueOnce({ data: serverResponse });

const driver = getPaymentDriver<PayPing>('payping', { apiKey: '2134' });

expect(
(
await driver.verifyPayment(
{ amount: 2000 },
{
code: '1234',
cardhashpan: 'hash',
clientrefid: 'clientrefid',
cardnumber: '1234-****-****-1234',
refid: '1234',
}
)
).transactionId
).toEqual(expectedResult.transactionId);
});
});
6 changes: 3 additions & 3 deletions test/zibal.spec.ts
Expand Up @@ -19,9 +19,9 @@ describe('Zibal Driver', () => {

const driver = getPaymentDriver<Zibal>('zibal', { merchantId: '2134' });

expect(typeof (await driver.requestPayment({ callbackUrl: 'https://google.com', amount: 20000 })).url).toBe(
'string'
);
expect(
typeof (await driver.requestPayment({ callbackUrl: 'https://path.to/callback-url', amount: 20000 })).url
).toBe('string');
});

it('throws payment errors accordingly', async () => {
Expand Down

0 comments on commit ff61c93

Please sign in to comment.