From 0b2c878aee8238762e576c8093583d87f80ad4e2 Mon Sep 17 00:00:00 2001 From: martian56 Date: Wed, 22 Jul 2026 12:32:47 +0400 Subject: [PATCH] feat: add enums for the values the API uses Values come from the sandbox's own enum module and the contract notes, so they match what the API actually sends rather than what the docs imply. Currency records that checkout takes AZN, USD, EUR and RUB while split, pre-auth, refund, reverse, payout and wallet take AZN only. Raw strings still work everywhere, so nothing that already compiles breaks. --- src/client.ts | 7 +++-- src/enums.ts | 75 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 13 ++++++++ src/response.ts | 6 ++-- src/transport.ts | 3 +- test/enums.test.ts | 17 +++++++++++ 6 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 src/enums.ts create mode 100644 test/enums.test.ts 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) + }) +})