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
JavaScript
;
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