Skip to content

Commit

Permalink
Added Parsian driver
Browse files Browse the repository at this point in the history
  • Loading branch information
alitnk committed Oct 17, 2021
1 parent cae66c7 commit 8c06ed3
Show file tree
Hide file tree
Showing 4 changed files with 366 additions and 0 deletions.
5 changes: 5 additions & 0 deletions src/drivers.ts
Expand Up @@ -5,6 +5,8 @@ import { IdPay } from './drivers/idpay';
import * as IdPayAPI from './drivers/idpay/api';
import { NextPay } from './drivers/nextpay';
import * as NextPayAPI from './drivers/nextpay/api';
import { Parsian } from './drivers/parsian';
import * as ParsianAPI from './drivers/parsian/api';
import { Payir } from './drivers/payir';
import * as PayirAPI from './drivers/payir/api';
import { PayPing } from './drivers/payping';
Expand All @@ -21,6 +23,7 @@ import * as ZibalAPI from './drivers/zibal/api';
export { Behpardakht } from './drivers/behpardakht';
export { IdPay } from './drivers/idpay';
export { NextPay } from './drivers/nextpay';
export { Parsian } from './drivers/parsian';
export { PayPing } from './drivers/payping';
export { Sadad } from './drivers/sadad';
export { Saman } from './drivers/saman';
Expand All @@ -31,6 +34,7 @@ interface ConfigMap {
idpay: IdPayAPI.Config;
nextpay: NextPayAPI.Config;
payir: PayirAPI.Config;
parsian: ParsianAPI.Config;
payping: PayPingAPI.Config;
sadad: SadadAPI.Config;
saman: SamanAPI.Config;
Expand All @@ -47,6 +51,7 @@ const drivers = {
idpay: IdPay,
nextpay: NextPay,
payir: Payir,
parsian: Parsian,
payping: PayPing,
sadad: Sadad,
saman: Saman,
Expand Down
182 changes: 182 additions & 0 deletions src/drivers/parsian/api.ts
@@ -0,0 +1,182 @@
import * as t from 'io-ts';
import { BaseReceipt, LinksObject, tBaseRequestOptions, tBaseVerifyOptions } from '../../types';

/*
* Parsian's API
* Currency: IRR
*
* Docs: https://miladworkshop.ir/blog/19-parsian-gateway-sample-code.html
*/

export const links: LinksObject = {
default: {
REQUEST: 'https://pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?wsdl',
VERIFICATION: 'https://pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?wsdl',
PAYMENT: '‫‪https://pec.shaparak.ir/NewIPG/',
},
};

/**
* Request of `SalePaymentRequest`
*/
export interface RequestPaymentReq {
/**
* فروشنده پین
*/
LoginAccount: string;

/**
* پرداخت مبلغ
*/
Amount: number;

/**
* شماره سفارش - بایت یکتا باشد و یکتایی آن از جانب بانک نیز کنترل شود
*/
OrderId: number;

/**
* صفحه بازگشت مشتری به وب سایت پذیرنده، ‌پس از انجام عمل پرداخت.
*/
CallBackUrl: string;

/**
* داده اضافی
*/
AdditionalData: string;
}

/**
* Response of `SalePaymentRequest`
*/
export interface RequestPaymentRes {
/**
* شماره درخواست در دروازه پرداخت که یک شماره تصادفی و یکتا برای تمامی عملیات تراکنش می باشد و فروشگاه ملزم به ثبت و نگهداری این کد است
*/
Token?: number | string;

/**
* کد وضعیت در عملیات موفق صفر است
*/
Status: number;
}

/**
* NOTE: this gets sent on POST method
*/
export interface CallbackParams {
Token: number | string;
status: number | string;
OrderId: number | string;
TerminalNo: number | string;
RRN: number | string;
HashCardNumber: string;
Amount: number;
}

/**
* Request of `ConfirmPayment`
*/
export interface VerifyPaymentReq {
/**
* پذیرنده شناسایی کد
*/
LoginAccount: string;

/**
* پرداخت دروازه در درخواست شماره
*/
Token: number | string;
}

/**
* Response of `ConfirmPayment`
*/
export interface VerifyPaymentRes {
/**
* کد وضعیت عملیات که در صورت موفقیت صفر است
*/
Status: number | string;

/**
* شماره مکرجع
*/
RRN: number;

/**
* شماره کارت کاربر به صورت ماسک شده
*/
CardNumberMasked: string;

/**
* شماره درخواست تراکنش دروازه پرداخت پارسیان
*/
Token: number | string;
}

/**
* Request of `ReversalRequest`
*/
export type ReversalPaymentReq = VerifyPaymentReq;

/**
* Response of `ReversalRequest`
*/
export interface ReversalPaymentRes {
/**
* کد وضعیت عملیات که در صورت موفقیت صفر است
*/
Status: string;

/**
* شماره درخواست تراکنش دروازه پرداخت پارسیان
*/
Token: number | string;
}

// export const errors: ErrorList = {
// // UnkownError
// '-32768': 'خطای ناشناخته رخ داده است',
// // Payment RequestIsNotEligibleToReversal
// '-1552': 'برگشت تراکنش مجاز نمی باشد',
// // Payment RequestsAlreadIsReversed
// '-1551': 'برگشت تراکت قبلا انجام شده است',
// // PaymentequestStatusIsNotReversalable
// '-1550': 'برگشت تراکش در وضعیت جاری امکان پذیر نمی باشد',
// // MaxAllowedTimeToReversalHasExceeded
// '-1549': 'زمان مجاز برای در خواست برگشت تراکنش به اتمام رسیده است',
// // BillPaymentRequestServiceFailed
// '-1548': 'فراخوانی سرویس درخواست پرداخت قبض ناموفق بود ',
// // InvalidConfigmRequestService
// '-1540': 'تایید تراکنش ناموفق می باشد',
// //TopupChargeServiceTopupChargeRequestFailed
// '-1536': 'فراخوانی سرویس درخواست شارژ تاپ آپ الاموفق بود',
// // PaymentIsAlreadyConfirmed
// '-1533': 'تراکنش قلا تایید شده است',
// // MerchantHasConfirmedPaymentRequest
// '-1532': 'تراکنش از سوی پذیرنده تایید شد',
// // CannotConfirmNonSuccessfulPayment
// '-1531': 'تایید تراکنش ناموفق امکان پذیر نمی باشد',
// // there's like 6 more pages of these errors
// // TODO import the errors?
// };

/*
* Package's API
*/

export const tConfig = t.interface({
merchantId: t.string,
});

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

export const tRequestOptions = t.intersection([t.partial({}), 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>;
94 changes: 94 additions & 0 deletions src/drivers/parsian/index.ts
@@ -0,0 +1,94 @@
import soap from 'soap';
import { Driver } from '../../driver';
import { PaymentException, RequestException, VerificationException } from '../../exceptions';
import * as API from './api';

export class Parsian 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);

const { amount, callbackUrl, description } = options;
const { merchantId } = this.config;
const client = await soap.createClientAsync(this.getLinks().REQUEST);

const requestFields: API.RequestPaymentReq = {
Amount: amount,
CallBackUrl: callbackUrl,
AdditionalData: description || '',
LoginAccount: merchantId,
OrderId: this.generateId(),
};

const response: API.RequestPaymentRes = client.SalePaymentRequest(requestFields);

const { Status, Token } = response;
if (Status.toString() !== '0' || typeof Token === 'undefined') {
throw new RequestException('خطایی در درخواست پرداخت به‌وجود آمد');
}

return this.makeRequestInfo(Token, 'GET', this.getLinks().PAYMENT, {
Token,
});
};

verifyPayment = async (_options: API.VerifyOptions, params: API.CallbackParams): Promise<API.Receipt> => {
const { Token, status } = params;
const { merchantId } = this.config;

if (status.toString() !== '0') {
throw new PaymentException('تراکنش توسط کاربر لغو شد.');
}

const soapClient = await soap.createClientAsync(this.getLinks().VERIFICATION);

const requestFields: API.VerifyPaymentReq = {
LoginAccount: merchantId,
Token: +Token,
};

// 1. Verify
const verifyResponse: API.VerifyPaymentRes = soapClient.ConfirmPayment(requestFields);

const { CardNumberMasked, RRN, Status } = verifyResponse;
if (!(Status.toString() === '0' && RRN > 0)) {
const reversalRequestFields: API.ReversalPaymentReq = requestFields;
const reversalResponse: API.ReversalPaymentRes = soapClient.ReversalRequest(reversalRequestFields);
if (reversalResponse.Status !== '0') {
throw new VerificationException('خطایی در تایید پرداخت به‌وجود آمد و مبلغ بازگشته نشد.');
}
throw new VerificationException('خطایی در تایید پرداخت به‌وجود آمد');
}

return {
transactionId: RRN,
cardPan: CardNumberMasked,
raw: verifyResponse,
};
};

/**
* YYYYMMDD
*/
dateFormat(date = new Date()) {
const yyyy = date.getFullYear();
const mm = date.getMonth() + 1;
const dd = date.getDate();
return yyyy.toString() + mm.toString() + dd.toString();
}

/**
* HHMMSS
*/
timeFormat(date = new Date()) {
const hh = date.getHours();
const mm = date.getMonth();
const ss = date.getSeconds();
return hh.toString() + mm.toString() + ss.toString();
}
}
85 changes: 85 additions & 0 deletions test/drivers/parsian.spec.ts
@@ -0,0 +1,85 @@
import { Parsian } from '../../src/drivers/parsian';
import * as API from '../../src/drivers/parsian/api';
import { RequestException } from '../../src/exceptions';
import { getPaymentDriver } from '../../src/drivers';

const mockSoapClient: any = {};
jest.mock('soap', () => ({
createClientAsync: async () => mockSoapClient,
}));

// const mockedSoap = soap as jest.Mocked<typeof soap>;
describe('Parsian Driver', () => {
it('returns the correct payment url', async () => {
const serverResponse: API.RequestPaymentRes = {
Token: 123,
Status: 0,
};

mockSoapClient.SalePaymentRequest = () => serverResponse;

const driver = getPaymentDriver<Parsian>('parsian', {
merchantId: 'merchant-id',
});

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

it('throws payment errors accordingly', async () => {
const serverResponse: API.RequestPaymentRes = {
Status: 1,
};

mockSoapClient.SalePaymentRequest = () => serverResponse;

const driver = getPaymentDriver<Parsian>('parsian', {
merchantId: 'merchant-id',
});

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

it('verifies the purchase correctly', async () => {
const serverResponse: API.VerifyPaymentRes = {
RRN: 123456789,
CardNumberMasked: '1234-****-****-1234',
Status: 0,
Token: 12345,
};
const callbackParams: API.CallbackParams = {
Amount: 20000,
HashCardNumber: 'hashed-card',
OrderId: 1234,
RRN: 123456789,
TerminalNo: 22,
Token: 12345,
status: 0,
};

const expectedResult: API.Receipt = { transactionId: 123456789, raw: serverResponse };

mockSoapClient.ConfirmPayment = () => serverResponse;
mockSoapClient.ReversalRequest = () => serverResponse;

const driver = getPaymentDriver<Parsian>('parsian', {
merchantId: 'merchant-id',
});

expect(await (await driver.verifyPayment({ amount: 2000 }, callbackParams)).transactionId).toBe(
expectedResult.transactionId
);
});
});

0 comments on commit 8c06ed3

Please sign in to comment.