diff --git a/apps/api/src/routes/sync.test.ts b/apps/api/src/routes/sync.test.ts index 158d81b..66ff42c 100644 --- a/apps/api/src/routes/sync.test.ts +++ b/apps/api/src/routes/sync.test.ts @@ -260,7 +260,7 @@ describe('sync routes — veesp', () => { return { ok: true, json: async () => ({ - details: { currency: 'EUR', acc_balance: '100.00', acc_credit: '0.00' }, + details: { currency: 'EUR', acc_balance: '0.00', acc_credit: '100.00' }, }), } } diff --git a/apps/api/src/services/veesp/operations.test.ts b/apps/api/src/services/veesp/operations.test.ts index 8240ad0..d9d7549 100644 --- a/apps/api/src/services/veesp/operations.test.ts +++ b/apps/api/src/services/veesp/operations.test.ts @@ -1,8 +1,35 @@ import { describe, expect, it } from 'vitest' -import { parseVeespCollection } from './operations.js' +import { parseVeespBalanceResponse, parseVeespCollection } from './operations.js' import type { VeespIpItem, VeespVmListItem } from './operations.js' +describe('parseVeespBalanceResponse', () => { + it('uses acc_credit as available prepaid balance', () => { + const result = parseVeespBalanceResponse({ + success: true, + details: { currency: 'EUR', acc_balance: '0.00', acc_credit: '42.50' }, + }) + expect(result.balance).toBe(42.5) + expect(result.currency).toBe('EUR') + }) + + it('does not treat unpaid invoice total (acc_balance) as account balance', () => { + const result = parseVeespBalanceResponse({ + success: true, + details: { currency: 'USD', acc_balance: '123456.55', acc_credit: '0.00' }, + }) + expect(result.balance).toBe(0) + }) + + it('falls back to credit field when acc_credit is absent', () => { + const result = parseVeespBalanceResponse({ + details: { currency: 'RUB', credit: '15.25' }, + }) + expect(result.balance).toBe(15.25) + expect(result.currency).toBe('RUB') + }) +}) + describe('parseVeespCollection', () => { it('parses vms object-map from Veesp API', () => { const json = { diff --git a/apps/api/src/services/veesp/operations.ts b/apps/api/src/services/veesp/operations.ts index 6561ff6..dcf28cf 100644 --- a/apps/api/src/services/veesp/operations.ts +++ b/apps/api/src/services/veesp/operations.ts @@ -157,6 +157,28 @@ function parseAmount(raw: string | number | undefined | null): number { return Number.isFinite(n) ? n : 0 } +/** + * Парсит ответ GET /balance Veesp/HostBill User API. + * acc_credit — предоплаченные средства на счёте; acc_balance — сумма неоплаченных инвойсов. + * @see https://secure.veesp.com/userapi — Billing → Account balance + */ +export function parseVeespBalanceResponse( + json: Record, + fallbackCurrency = 'EUR', +): VeespBalanceResult { + const details = + (json.details as Record | undefined) ?? + (json.balance as Record | undefined) ?? + json + const currency = String(details.currency ?? fallbackCurrency).trim() || fallbackCurrency + const credit = parseAmount(details.acc_credit as string | number | undefined) + const direct = parseAmount( + (details.credit ?? details.balance) as string | number | undefined, + ) + const balance = credit || direct + return { balance, currency, enoughmoneyto: '' } +} + function normalizeSlug(value: string | undefined | null): string { return String(value ?? '') .trim() @@ -388,13 +410,7 @@ export async function fetchBalance( ): Promise { const client = clientFor(baseUrl, credentials) const json = await client.request>('/balance') - const details = - (json.details as Record | undefined) ?? - (json.balance as Record | undefined) ?? - json - const currency = String(details.currency ?? fallbackCurrency).trim() || fallbackCurrency - const balance = parseAmount(details.acc_balance as string | number | undefined) - return { balance, currency, enoughmoneyto: '' } + return parseVeespBalanceResponse(json, fallbackCurrency) } export async function fetchInvoices(baseUrl: string, credentials: string): Promise {