🏦 PrivatBank statement — for the developer
Updated
User: how to switch it on. Analyst: the package scope · setting up PrivatBank.
Package LocalizationUkraine, service IUaBank. The integration wire is PrivatBank
Autoclient API v3.0.0, not an invented JSON. The API description comes from
the Privat24 for Business integration cabinet / the Autoclient v3 Google Doc.
There is no live acp.privatbank.ua in CI.
Why there is no sandbox#
Autoclient v3 documents one HOST: https://acp.privatbank.ua. The
Environment=test field on the card is a label, not another URL. "MSB-TEST
LLC" in the sample JSON is data from the API description, not a cabinet.
Privat's sandbox (Open Banking XS2A + QWAC) is a different contract;
pasting it into UaBankConnection will break the headers, not "almost work".
Why this is not the Unified Register of Tax Invoices (ЄРПН)#
The Unified Register of Tax Invoices (ЄРПН) is a document flow: the IOutboundGateway queue, MakeDoc → ToGov → receipt. A
statement is a poll: the answer is needed now, a retry an hour later is a
different batch. So IOutboundCall and channel pb-statements with
method: GET.
Payments are not born from the statement. CustomerPayment / VendorPayment
without a matching company registry code (ЄДРПОУ) are somebody else's money on somebody else's contract.
This integration writes the UaBankStatement information register only — it
does not create CustomerPayment / VendorPayment rows from statement lines.
Outbound payment creation (POST /api/proxy/payment/create + add-sign КЕП) is
out of scope here: a script cannot produce a Ukrainian DSTU (ДСТУ) signature, and
inventing one is a bank fine, not our validation.
Official names, not third-party libraries#
Required headers in the v3 description: User-Agent, token, Content-Type.
There is no separate id header — third-party clients added that. The channel
is authMode: Header, authHeader: token. The value lives in
Integration:Credentials, the name on UaBankConnection.CredentialRef.
HOST https://acp.privatbank.ua, path /api/statements/transactions. On the
card BaseUrl = https://acp.privatbank.ua/api, RelativePath appends
/statements/transactions?.... Dates DD-MM-YYYY. Page limit 100 (the official
recommendation, max 500). Pagination: exist_next_page + next_page_id →
followId. Transaction uniqueness: REF+REFN. TRANTYPE C/D. At most 1
request per second. Before the statement — GET /statements/settings: phase
must be WRK, work_balance must not be Y.
GET with no body#
HttpOutboundTransport (and the agent hop) must not always attach
StringContent. Autoclient GET with Content-Length is a 400. The host
builds the request through OutboundHttpRequest: GET/HEAD have no Content.
The attempt journal masks the token header the same way as authorization /
x-api-key.
Without a Core rebuild the GET body still goes out as before — a live ACP
statement will not be imported, and the integration test does not go red for that: it
feeds ImportBodyAsync a sample JSON.
Channel#
| Channel | Autoclient method |
|---|---|
pb-statements |
GET /statements/settings and /statements/transactions. Key pb-settings: / pb-tx:{code}:{page} |
connectionTable: UaBankConnection. The three Unified Register of Tax Invoices (ЄРПН) channels still read
TaxAuthorityConnection. A bank token does not go on the tax card.
Job PullUaBankStatements (20 6 * * *) calls IUaBank.PullAllAsync. It
imports only FL_REAL=r and PR_PR=r. Reversals and interim rows stay out of
the register.
Tests: UkraineBankStatementTest (sample from the API description →
IR_UaBankStatement, REF+REFN dedup, channel metadata) and
UkraineBankConnectionTest (the Unified Register of Tax Invoices (ЄРПН) ≠ bank).
Same topic for other readers: Analyst · User