Developer guide
File map
Section titled “File map”app/addons/kv_paytr_payments/├── addon.xml scheme 3.0, settings, 5 tables, install/uninstall functions├── init.php hooks: prepare_checkout_payment_methods, change_order_status_post, delete_order,│ update_company, delete_company; license client boot├── func.php install/uninstall, payment start, payment_notification handler, hook handlers,│ settings info handlers, template helpers├── payments/│ ├── kv_paytr_iframe.php processor script "PayTR iFrame"│ └── kv_paytr_direct.php processor script "PayTR Direct API (card form)"├── controllers/frontend/kv_paytr_payments.php pay (iFrame page), card (Direct API form), installments (AJAX, BIN)├── controllers/backend/kv_paytr_payments.php refund, links, status check, transfers, rates refresh, manage (log), process_transfers (cron)├── controllers/backend/kv_paytr_payments_license.php "Check now" / cron license check├── schemas/block_manager/block_cache_properties.post.php license state in the block cache key├── schemas/menu/menu.post.php Orders → PayTR transactions├── schemas/permissions/admin.post.php└── Tygh/Addons/KvPaytrPayments/ PSR-0 classes ├── Signer.php every PayTR signature (pure PHP) ├── Payload.php amounts, currencies, field limits, basket, merchant_oid, IBAN (pure PHP) ├── InstallmentTable.php rates normalization and plan calculation (pure PHP) ├── ProcessorConfig.php validated processor_params of a payment method ├── ApiClient.php PayTR HTTP calls (Tygh\Http), endpoint override for development ├── Repository.php db_* access to the add-on tables, atomic "claim" updates ├── Gateway.php payment attempts, notifications, customer return, status mapping ├── RefundService.php refunds ├── LinkService.php pay-by-link (Link API) and its notification ├── InstallmentService.php rates cache (storage_data, 24 h) ├── TransferService.php Multi-Vendor platform transfers ├── License/Client.php license check client (generated from the shared Kavio Labs template) └── Upgrade/Synchronizer.php self-healing in-place upgradesdesign/backend/templates/addons/kv_paytr_payments/├── views/payments/components/cc_processors/kv_paytr_{iframe,direct}.tpl → components/processor_settings.tpl├── hooks/orders/payment_info.post.tpl PayTR panel (inside the order form: summary and buttons)├── hooks/orders/tabs_extra.post.tpl refund / link / transfer dialogs (outside the order form)├── hooks/companies/detailed_content.post.tpl vendor bank account (Add-ons tab)├── settings/{setup_info,marketplace_info}.tpl├── license/info.tpl License tab└── views/kv_paytr_payments/manage.tpl transactions and transfers logvar/themes_repository/responsive/templates/addons/kv_paytr_payments/├── views/kv_paytr_payments/{pay,card}.tpl├── views/orders/components/payments/kv_paytr.tpl checkout hint (processor template)├── components/installment_table.tpl└── hooks/products/product_detail_bottom.post.tpl, hooks/index/{styles,scripts}.post.tpljs/addons/kv_paytr_payments/func.js Direct API form: BIN lookup, plan selection, validationNo core files are modified.
Processors
Section titled “Processors”Registered on install (fn_kv_paytr_payments_install(), idempotent) in cscart_payment_processors with addon = 'kv_paytr_payments', callback = 'N', type = 'P', the same pattern as the bundled pay360 add-on. Uninstall deletes them and disables the payment methods that used them.
Payment flow
Section titled “Payment flow”fn_start_payment()includes the processor script, which callsfn_kv_paytr_payments_start_payment().Gateway::startIframe()builds the request (amount in the payment currency × 100, base64 JSON basket, customer data), stores apendingrow inkv_paytr_transactionsand callsPOST /odeme/api/get-token. The customer is redirected tokv_paytr_payments.pay, which embedshttps://www.paytr.com/odeme/guvenli/<token>.Gateway::startDirect()signs the hidden fields of the card form instead (single payment);Gateway::signDirectPlan()re-signs them for the plan picked by the customer (installment count and card program are part of the token). Payment page access (kv_paytr_payments.pay|cardand theinstallmentsAJAX call): the session that placed the order (the attempt is recorded in the session), the logged-in owner of the order, or thekeyparameter of the checkout redirect. The key isSigner::pageKey(), an HMAC of the order, the attempt and the session ID with the method’s credentials, so it still works when a concurrent request of the same visitor overwrote the session data written at checkout (guests had no other way back to the page), but is useless in any other session and for any other order. A valid key restores the session record and the guest’s order access (auth.order_ids). The card form fields are cached in the session and rebuilt from the order and the stored attempt (Gateway::rebuildDirectFields(), same amount and currency) when missing.- PayTR posts the result to
payment_notification.notify?payment=kv_paytr_iframe(payment_notificationis a trusted controller: no CSRF check, no regional redirect).Gateway::handleNotification():- looks up the attempt by
merchant_oidand loads the credentials of its payment method (vendor-owned methods work too); - verifies
hashwithhash_equals; - claims the attempt with
UPDATE … SET finalized_at = NOW WHERE merchant_oid = ? AND finalized_at = 0. Only the first notification gets a row back; repeats are answeredOKwithout side effects; - success: payment info + status change only from Incomplete, Awaiting confirmation or Failed; failure: status change only if no attempt was paid and no newer attempt is pending;
- deletes the core “payment started” marker (
order_datatypeS); - output buffering is active while the order is processed, so notices or e-mail rendering output can never corrupt the
OKanswer.
- looks up the attempt by
- The customer returns to
payment_notification.return|fail. If the page loads inside the PayTR iframe (Sec-Fetch-Dest: iframe), a small page moves it to the top window first.Gateway::handleReturn()polls the attempt for a few seconds, then callsPOST /odeme/durum-sorgu; a successful inquiry is applied with the same claim. Otherwise the order gets the pending status. Finallyfn_order_placement_routines('route').
Signatures
Section titled “Signatures”All are base64(HMAC-SHA256(fields . merchant_salt, merchant_key)); see Signer.php.
| Call | Concatenated fields |
|---|---|
| iFrame get-token | merchant_id, user_ip, merchant_oid, email, payment_amount, user_basket, no_installment, max_installment, currency, test_mode |
| Direct API | merchant_id, user_ip, merchant_oid, email, payment_amount, payment_type, installment_count, currency, test_mode, non_3d |
| Notification (iFrame/Direct) | merchant_oid, salt, status, total_amount |
| Link notification | callback_id, merchant_oid, salt, status, total_amount |
| Refund | merchant_id, merchant_oid, return_amount |
| Status inquiry | merchant_id, merchant_oid |
| Installment rates | merchant_id, request_id |
| BIN lookup | bin_number, merchant_id |
| Link create | name, price, currency, max_installment, link_type, lang, then min_count (product) or email (collection) |
| Link delete / send | id, merchant_id[, cell_phone or email] |
| Platform transfer | merchant_id, merchant_oid, trans_id, submerchant_amount, total_amount, transfer_name, transfer_iban |
| Transfer result notification | trans_ids (JSON, backslashes removed), salt |
Hooks used
Section titled “Hooks used”| Type | Hook | Purpose |
|---|---|---|
| PHP | prepare_checkout_payment_methods |
Hides the PayTR payment methods at checkout without a valid license |
| PHP | before_dispatch (admin area and console only; static handler License\Client::onBeforeDispatch) |
Runs the due license check and shows license notices |
| PHP | change_order_status_post |
Queue a marketplace transfer at the configured vendor order status (MV) |
| Schema | block_manager/block_cache_properties (callable_handlers) |
License state in every block cache key (installment table) |
| PHP | delete_order |
Delete the add-on rows of the order |
| PHP | update_company, delete_company |
Save / delete the vendor bank account (MV) |
| Template (admin) | orders:payment_info (post), orders:tabs_extra (post) |
PayTR panel and dialogs |
| Template (admin) | companies:detailed_content (post) |
Vendor bank account fields |
| Template (storefront) | products:product_detail_bottom (post) |
Installment table |
| Template (storefront) | index:styles, index:scripts (post) |
LESS and JS |
All hooks exist in CS-Cart 4.17.2 (Ultimate and Multi-Vendor), Multi-Vendor 4.18.3 and Multi-Vendor 4.21.2.
Template helpers
Section titled “Template helpers”Callable as Smarty modifiers: fn_kv_paytr_payments_get_installment_plans($price), fn_kv_paytr_payments_get_max_installment($payment_id), fn_kv_paytr_payments_get_order_panel($order_id), fn_kv_paytr_payments_format_amount($amount, $currency).
To show the installment table elsewhere (for example in a custom product template):
{include file="addons/kv_paytr_payments/components/installment_table.tpl" kv_price=$product.price}License check
Section titled “License check”Tygh\Addons\KvPaytrPayments\License\Client verifies the license against the Kavio Labs license server.
fn_kv_paytr_payments_is_licensed()is the gate. When it returnsfalse,fn_kv_paytr_payments_prepare_checkout_payment_methods()removes the PayTR iFrame and Direct API methods from the checkout list, sofn_kv_paytr_payments_get_installment_plans()returns no plans and the product page hook prints nothing. It reads the stored state only (one storage read per request) and never contacts the server.- Nothing that completes an already started payment depends on the license: the processor scripts,
kv_paytr_payments.pay|card|installments,payment_notification.notify|return|fail, refunds, status inquiries, pay-by-link, platform transfers and the transfer cron work for existing orders whatever the license state. - The installment table is printed inside the product page’s main block, which CS-Cart may cache. The license state is part of every block cache key (
block_cache_properties.post.php,callable_handlers), so the table appears or disappears at once. Client::boot()(init.php) registers abefore_dispatchhandler for the admin area and the console only. On admin page views and console requests it runs the check when it is due (key changed, or 24 hours after the last check; 1 hour after a failure) and shows the admin notice once per session.- Admin license UI (generated): the License card at the top of the add-on page (
license/card.tplthrough theaddons:tabs_contentpre hook), the badge in Add-ons > Manage (license/badge.tpl,addons:managepost hook), the status bar (license/status_bar.tpl, first line inside the mainbox capture ofviews/kv_paytr_payments/manage.tpl; nothing for vendors) andcss/addons/kv_paytr_payments/license.less(index:stylespost hook). The off-effect sentence is the language variablekv_paytr_payments.license_off_effect. - Request:
POST https://cs-cart.kaviolabs.com/index.php?dispatch=kv_licenses.checkwith JSON{"v":1, "license", "addon", "version", "hosts":[…], "platform":{"version", "edition"}, "nonce"}, sent throughTygh\Http(3 s connect, 5 s total; kept out of the CS-Cart request log). The answer is accepted only when its Ed25519 signature matches a public key embedded in the client and it echoes the nonce. - State: storage key
kv_license_kv_paytr_payments(?:storage_data), orvar/kv_storage/kv_paytr_payments/license.jsonwhile the full page cache is active, protected by an HMAC with the store’s crypt key. - Cron:
php admin.php --dispatch=kv_paytr_payments_license.check. - Development stores only (
http_hostandhttps_hostare development hosts): the constantsKV_LICENSE_SERVER_URL,KV_LICENSE_TEST_HOST(act as if the development storefronts ran on that host) andKV_LICENSE_TEST_TIME_OFFSET(seconds). They are ignored on production hosts.
php /path/to/store/admin.php --dispatch=kv_paytr_payments.process_transfersSends due vendor transfers in automatic mode and prints PayTR transfers sent: N, failed: M.
Development and tests
Section titled “Development and tests”Nothing in tests/ is packaged.
-
tests/paytr_signer_test.php: standalone checks of every signature and payload rule. Vectors marked “PayTR tool” were produced by PayTR’s public hash calculator (dev.paytr.com > Servis Test Araçları > Hash Hesaplama) from the samples on that page.php81 tests/paytr_signer_test.php -
tests/mock-paytr/server.js: a local mock of the PayTR endpoints (get-token, hosted payment page, Direct API with a 3D Secure step, refund, status inquiry, installment rates, BIN lookup, Link API, platform transfer) that validates every token and sends signed notifications to the store. Node 18+, no dependencies.node tests/mock-paytr/server.js --port 8790 --merchant-id 123456 --key TESTKEYTESTKEY --salt TESTSALTTESTSALTPoint a development store at it in
config.local.php(orlocal_conf.php):define('KV_PAYTR_API_BASE_URL', 'http://localhost:8790');All PayTR URLs (API calls, iFrame page, resizer script, Direct API form action) then use the mock, and the admin panel shows a warning. Never define this constant on a live store.
-
tests/e2e/checkout-e2e.mjs(CS-Cart and Multi-Vendor) andtests/e2e/marketplace-e2e.mjs(Multi-Vendor) drive a local store over HTTP against the mock: checkout success, failure, late notification, status inquiry, forged and repeated notifications, refunds, Direct API with installments, pay-by-link, installment table, vendor IBAN, transfers, cron and vendor panel.node tests/e2e/checkout-e2e.mjs --store http://localhost:8171 --db cscart_cs417 --product 12node tests/e2e/checkout-e2e.mjs --store http://localhost:8172 --db cscart_mv417 --product 145node tests/e2e/marketplace-e2e.mjs --store http://localhost:8172 --db cscart_mv417
Third-party code
Section titled “Third-party code”Tygh/Addons/KvPaytrPayments/License/Ed25519.php (checks the signature of the license server’s answers on servers whose PHP has no sodium extension) is derived from paragonie/sodium_compat, ISC License, Copyright (c) 2016-2023 Paragon Initiative Enterprises and Copyright (c) 2013-2019 Frank Denis. The full license notice is in the file header.