diff --git a/src/client.ts b/src/client.ts index 047cfe9..3c99e3c 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,5 @@ import { verifyCallback } from './callback' +import { Currency, Language } from './enums' import { TransportError } from './errors' import type { EpointCallback, EpointResponse } from './response' import { buildBody, type Payload, parse } from './transport' @@ -39,8 +40,8 @@ export class EpointClient { this.publicKey = options.publicKey this.privateKey = options.privateKey this.baseUrl = (options.baseUrl ?? PRODUCTION_URL).replace(/\/+$/, '') - this.language = options.language ?? 'az' - this.currency = options.currency ?? 'AZN' + this.language = options.language ?? Language.AZ + this.currency = options.currency ?? Currency.AZN this.successRedirectUrl = options.successRedirectUrl this.errorRedirectUrl = options.errorRedirectUrl this.timeoutMs = options.timeoutMs ?? 30_000 @@ -58,7 +59,7 @@ export class EpointClient { publicKey, privateKey, baseUrl: env.EPOINT_BASE_URL ?? PRODUCTION_URL, - language: env.EPOINT_LANGUAGE ?? 'az', + language: env.EPOINT_LANGUAGE ?? Language.AZ, successRedirectUrl: env.EPOINT_SUCCESS_REDIRECT_URL, errorRedirectUrl: env.EPOINT_FAILED_REDIRECT_URL, ...overrides, diff --git a/src/enums.ts b/src/enums.ts new file mode 100644 index 0000000..e5327f8 --- /dev/null +++ b/src/enums.ts @@ -0,0 +1,75 @@ +/** The status on a response or a callback. */ +export const Status = { + NEW: 'new', + SUCCESS: 'success', + FAILED: 'failed', + ERROR: 'error', + RETURNED: 'returned', + SERVER_ERROR: 'server_error', +} as const + +export type Status = (typeof Status)[keyof typeof Status] + +export const SETTLED_STATUSES: ReadonlySet = new Set([Status.SUCCESS]) + +export const CardStatus = { + NEW: 'new', + ACTIVE: 'active', + PENDING: 'pending', + REJECTED: 'rejected', + EXPIRED: 'expired', + SESSION_EXPIRED: 'session_expired', +} as const + +export type CardStatus = (typeof CardStatus)[keyof typeof CardStatus] + +export const USABLE_CARD_STATUSES: ReadonlySet = new Set([CardStatus.ACTIVE]) + +export const InvoiceStatus = { + WAITING: 'waiting_for_payment', + PAID: 'paid', + CANCELED: 'canceled', +} as const + +export type InvoiceStatus = (typeof InvoiceStatus)[keyof typeof InvoiceStatus] + +export const B2BStatus = { + PENDING: 'PENDING', + PROCESSING: 'PROCESSING', + SUCCESS: 'SUCCESS', + FAILED: 'FAILED', +} as const + +export type B2BStatus = (typeof B2BStatus)[keyof typeof B2BStatus] + +/** What the transaction did, as it comes back on a callback. */ +export const OperationCode = { + CARD_REGISTRATION: '001', + PAYMENT: '100', + REGISTRATION_WITH_PAYMENT: '200', +} as const + +export type OperationCode = (typeof OperationCode)[keyof typeof OperationCode] + +export const Language = { + AZ: 'az', + EN: 'en', + RU: 'ru', +} as const + +export type Language = (typeof Language)[keyof typeof Language] + +export const Currency = { + AZN: 'AZN', + USD: 'USD', + EUR: 'EUR', + RUB: 'RUB', +} as const + +export type Currency = (typeof Currency)[keyof typeof Currency] + +/** Checkout takes any of these. */ +export const SUPPORTED_CURRENCIES: ReadonlySet = new Set(Object.values(Currency)) + +/** Split, pre-auth, refund, reverse, payout and wallet take AZN and nothing else. */ +export const AZN_ONLY: ReadonlySet = new Set([Currency.AZN]) diff --git a/src/index.ts b/src/index.ts index 7bc3b61..50e1a53 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,18 @@ export { verifyCallback } from './callback' export { EpointClient } from './client' +export { + AZN_ONLY, + B2BStatus, + CardStatus, + Currency, + InvoiceStatus, + Language, + OperationCode, + SETTLED_STATUSES, + Status, + SUPPORTED_CURRENCIES, + USABLE_CARD_STATUSES, +} from './enums' export { EpointError, GatewayError, SignatureError, TransportError } from './errors' export { type EpointCallback, EpointResponse } from './response' export type { diff --git a/src/response.ts b/src/response.ts index 53a2078..aa41f08 100644 --- a/src/response.ts +++ b/src/response.ts @@ -1,3 +1,5 @@ +import { SETTLED_STATUSES } from './enums' + function asString(value: unknown): string | undefined { return value === undefined || value === null ? undefined : String(value) } @@ -10,7 +12,7 @@ export class EpointResponse { } get ok(): boolean { - return this.raw.status === 'success' + return SETTLED_STATUSES.has(String(this.raw.status)) } get transaction(): string | undefined { @@ -62,7 +64,7 @@ export function toCallback(raw: Record): EpointCallback { cardName: (raw.card_name as string | null) ?? null, amount: raw.amount == null ? 0 : Number(raw.amount), otherAttr: raw.other_attr, - ok: status === 'success', + ok: SETTLED_STATUSES.has(status), raw, } } diff --git a/src/transport.ts b/src/transport.ts index 9a35918..cc68367 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -1,3 +1,4 @@ +import { Status } from './enums' import { GatewayError, TransportError } from './errors' import { EpointResponse } from './response' import { encodePayload, sign } from './signing' @@ -27,7 +28,7 @@ export function parse(statusCode: number, body: unknown): EpointResponse { } const record = body as Payload - if (record.status === 'error') { + if (record.status === Status.ERROR) { throw new GatewayError(String(record.message ?? 'request refused'), { code: record.code == null ? undefined : String(record.code), status: String(record.status), diff --git a/test/enums.test.ts b/test/enums.test.ts new file mode 100644 index 0000000..ade1dda --- /dev/null +++ b/test/enums.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { Currency, EpointResponse, Language, OperationCode, Status } from '../src/index' + +describe('enums', () => { + it('are plain strings on the wire', () => { + expect(JSON.stringify({ language: Language.AZ, currency: Currency.AZN })).toBe( + '{"language":"az","currency":"AZN"}', + ) + expect(OperationCode.PAYMENT).toBe('100') + expect(Status.SERVER_ERROR).toBe('server_error') + }) + + it('still reads a raw status string as ok', () => { + expect(new EpointResponse({ status: 'success' }).ok).toBe(true) + expect(new EpointResponse({ status: 'failed' }).ok).toBe(false) + }) +})