Skip to content

Developer notes

Technical reference for developers who customize Tpay Payments.

Item Value
Add-on id kv_tpay
Function prefix fn_kv_tpay_
Language variables kv_tpay.*
CSS scope .kv-tpay
PHP namespace Tygh\Addons\KvTpay
Preset payment (generated by kv new --kind integration)
Class Role
Client\BaseClient HTTP (cURL, TLS verification, timeouts), JSON, OAuth2 client-credentials with an encrypted token cache, error mapping, masked logging
Client\Api Tpay OpenAPI: token, bank groups, create transaction, read transaction, refund, refund list
Client Client per company, the connection test
PaymentService Create transaction, notification checks, read-back (GET /transactions/{id}), refunds and refund reconciliation
Transition Pure status logic: Tpay transaction status to local status to order status, never backwards
Accounts, Secret Credentials (test and live) sealed with AES-256-GCM; one account per store owner or per vendor
Config, Access Settings, owner mode, who may see and manage what
Queue, Jobs, JobException Job queue: back-off 5 min / 15 min / 1 h / 3 h / 12 h, per-job isolation, overlap lock; handlers in Jobs::handle(), recurring work in Jobs::schedule()
Log Event log with masking, filters, CSV, retention
Webhook Notification verification (JWS + md5sum), idempotent event ids; the controller kv_tpay.webhook?c=<account> stores the event as a job, answers TRUE and runs it at once
Table Purpose
?:kv_tpay_accounts Credentials per company (secrets sealed), merchant id, last connection check, token cache
?:kv_tpay_log Log
?:kv_tpay_jobs Queue
?:kv_tpay_transactions, ?:kv_tpay_refunds Payments (provider_ref = Tpay transactionId, title = TR-..., crc = our hiddenDescription) and refunds

PHP: change_order_status_post (automatic refund when an order reaches the chosen status), prepare_checkout_payment_methods (hides the method while it cannot start payments). Template hooks: orders:tabs_extra (payment tab), addons:manage / addons:tabs_content (licence), index:styles.

API reference used (official Tpay documentation)

Section titled “API reference used (official Tpay documentation)”

Sources: the OpenAPI reference https://api.tpay.com (Tpay Open API, servers https://api.tpay.com production and https://openapi.sandbox.tpay.com/ sandbox) and the guides at https://docs-api.tpay.com. Every call of the add-on:

Call Endpoint Fields used Source
Token POST /oauth/auth, application/x-www-form-urlencoded request client_id, client_secret; answer access_token, expires_in (7200), sent as Authorization: Bearer <token>; cached until shortly before it expires docs-api.tpay.com/first-steps/authorization
Create transaction POST /transactions, JSON amount (number), currency (PLN), description, hiddenDescription (our unique reference), lang, payer.{email,name,phone,address,code,city,country}, pay.groupId (BLIK 150 or a bank group), callbacks.payerUrls.{success,error}, callbacks.notification.url; answer transactionId, title, transactionPaymentUrl, status api.tpay.com post_transactions_create; docs-api.tpay.com BLIK page
Read transaction GET /transactions/{transactionId} answer status (pending, paid, correct, refund, canceled), amount, currency, hiddenDescription, title api.tpay.com get_transaction_get
Refund POST /transactions/{transactionId}/refunds, JSON full refund: empty body {}; partial refund: {"amount": 100.15}; answer refundsList / refundsCardsList / refundsNoAccountList items (refundId, status, amount, currency) docs-api.tpay.com/refunds; api.tpay.com post_transactions_create_refunds
Refund list GET /transactions/{transactionId}/refunds same lists; refund statuses new, pending, to_complete, cancel, hold, processed, done (transactional), declined, authorized (cards) docs-api.tpay.com/refunds; api.tpay.com
Payment groups GET /transactions/bank-groups?onlyOnline=true answer groups[].id, name (shown by “Test connection”) api.tpay.com get_transactions_get_bank_groups
Errors all endpoints {"result":"failed","requestId","errors":[{"errorCode","errorMessage","fieldName","devMessage","docUrl"}]} api.tpay.com; docs-api.tpay.com/error-codes

