UNPKG

tonb-merchant-api-client

Version:

Merchant API client is a library to interact with TONB Merchant API.

226 lines (200 loc) 5.81 kB
import axios, { AxiosInstance, AxiosResponse } from 'axios'; import { Provider } from '../../client/invoice-manager'; import { CreateInvoiceData, Invoice, InvoiceID, InvoiceStats, DataContainer, InvoiceUpdate, } from '../../models/invoice'; import { CancelInvoiceRequestData, CancelInvoiceResponseData, CreateInvoiceRequestData, CreateInvoiceResponseData, GetInvoiceInfoResponseData, GetInvoiceStatsResponseData, InvoiceResponse, } from './types'; import axiosRetry from 'axios-retry'; import { createHmac } from 'crypto'; type Settings = | { url: string; apiKey: string; merchantId: number; } | { client: AxiosInstance }; /** HttpProvider provides methods to communicate with Merchant API via HTTP. */ export class HttpProvider implements Provider { private readonly _apiKey: string; private readonly _client: axios.AxiosInstance; constructor(settings: Settings) { // if client is provided then use it if ('client' in settings) { this._client = settings.client; // Accessing the Authorization header const headers = settings.client.defaults.headers; this._apiKey = String(headers['Authorization']); if (!this._apiKey) { console.error('Authorization header not found'); } return; } this._apiKey = settings.apiKey; this._client = axios.create({ baseURL: new URL(`/m/${settings.merchantId}/`, settings.url).href, headers: { Authorization: settings.apiKey, }, }); axiosRetry(axios, { retries: 3, }); } async createInvoice( data: CreateInvoiceData ): Promise<DataContainer<Invoice>> { let resp: axios.AxiosResponse<CreateInvoiceResponseData>; try { resp = await this._sendWithAuth<CreateInvoiceResponseData>( 'POST', `/invoice`, data ); } catch (e) { return this._invalidData(e); } if (!this._isSuccess(resp)) { return this._invalidData(); } return { data: this._parseInvoiceResponse(resp.data.data), isValid: true, }; } async cancelInvoice(invoiceId: InvoiceID): Promise<DataContainer<Invoice>> { let resp: axios.AxiosResponse<CancelInvoiceResponseData>; try { resp = await this._sendWithAuth<CancelInvoiceResponseData>( 'PATCH', `/invoice/cancel`, { invoiceId, } ); } catch (e) { return this._invalidData(e); } if (!this._isSuccess(resp)) { return this._invalidData(); } return { data: this._parseInvoiceResponse(resp.data.data), isValid: true, }; } async getInvoiceInfo(invoiceId: InvoiceID): Promise<DataContainer<Invoice>> { let resp: axios.AxiosResponse<GetInvoiceInfoResponseData>; try { resp = await this._sendWithAuth<GetInvoiceInfoResponseData>( 'GET', `/invoice/info?id=${invoiceId}` ); } catch (e) { return this._invalidData(e); } if (!this._isSuccess(resp)) { return this._invalidData(); } return { data: this._parseInvoiceResponse(resp.data.data), isValid: true, }; } async getInvoiceStats(): Promise<DataContainer<InvoiceStats>> { let resp: axios.AxiosResponse<GetInvoiceStatsResponseData>; try { resp = await this._sendWithAuth<GetInvoiceStatsResponseData>( 'GET', `/invoice/stats` ); } catch (e) { return this._invalidData(e); } if (!this._isSuccess(resp)) { return this._invalidData(); } return { data: { ...resp.data.data, }, isValid: true, }; } isUpdateValid(update: InvoiceUpdate): boolean { const { data } = update; const { sign, ...others } = data; const response = Object.entries(others) .sort() // eslint-disable-next-line @typescript-eslint/no-unused-vars .map(([_key, value]) => value) .join(';'); const hmac = createHmac('sha256', this._apiKey); hmac.update(response); const signReceived = hmac.digest('hex'); return sign === signReceived; } private _sendWithAuth<Res>( method: axios.Method, path: string, data?: CreateInvoiceRequestData | CancelInvoiceRequestData | null ): Promise<axios.AxiosResponse<Res>> { switch (method) { case 'POST': return this._client.post<Res>(path, data); case 'GET': return this._client.get<Res>(path); case 'PATCH': return this._client.patch<Res>(path, data); } throw new Error(`unsupported method: ${method}`); } private _invalidData<T>(e?: unknown): DataContainer<T> { return { data: null, isValid: false, error: e, }; } private _parseInvoiceResponse(invoiceResponse: InvoiceResponse): Invoice { return { ...invoiceResponse, id: Number(invoiceResponse.id), amount: BigInt(invoiceResponse.amount), order_id: BigInt(invoiceResponse.order_id), createdAt: invoiceResponse.createdAt ? Date.parse(invoiceResponse.createdAt) : undefined, updatedAt: invoiceResponse.updatedAt ? Date.parse(invoiceResponse.updatedAt) : undefined, user_from_id: invoiceResponse.user_from_id ? BigInt(invoiceResponse.user_from_id) : undefined, user_to_id: invoiceResponse.user_to_id ? BigInt(invoiceResponse.user_to_id) : undefined, wallet_from_id: invoiceResponse.wallet_from_id ? Number(invoiceResponse.wallet_from_id) : undefined, wallet_to_id: invoiceResponse.wallet_to_id ? Number(invoiceResponse.wallet_to_id) : undefined, }; } private _isSuccess(res: AxiosResponse): boolean { return Math.floor(res.status / 100) === 2 && !!res.data; } }