UNPKG

zarinpal-checkout

Version:

Type-safe ZarinPal checkout client for Node.js with modern tooling and backwards-compatible API methods.

189 lines (183 loc) 6.56 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const API_BASE_URL = { production: 'https://payment.zarinpal.com/pg/v4/payment/', sandbox: 'https://sandbox.zarinpal.com/pg/v4/payment/' }; const API_PATHS = { paymentRequest: 'request.json', paymentVerification: 'verify.json', unverifiedTransactions: 'unVerified.json', refreshAuthority: 'refresh.json' }; const START_PAY_BASE_URL = { production: 'https://www.zarinpal.com/pg/StartPay/', sandbox: 'https://sandbox.zarinpal.com/pg/StartPay/' }; const MERCHANT_ID_LENGTH = 36; // @ts-ignore TS5097: Node ESM tests require explicit .ts extension. const VALID_CURRENCIES = new Set(['IRR', 'IRT']); const DEFAULT_HEADERS = { accept: 'application/json', 'content-type': 'application/json' }; class FetchHttpClient { constructor(options) { this.timeoutMs = options.timeoutMs ?? 10000; this.headers = { ...DEFAULT_HEADERS, ...(options.requestConfig?.headers ?? {}), ...(options.axiosConfig?.headers ?? {}) }; } async request(config) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), this.timeoutMs); try { const response = await fetch(config.url, { method: config.method, headers: this.headers, body: JSON.stringify(config.data), signal: controller.signal }); const parsedData = await response.json(); if (!response.ok) { const httpError = new Error(`Request failed with status ${response.status}`); httpError.response = { data: parsedData }; throw httpError; } return { data: parsedData }; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { throw new Error(`Request timed out after ${this.timeoutMs}ms`); } throw error; } finally { clearTimeout(timeout); } } } function hasApiError(error) { if (typeof error !== 'object' || error === null) { return false; } const responseData = error.response?.data; return responseData !== undefined && typeof responseData === 'object' && responseData !== null && 'errors' in responseData; } class ZarinPalCheckout { constructor(merchantId, options = {}) { if (merchantId.length !== MERCHANT_ID_LENGTH) { throw new Error(`The MerchantID must be ${MERCHANT_ID_LENGTH} characters.`); } this.merchant = merchantId; this.sandbox = options.sandbox ?? false; this.currency = options.currency ?? 'IRT'; if (!VALID_CURRENCIES.has(this.currency)) { throw new Error("Invalid currency. Valid options are 'IRR' or 'IRT'."); } this.client = options.httpClient ?? new FetchHttpClient(options); } async PaymentRequest(input) { const metadata = { ...(input.Email !== undefined ? { email: input.Email } : {}), ...(input.Mobile !== undefined ? { mobile: input.Mobile } : {}) }; const data = await this.request(API_PATHS.paymentRequest, { merchant_id: this.merchant, currency: this.currency, amount: input.Amount, callback_url: input.CallbackURL, description: input.Description, ...(Object.keys(metadata).length > 0 ? { metadata } : {}) }); return { status: data.code, authority: data.authority ?? '', url: `${this.getStartPayUrl()}${data.authority ?? ''}` }; } async PaymentVerification(input) { const data = await this.request(API_PATHS.paymentVerification, { merchant_id: this.merchant, amount: input.Amount, authority: input.Authority }); return { status: data.code, message: data.message, ...(data.card_hash !== undefined ? { cardHash: data.card_hash } : {}), ...(data.card_pan !== undefined ? { cardPan: data.card_pan } : {}), ...(data.ref_id !== undefined ? { refId: data.ref_id } : {}), ...(data.fee_type !== undefined ? { feeType: data.fee_type } : {}), ...(data.fee !== undefined ? { fee: data.fee } : {}) }; } async UnverifiedTransactions() { const data = await this.request(API_PATHS.unverifiedTransactions, { merchant_id: this.merchant }); return { code: data.code, message: data.message, authorities: data.authorities ?? [] }; } async RefreshAuthority(input) { const data = await this.request(API_PATHS.refreshAuthority, { merchant_id: this.merchant, authority: input.Authority, expire: input.Expire }); return { code: data.code, message: data.message }; } TokenBeautifier(token) { return token.split(/\b0+/g); } getApiBaseUrl() { return this.sandbox ? API_BASE_URL.sandbox : API_BASE_URL.production; } getStartPayUrl() { return this.sandbox ? START_PAY_BASE_URL.sandbox : START_PAY_BASE_URL.production; } async request(path, payload) { const url = `${this.getApiBaseUrl()}${path}`; try { const response = await this.client.request({ url, method: 'POST', data: payload }); return response.data.data; } catch (error) { if (hasApiError(error) && error.response?.data?.errors) { throw error.response.data; } throw error; } } } // @ts-ignore TS5097: Node ESM tests require explicit .ts extension. const version = '1.0.0'; const createWithOptions = (merchantId, options = {}) => { return new ZarinPalCheckout(merchantId, options); }; const create = (merchantId, sandbox = false, currency = 'IRT') => { return createWithOptions(merchantId, { sandbox, currency }); }; const ZarinpalCheckout = { version, create, createWithOptions }; exports.ZarinPalCheckout = ZarinPalCheckout; exports.create = create; exports.createWithOptions = createWithOptions; exports.default = ZarinpalCheckout; exports.version = version; //# sourceMappingURL=index.cjs.map