Developer notes
Technical reference for developers who customize Tpay Payments.
Add-on identity
Section titled “Add-on identity”| 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) |
Structure
Section titled “Structure”| 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 |
Database tables
Section titled “Database tables”| 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 |
Hooks used
Section titled “Hooks used”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 |
Notification (webhook) verification
Section titled “Notification (webhook) verification”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.
- JWS (documented as mandatory): header
base64url(header).<empty>.base64url(signature); the header JSON hasx5u, the signing certificate URL. The add-on requiresx5uto start withhttps://secure.tpay.com/(live) orhttps://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.pemfor live accounts,https://secure.sandbox.tpay.com/x509/tpay-jws-root.pemfor test accounts;openssl_x509_verify) and verifies RSA-SHA256 overheader + "." + base64url(raw body). Certificates are cached for one hour; a failed check is repeated once with fresh certificates (rotation). - 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). - Merchant:
idmust equal the merchant id saved on the Connection page. - Answer: HTTP 200 and the body
TRUE(exactly as documented). A notification that fails any check gets HTTP 400 andFALSE; 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. - 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.
Money rules
Section titled “Money rules”- Amounts are stored in grosz (integer).
tr_amount,tr_paid,tr_currencyof the notification andamount,currency,hiddenDescriptionof 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}:paidorcorrect= 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.successanderror) reads the transaction back too; the same code runs for the notification, the cron and the “Synchronise” button.
Flagged (documentation gaps, not guessed)
Section titled “Flagged (documentation gaps, not guessed)”| 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). |
Test the HTTP layer without the provider
Section titled “Test the HTTP layer without the provider”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.
Template overrides
Section titled “Template overrides”Admin templates live in design/backend/templates/addons/kv_tpay/. Do not edit them in a store; they are replaced on update.