148 lines
3.5 KiB
TypeScript
148 lines
3.5 KiB
TypeScript
import { getCsrfToken } from '@shared/api/csrf'
|
|
import { refreshAccessToken } from '@shared/api/tokenStore'
|
|
|
|
const B2B_API_URL = 'https://app.b2b.elcsa.ru'
|
|
|
|
async function doB2bRequest<T>(
|
|
path: string,
|
|
options: RequestInit,
|
|
allowRetry: boolean,
|
|
): Promise<T> {
|
|
const csrf = await getCsrfToken()
|
|
|
|
const res = await fetch(`${B2B_API_URL}${path}`, {
|
|
...options,
|
|
credentials: 'include',
|
|
headers: {
|
|
'X-CSRF-Token': csrf,
|
|
...options.headers,
|
|
},
|
|
})
|
|
|
|
if (res.status === 401 && allowRetry) {
|
|
try {
|
|
await refreshAccessToken()
|
|
return doB2bRequest<T>(path, options, false)
|
|
} catch {
|
|
throw new Error('Unauthorized')
|
|
}
|
|
}
|
|
|
|
const data = await res.json()
|
|
if (!res.ok) throw data
|
|
return data as T
|
|
}
|
|
|
|
export interface CreatePurchaseRequestBody {
|
|
rub_amount: number | string
|
|
comment?: string | null
|
|
target_wallet_chain?: string | null
|
|
target_wallet_address?: string | null
|
|
}
|
|
|
|
export interface B2bPurchaseRequest {
|
|
id: string
|
|
organization_id: string
|
|
status: string
|
|
usdt_amount: string
|
|
rub_amount: string | null
|
|
exchange_rate: string | null
|
|
service_fee_percent: string | null
|
|
comment: string | null
|
|
admin_comment: string | null
|
|
target_wallet_chain: string | null
|
|
target_wallet_address: string | null
|
|
tx_hash: string | null
|
|
created_at: string | null
|
|
updated_at: string | null
|
|
completed_at: string | null
|
|
}
|
|
|
|
export interface B2bPurchaseRequestListResponse {
|
|
items: B2bPurchaseRequest[]
|
|
total: number
|
|
}
|
|
|
|
export function createPurchaseRequest(
|
|
body: CreatePurchaseRequestBody,
|
|
): Promise<B2bPurchaseRequest> {
|
|
return doB2bRequest('/purchase-requests', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
}, true)
|
|
}
|
|
|
|
export function getMyPurchaseRequests(
|
|
limit = 50,
|
|
offset = 0,
|
|
): Promise<B2bPurchaseRequestListResponse> {
|
|
return doB2bRequest(`/purchase-requests?limit=${limit}&offset=${offset}`, {}, true)
|
|
}
|
|
|
|
// --- Organizations (legal-entity wallets) ---
|
|
// Note: organizations endpoints live under /v1, unlike /purchase-requests.
|
|
|
|
export interface OrgWallet {
|
|
id: string
|
|
chain: string
|
|
address: string
|
|
derivation_path: string
|
|
created_at: string | null
|
|
}
|
|
|
|
export interface OrgAmountRaw {
|
|
raw: string
|
|
formatted: string
|
|
decimals: number
|
|
// null when pricing is unavailable (e.g. an upstream provider error).
|
|
usd_price: string | null
|
|
usd_value: string | null
|
|
}
|
|
|
|
export interface OrgWalletBalance {
|
|
id: string
|
|
chain: string
|
|
address: string
|
|
derivation_path: string
|
|
native_symbol: string
|
|
native: OrgAmountRaw
|
|
tokens: Record<string, OrgAmountRaw>
|
|
// null when the wallet's balance could not be fetched (see `error`).
|
|
total_usd: string | null
|
|
error: string | null
|
|
}
|
|
|
|
export interface OrgBalancesResponse {
|
|
items: OrgWalletBalance[]
|
|
total_usd: string | null
|
|
has_errors: boolean
|
|
}
|
|
|
|
export interface OrgMnemonicResponse {
|
|
mnemonic: string
|
|
}
|
|
|
|
export interface OrgSecretKey {
|
|
chain: string
|
|
address: string
|
|
derivation_path: string
|
|
private_key: string
|
|
}
|
|
|
|
export function getOrganizationWallets(): Promise<OrgWallet[]> {
|
|
return doB2bRequest('/v1/organizations/wallets', {}, true)
|
|
}
|
|
|
|
export function getOrganizationBalances(): Promise<OrgBalancesResponse> {
|
|
return doB2bRequest('/v1/organizations/wallets/balances', {}, true)
|
|
}
|
|
|
|
export function getOrganizationMnemonic(): Promise<OrgMnemonicResponse> {
|
|
return doB2bRequest('/v1/organizations/wallets/mnemonic', {}, true)
|
|
}
|
|
|
|
export function getOrganizationSecretKeys(): Promise<OrgSecretKey[]> {
|
|
return doB2bRequest('/v1/organizations/wallets/secret-keys', {}, true)
|
|
}
|