Skip to content

Conformance with the Tpay documentation

Every requirement of the official Tpay documentation that the add-on relies on, where it is implemented and where it is tested. Sources: the OpenAPI reference https://api.tpay.com and the guides https://docs-api.tpay.com (read 2026-11-07). “Flagged” = the documentation is silent or contradicts itself; the choice made is stated and nothing is guessed silently. The tests run against a mock built from this documentation (tests/mock-server/server.js), not against the Tpay sandbox (no account).

# Documented requirement Implementation Test
1 Authorization is OAuth 2.0; POST https://api.tpay.com/oauth/auth with client_id and client_secret (application/x-www-form-urlencoded) answers access_token, expires_in (7200); use Authorization: Bearer; do not fetch a token for every request Client\Api::tokenPath/tokenGrant, BaseClient::token (encrypted cache until shortly before expiry, one re-login on 401) unit “token request: form body, no grant_type”, e2e “mock saw POST /oauth/auth”
2 Sandbox server https://openapi.sandbox.tpay.com/ Api::baseUrl() (test mode) unit “token endpoint sandbox”, “live base url”
3 POST /transactions: required amount, currency (PLN), description, payer.email, payer.name, callbacks.payerUrls.success/error, callbacks.notification.url; optional hiddenDescription, lang, pay.groupId (the reference marks pay as required; the guides omit it - see developer.md “Flagged”); answer transactionId, title, transactionPaymentUrl Api::create, PaymentService::initialize unit “create body …”, e2e “create body …”
4 BLIK: pay.groupId 150; the payer enters the code on the Tpay page processor setting “BLIK only” unit “create body pay.groupId BLIK”, e2e create body
5 hiddenDescription identifies the order on the merchant side and must be unique (crc_not_unique); it returns as tr_crc PaymentService::initialize (kv<order>-<txn>-<8 hex>), Transactions::byCrc e2e “transaction stored pending with our reference”
6 Error body {"result":"failed","errors":[{errorCode, errorMessage, fieldName, ...}]} Api::errorFrom unit “400 error mapped”
7 Notifications are signed with JWS (RFC 7515), header X-JWS-Signature; verification is mandatory; certificate from x5u on the Tpay host, signed by https://secure.tpay.com/x509/tpay-jws-root.pem; RSA-SHA256 over header.base64url(body) Webhook::verifyJws unit “jws …” (11 cases incl. tampered body, rogue CA, foreign x5u, look-alike host, sandbox certificate on a live account), e2e “wrong JWS”, “without JWS”
8 md5sum = md5(id . tr_id . tr_amount . tr_crc . code); “should always be verified”; empty code when none is set Webhook::expectedMd5 unit “md5 …”, e2e “wrong md5sum rejected”
9 Answer HTTP 200 with the body TRUE; anything else makes Tpay resend (schedule 1 min x10, 3 min x10, 10 min x10, 1 h x5, 12 h, 24 h); a 404 stops resending controller kv_tpay.webhook: TRUE on accept, HTTP 400 FALSE on reject, never 404 e2e “notification accepted: HTTP 200 and the body TRUE”, rejects answer 400 FALSE
10 The same notification may arrive more than once and must not be processed twice; each notification of a transaction carries the same identifier event id md5(tr_id|tr_status) = unique job key; processing idempotent e2e “repeated notification is idempotent”, “one job row for the event”
11 Notification does not follow 301 / 302; TLS 1.2 callback URLs sent as https (Config::callbackProtocol); documented in Installation docs
12 tr_amount target amount, tr_paid amount paid (“may differ from the transaction amount”), tr_currency, test_mode PaymentService::applyNotification: amount, paid, currency must equal the order, test mode refused for live accounts e2e “paid amount that differs is not marked paid”
13 id = merchant id merchant id saved on the Connection page must equal id e2e “another merchant id rejected”
14 GET /transactions/{transactionId}: status pending / paid / correct / refund / canceled Api::get, Transition::fromProvider, PaymentService::sync (paid and correct = paid; amount, currency, hiddenDescription compared) unit status words, e2e “read-back with another amount is not marked paid”
15 tr_status true = paid, chargeback = full refund (standard refund) Webhook::kind, read-back sets refunded e2e “full refund: refunded, chargeback notification handled”
16 Full refund = POST /transactions/{id}/refunds with an empty body {}; partial = {"amount": 100.15}; a partial refund never changes the transaction status Api::refund, PaymentService::refund (local status follows the refunded sum) unit “full refund endpoint + empty object body”, e2e refund tests
17 Refund list GET /transactions/{id}/refunds; refund statuses new / pending / to_complete / cancel / hold / processed / done, cards declined / authorized Api::refunds, PaymentService::refreshRefunds, Transition::refundDead/refundDone e2e “a refund cancelled by Tpay restores the balance”
18 The refund needs sufficient balance; errors no_sufficient_funds_or_refund_limit_reached, refund_period_expired, already_refunded, cannot_create_refund shown in the log and on the order tab, nothing recorded as refunded e2e “an error from Tpay leaves the payment untouched”
19 Payment groups GET /transactions/bank-groups?onlyOnline=true Api::bankGroups, shown by “Test connection” e2e “test text names the payment groups”

Flagged (documentation gap or contradiction)

Section titled “Flagged (documentation gap or contradiction)”

See “Flagged” in developer.md: sandbox certificate host, token request encoding (guide: urlencoded, generated reference: multipart), token scope, refund id extraction, refundsNoAccountList status, no lookup by tr_id, PLN-only, BLIK Level 0 not in 1.0.

The add-on is not certified, approved or tested by Tpay; it was tested against a mock built from the public documentation. A first run on a Tpay sandbox account is the merchant’s acceptance test (Installation, step 2).