Nodea — logo

Rozliczenia (billing) — API v1 i MCP | Dokumentacja Nodea

Moduł Rozliczenia (billing) jest w API wyłącznie do odczytu. Eksponuje to, co web UI pokazuje klientowi: salda portfeli, księgę transakcji portfela oraz faktury wraz z pozycjami — i nic więcej. Dokument opisuje warstwę REST API v1 (/api/v1/billing/*) oraz narzędzia MCP z tego modułu.

Uwierzytelnianie i zakresy (scopes)

API v1 uwierzytelnia się osobistym tokenem Passport przez nagłówek Authorization: Bearer <token>. Wszystkie endpointy billing są chronione zakresem account:read (nie istnieje ścieżka zapisu). Cały moduł jest dodatkowo bramkowany flagą Pennant features:hosting-suite (odpowiada web'owym grupom /app/account/*). Limit zapytań: throttle:api-v1 = 240/min na użytkownika tokenu.

Read-only — co NIE jest eksponowane

Świadomie web-only (interaktywne i/lub powiązane ze Stripe) i nieobecne w API:

  • Doładowania (topup, Stripe Checkout),
  • zarządzanie metodami płatności (SetupIntents + Stripe Elements),
  • konfiguracja auto-doładowania (mutacja — w API pola auto-recharge są tylko do odczytu),
  • pobieranie PDF faktury (renderowanie binarne, trasa web-only).

Odpowiedzi nie zawierają żadnych identyfikatorów Stripe (customer / payment method / intent) ani wewnętrznych: whmcs_id, wfirma_id, pdf_path. Moduł nigdy nie rozwiązuje klienta Stripe.

Własność i widoczność (ownership)

Ani Wallet, ani Invoice nie mają klasy polityki — dostęp na poziomie wiersza jest owner-scoped inline. Cudzy lub nieznany identyfikator zwraca konsekwentnie 404 (nie 403), by API nie potwierdzało istnienia wiersza innego najemcy.

API v1 — endpointy

MetodaŚcieżkaScopeOpis
GET/api/v1/billing/walletsaccount:readPortfele użytkownika (jeden wiersz na walutę). Ściśle read-only: NIE tworzy leniwie pary PLN/EUR, więc bez historii lista może być pusta.
GET/api/v1/billing/transactionsaccount:readKsięga transakcji portfeli, najnowsze pierwsze. Filtry wallet_id i/lub currency. Paginacja simple.
GET/api/v1/billing/invoicesaccount:readFaktury użytkownika (issued_at DESC, id DESC). Filtr status. Paginacja simple.
GET/api/v1/billing/invoices/{invoice}account:readJedna faktura z pozycjami (items[]). Ownership inline (cudza → 404).

GET /billing/wallets — odpowiedź

Kolekcja WalletResource — pola: id, currency (PLN|EUR), balance_cents, balance_formatted (string, np. „123,45 PLN”), auto_recharge_enabled, auto_recharge_threshold_cents, auto_recharge_amount_cents, is_below_threshold, created_at, updated_at. Pola auto-recharge są tylko do odczytu (zmiana jest web-only).

GET /billing/transactions — parametry i semantyka filtrów

  • wallet_id (int, opcjonalny) — nazywa konkretny zasób, więc cudzy/nieznany id to 404.
  • currency (enum PLN|EUR, opcjonalny) — zwykły filtr: wartość spoza enum to 422, poprawna waluta bez portfela zwraca pustą stronę.
  • per_page (int 1–100, domyślnie 20), page (int ≥1).

Oba filtry razem się przecinają (np. wallet_id portfela PLN z currency=EUR zwraca pustą stronę, nie błąd). Odpowiedź: kolekcja WalletTransactionResource — pola: id, wallet_id, type, amount_cents (kwota ze znakiem), balance_after_cents, currency (gdy relacja portfela doładowana), note, created_at. Wewnętrzne pola (klucz idempotencji, referencja polimorficzna, autor-admin, legacy WHMCS id) pozostają po stronie serwera.

GET /billing/invoices — parametry i pola

  • status (enum, opcjonalny) — wartość spoza enum to 422.
  • per_page (int 1–100, domyślnie 20), page (int ≥1).

Kolekcja InvoiceResource — pola: id, number, status, currency, subtotal_cents (netto), vat_rate, vat_cents, total_cents (brutto), issued_at, due_at (data), paid_at, period_start, period_end. Na liście klucz items jest nieobecny; w szczegółach faktury items[] to InvoiceItemResource: id, description, quantity (decimal, serializowany jako string, np. „2.5000”), unit_price_cents, subtotal_cents, vat_cents, total_cents. Pominięte: whmcs_id, wfirma_id, pdf_path oraz jakiekolwiek URL-e PDF.

Kody błędów

  • 401 — brak/niepoprawny token.
  • 403 — brak scope account:read lub nieaktywna flaga hosting-suite.
  • 404 — cudzy/nieznany wallet_id lub faktura.
  • 422 — walidacja filtrów (np. currency/status spoza enum).

Przykłady — API

Portfele

curl -s https://app.nodea.io/api/v1/billing/wallets \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

Transakcje z filtrem waluty

curl -s "https://app.nodea.io/api/v1/billing/transactions?currency=PLN&per_page=25" \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

Faktury opłacone

curl -s "https://app.nodea.io/api/v1/billing/invoices?status=paid" \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

Jedna faktura z pozycjami

curl -s https://app.nodea.io/api/v1/billing/invoices/123 \
  -H "Authorization: Bearer $NODEA_TOKEN" \
  -H "Accept: application/json"

MCP — narzędzia

Narzędzia MCP dzielą model uwierzytelniania i zakresów z API v1 (Bearer + scope sprawdzany w ensureScope) oraz flagę features:hosting-suite — narzędzie na nieaktywnej fladze znika z tools/list. Wszystkie są read-only (adnotacje IsReadOnly/IsIdempotent) i nigdy nie dotykają Stripe. Identyfikatory faktur to liczby całkowite; brakujący i cudzy id zwracają ten sam błąd (AuthorizationException: „Invoice not found or not accessible with your permissions.”) — brak enumeracji między najemcami.

NarzędzieScopeTrybOpis
billing-walletsaccount:readread-onlySalda portfeli (jeden wiersz na walutę): balance_cents, balance_formatted, konfiguracja auto-recharge. Bez parametrów. Świeże konto może zwrócić pustą listę.
billing-transactionsaccount:readread-onlyKsięga transakcji, najnowsze pierwsze. Filtry type i/lub currency, page, per_page (max 50, domyślnie 15).
billing-invoicesaccount:readread-onlyFaktury, najnowsze pierwsze (issued_at DESC, id DESC). Filtr status, page, per_page (max 50, domyślnie 15).
billing-invoice-getaccount:readread-onlyJedna faktura z pozycjami po id. Owner-scoped.

Filtr type w billing-transactions przyjmuje wartości WalletTransactionType (m.in. topup_stripe, topup_admin, refund, debit_meter, debit_invoice, adjustment). Filtr status w fakturach przyjmuje wartości InvoiceStatus (m.in. draft, issued, paid, void, migrated_historical). Uwaga: shape'y MCP są nieco bogatsze niż API — billing-invoice-get zwraca w pozycjach dodatkowo vat_rate, period_start, period_end (których nie ma w InvoiceItemResource), a walletArray pomija id portfela oraz is_below_threshold. Wewnętrzne pola (referencja polimorficzna, klucz idempotencji, atrybucja admina, wfirma_id/whmcs_id, pdf_path) nigdy nie są zwracane.

Przykład — MCP (JSON-RPC)

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "billing-transactions",
    "arguments": { "currency": "PLN", "type": "debit_meter", "per_page": 25 }
  }
}
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "billing-invoice-get",
    "arguments": { "id": 123 }
  }
}