tonb-merchant-api-client
Version:
Merchant API client is a library to interact with TONB Merchant API.
226 lines (200 loc) • 5.81 kB
text/typescript
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;
}
}