From 59988871484148acd24f151d5da9de26a04a1eb9 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Mon, 29 Jun 2026 15:43:54 +0700 Subject: [PATCH] =?UTF-8?q?fix(veesp):=20=D1=87=D0=B8=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20acc=5Fcredit=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?acc=5Fbalance=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B1=D0=B0=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- apps/api/src/routes/sync.test.ts | 2 +- .../api/src/services/veesp/operations.test.ts | 29 +++++++++++++++++- apps/api/src/services/veesp/operations.ts | 30 ++++++++++++++----- 3 files changed, 52 insertions(+), 9 deletions(-) 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 {