Source: https://docs-api.tpay.com/en/webhooks/. Tpay sends POST application/x-www-form-urlencoded with id (merchant id), tr_id (transaction title, TR-...), tr_date, tr_crc (our hiddenDescription), tr_amount, tr_paid, tr_currency, tr_desc, tr_status (true = paid, chargeback = full refund), tr_error, tr_email, md5sum, test_mode, and the header X-JWS-Signature.

  1. JWS (documented as mandatory): header base64url(header).<empty>.base64url(signature); the header JSON has x5u, the signing certificate URL. The add-on requires x5u to start with https://secure.tpay.com/ (live) or https://secure.sandbox.tpay.com/ (test; see “Flagged” below), downloads it (TLS verified), checks it against the root certificate of the same host (https://secure.tpay.com/x509/tpay-jws-root.pem for live accounts, https://secure.sandbox.tpay.com/x509/tpay-jws-root.pem for test accounts; openssl_x509_verify) and verifies RSA-SHA256 over header + "." + base64url(raw body). Certificates are cached for one hour; a failed check is repeated once with fresh certificates (rotation).
  2. md5sum: md5(id . tr_id . tr_amount . tr_crc . security code) over the values as received; the security code is the one of the merchant panel (empty string when none is set, as documented).
  3. Merchant: id must equal the merchant id saved on the Connection page.
  4. Answer: HTTP 200 and the body TRUE (exactly as documented). A notification that fails any check gets HTTP 400 and FALSE; never 404 (Tpay stops resending after a 404). The notification system does not follow 301/302 redirects (documented): the notification URL is sent as https without redirects.
  5. Idempotency (documented: the same notification may arrive more than once, each carries the same identifier): the event id is md5(tr_id | tr_status), stored as a unique job key; processing is idempotent.
  • Amounts are stored in grosz (integer). tr_amount, tr_paid, tr_currency of the notification and amount, currency, hiddenDescription of the read-back must equal what the add-on created; otherwise the job ends as “dead” with an error and the order is not paid.
  • The state always comes from GET /transactions/{id}: paid or correct = paid. A notification body is only a hint; one that arrives before Tpay reports the payment is retried.
  • A test-mode notification (test_mode=1) is refused for a live account.
  • Refunds: Tpay has no idempotency key. After an unclear answer (timeout, 5xx) the add-on reads the refund list and only records the refund when Tpay booked it; it never repeats a refund blindly. A refund that Tpay later cancels or declines gives the amount back to the refundable balance.
  • Return of the payer (payerUrls.success and error) reads the transaction back too; the same code runs for the notification, the cron and the “Synchronise” button.
Item What the add-on does
Certificate host of the sandbox The documentation shows https://secure.tpay.com in the example; the OpenAPI reference lists https://secure.sandbox.tpay.com/x509/notifications-jws.pem and tpay-jws-root.pem for the sandbox. Test-mode accounts accept only that host, live accounts only secure.tpay.com.
pay on create The generated reference marks pay (with blikPaymentData, cardPaymentData, …) as required on POST /transactions; the guides’ examples omit it. The add-on sends pay.groupId only when a payment group is chosen (BLIK or a group id) and otherwise lets the payer choose on the Tpay page.
Token request encoding The guide says application/x-www-form-urlencoded; the generated reference says multipart/form-data. The add-on uses the guide.
Token scope The example answer shows scope: read; the documentation does not say whether write calls need another scope. Create and refund are sent with the same token.
Refund id of an answer POST .../refunds answers the whole refund list; the new refund is identified as the item with an id we have not stored and the requested amount.
refundsNoAccountList status These items have no status field; they are treated as new.
Transaction lookup by tr_id The notification carries the title, not the transactionId. The add-on finds the order by tr_crc (our hiddenDescription) and reads back with the stored transactionId; it never looks up by title. A create request whose answer was lost has no transactionId and is not recovered automatically (the order stays unpaid, the notification is logged).
Currencies The create request documents currency = PLN. Orders in another currency are converted with the store’s rate to PLN (the store needs an active PLN currency). Other currencies exist only for cards on request (docs: Payments in other currencies) and are not part of this version.
BLIK Level 0 Documented (6-digit code entered in the shop); not implemented in 1.0: BLIK is paid on the Tpay page (group 150).

The base URLs can be overridden for tests in config.local.php (development only):

$config['kv_tpay_base_url'] = 'http://127.0.0.1:11871';
$config['kv_tpay_cert_base_url'] = 'http://127.0.0.1:11871';

node tests/mock-server/server.js 11871 starts a local stand-in of the documented API (it signs notifications with a throw-away CA, php tests/mock-server/gen-certs.php); node tests/e2e/e2e.mjs runs the HTTP end-to-end test against it; php tests/unit.php runs the unit tests with the documentation examples in tests/fixtures/. This proves the add-on against the documentation, not against the real Tpay sandbox.

Admin templates live in design/backend/templates/addons/kv_tpay/. Do not edit them in a store; they are replaced on update.