diff --git a/src/drivers.ts b/src/drivers.ts index 549a332..73c8be5 100644 --- a/src/drivers.ts +++ b/src/drivers.ts @@ -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'; @@ -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'; @@ -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; @@ -38,6 +42,7 @@ const drivers = { idpay: IdPay, nextpay: NextPay, payir: Payir, + payping: PayPing, sadad: Sadad, saman: Saman, zarinpal: Zarinpal, diff --git a/src/drivers/payping/api.ts b/src/drivers/payping/api.ts new file mode 100644 index 0000000..c190755 --- /dev/null +++ b/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 [ 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 [ 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; + +export const tRequestOptions = t.intersection([ + t.partial({ + mobile: t.string, + email: t.string, + name: t.string, + }), + tBaseRequestOptions, +]); + +export type RequestOptions = t.TypeOf; + +export const tVerifyOptions = t.intersection([t.interface({}), tBaseVerifyOptions]); + +export type VerifyOptions = t.TypeOf; + +export type Receipt = BaseReceipt; diff --git a/src/drivers/payping/index.ts b/src/drivers/payping/index.ts new file mode 100644 index 0000000..12df5ae --- /dev/null +++ b/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 { + 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(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 => { + const { code, refid } = params; + const { amount } = options; + let response; + + try { + response = await axios.post(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']; + } +} diff --git a/test/idpay.spec.ts b/test/idpay.spec.ts index c20960d..ee7be26 100644 --- a/test/idpay.spec.ts +++ b/test/idpay.spec.ts @@ -18,9 +18,9 @@ describe('IdPay Driver', () => { const driver = getPaymentDriver('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 () => { diff --git a/test/nextpay.spec.ts b/test/nextpay.spec.ts index 110cef4..ffd329a 100644 --- a/test/nextpay.spec.ts +++ b/test/nextpay.spec.ts @@ -18,9 +18,9 @@ describe('NextPay Driver', () => { const driver = getPaymentDriver('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 () => { diff --git a/test/payir.spec.ts b/test/payir.spec.ts index d2ca5dd..1070e0b 100644 --- a/test/payir.spec.ts +++ b/test/payir.spec.ts @@ -18,9 +18,9 @@ describe('Payir Driver', () => { const driver = getPaymentDriver('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 () => { diff --git a/test/payping.spec.ts b/test/payping.spec.ts new file mode 100644 index 0000000..c41516d --- /dev/null +++ b/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; +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', { 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', { 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', { apiKey: '2134' }); + + expect( + ( + await driver.verifyPayment( + { amount: 2000 }, + { + code: '1234', + cardhashpan: 'hash', + clientrefid: 'clientrefid', + cardnumber: '1234-****-****-1234', + refid: '1234', + } + ) + ).transactionId + ).toEqual(expectedResult.transactionId); + }); +}); diff --git a/test/zibal.spec.ts b/test/zibal.spec.ts index 3797ba5..5ffc1cf 100644 --- a/test/zibal.spec.ts +++ b/test/zibal.spec.ts @@ -19,9 +19,9 @@ describe('Zibal Driver', () => { const driver = getPaymentDriver('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 () => {