{"id":6653,"date":"2026-05-09T15:12:16","date_gmt":"2026-05-09T15:12:16","guid":{"rendered":"https:\/\/ecommerce.holest.com\/?p=6653"},"modified":"2026-05-09T15:40:28","modified_gmt":"2026-05-09T15:40:28","slug":"holestpay-frontcore-samo-javascript-vodic-za-integraciju-placanja-za-ai-asistente","status":"publish","type":"post","link":"https:\/\/ecommerce.holest.com\/en\/holestpay-frontcore-samo-javascript-vodic-za-integraciju-placanja-za-ai-asistente\/","title":{"rendered":"HolestPay FrontCore (JavaScript-Only) Payment Integration Guide for AI Coding Assistants"},"content":{"rendered":"<p>Tell your AI coding assistant (such as Cursor, for example) that you want to integrate HolestPay for payments, fiscalization, and shipping, and provide it with this MD markup: https:\/\/apps.holest.com\/holest-pay\/AI_INTEGRATION_GUIDE_FRONTCORE.md.txt<br \/>\nYour AI assistant will then know exactly how to guide you through the process. <\/p>\n<p>Make sure to SET UP FIRST ALL methods you plan to use on https:\/\/sandbox.pay.holest.com\/ (payment | fiscal | shipping) and test them using &#8220;pay-by-link&#8221; (entirely off-site). If everything is configured properly, the integration will go smoothly with just a few queries.<\/p>\n<p><a href=\"https:\/\/apps.holest.com\/holest-pay\/AI_INTEGRATION_GUIDE_FRONTCORE.md.txt\" target=\"_blank\" rel=\"noopener noreferrer\" data-doc-type=\"frontcore\" data-integration-mode=\"javascript-only\" data-format=\"markdown-text\">HolestPay FrontCore (JavaScript-Only) Payment Integration Guide for AI Coding Assistants &#8211; MD file&#8230;<\/a><br \/>\n<\/p>\n<pre><code>\r\n# HolestPay Integration Guide \u2014 FrontCore\r\n\r\n> This guide is written for AI coding assistants. It describes how to implement HolestPay payment integration using the **FrontCore approach** \u2014 a JavaScript-only integration that does **not require a server-side signature** for the initial payment request.  \r\n> Reference implementation: `hpay_frontcore_sample.html`\r\n>\r\n> **Sample data files \u2014 read these to understand real data structures:**\r\n> - `pos_as_read.json` \u2014 https:\/\/apps.holest.com\/holest-pay\/pos_as_read.json  \r\n>   Full `client.POS` (same as HPay.POS) object as returned by `HPayInit()`. Contains `payment`, `shipping`, and `fiscal` method arrays with all their properties (`Uid`, `HPaySiteMethodId`, `Name`, `Hidden`, `SubsciptionsType`, `POps`, `PayInputUrl`, `Use IFRAME`, etc.).\r\n> - `response_sample.json` \u2014 https:\/\/apps.holest.com\/holest-pay\/response_sample.json  \r\n>   Full `hpay_response` object as received in `onHPayResult` event and as POST-back `hpay_forwarded_payment_response`. Contains `payment_status`, `status`, `transaction_uid`, `transaction_user_info`, `vault_token_uid`, `vault_card_brand`, `vault_card_umask`, `vault_exp`, `payment_html`, `fiscal_html`, `integr_html`, `shipping_html`, and all other result fields.\r\n\r\n---\r\n\r\n## What is FrontCore?\r\n\r\nFrontCore is a HolestPay integration mode where:\r\n\r\n- The HPay script is **automatically loaded** from the payment server when the buyer's browser visits a **whitelisted origin** (domain).\r\n- **No Secret Key is needed on the frontend** \u2014 the payment request is signed internally by the HPay infrastructure based on the trusted origin.\r\n- The developer only needs the **Merchant Site UID** in the browser.\r\n- The **Secret Key is still required on your server** for backend charges (COF\/MIT) , admin operations, and result signature verification.\r\n\r\n### Recommendation\r\n\r\nUse FrontCore selectively.\r\n\r\n- For most production implementations, **Standard integration** should be the primary\/default choice.\r\n- FrontCore is best when you need to connect a site quickly and start payment flow fast.\r\n- Even with FrontCore, production-grade backend verification, webhook idempotency, and secure server ownership are still required.\r\n\r\n---\r\n\r\n## Prerequisites\r\n\r\n1. A configured HolestPay POS on `pay.holest.com` (production) or `sandbox.pay.holest.com` (sandbox).\r\n2. All desired payment methods, fiscal methods, and shipping methods activated on the POS in the HPay panel.\r\n3. In the HPay panel ? site\/POS settings:\r\n   - **Merchant Site UID** (`merchant_site_uid`) \u2014 **not** required as an input parameter to `HPayInit()` for FrontCore. It is automatically available as `client.MerchantsiteUid` after `HPayInit()` resolves, and must be saved for use in backend charge requests and admin operations.\r\n   - **POS Secret Key** \u2014 needed only on your **server** (for charges, admin ops, result verification).\r\n   - **Frontend Script-Core Origins** \u2014 add your site's domain (e.g. `yoursite.com` or `*.yoursite.com`) to the whitelist. **Without this, the FrontCore script will not load.**\r\n\r\n---\r\n\r\n## Pre-Implementation Client Questions (Ask Before Building)\r\n\r\nBefore writing integration code, confirm the following with the client:\r\n\r\n- Do you want us to implement the bank-required footer logotypes strip (card logos, bank logos, 3DS logos) using HolestPay POS parameters, visible in the site footer on **all pages** (not only checkout)?\r\n- For Terms of Service, do you want to use the HolestPay-provided TOS page directly, compare\/merge its clauses into your existing TOS page, or handle TOS in another way?\r\n- Do you require an `I accept Terms of Service` checkbox on checkout with a clickable Terms link (page link or modal)?\r\n\r\n---\r\n\r\n## Recommended Quick-Start from HPay Panel\r\n\r\nBefore implementing from scratch, open HPay panel:\r\n\r\n- `PLATFORM MODULES` -> at the bottom use:\r\n  - `Get HTML with embeded POS (selected POS) credentials (production requires server-side sigining)...`\r\n  - `Get HPay-FrontCore HTML with embeded POS (selected POS) credentials (signing automatic, javascript-only implementable) ...`\r\n- Download generated sample files and deploy them to an HTTPS test location.\r\n- For FrontCore, explicitly add that HTTPS test location to `Frontend Script-Core Origins`; otherwise the FrontCore script will not load there.\r\n- Use these files for immediate end-to-end checks (form rendering, payment flow, event payloads, response format, order fields).\r\n- AI assistants and developers can inspect these generated samples during development to discover integration details that may not be fully documented.\r\n\r\nFor this FrontCore guide, start from the FrontCore generated sample (based on `hpay_frontcore_sample.html`) and validate script loading + origin whitelist behavior first.\r\n\r\n---\r\n\r\n## Platform Clarifications (Important)\r\n\r\n- In HolestPay terminology, a `POS` means your website\/app sales endpoint (web, Android, iOS, desktop), not a physical in-store terminal.\r\n- `sandbox` and `production` are intentionally isolated environments. Configure POS, methods, and credentials separately in each environment.\r\n- For status processing, treat HolestPay `status` format as canonical for order lifecycle across panel\/API\/webhooks:\r\n  - `PAYMENT:<payment_status>`\r\n  - optional fiscal\/integration segments: `<module_uid>_FISCAL:<status>` or `<module_uid>_INTEGR:<status>`\r\n  - optional shipping segments: `<module_uid>_SHIPPING:<packet_no>@<shipping_status>`\r\n- Keep section order in composed status as: `PAYMENT` -> `FISCAL\/INTEGRATION` -> `SHIPPING`.\r\n- Handle additional payment statuses beyond only paid\/failed flows, especially `AWAITING`, `PAYING`, `RESERVED`, and `OBLIGATED`, depending on your business process.\r\n\r\n---\r\n\r\n## HolestPay Order Status Format\r\n\r\n```shell\r\n[PAYMENT:payment_status][ (fmethod1_uid)_FISCAL:(fmethod1_status) [(fmethod2_uid)_FISCAL:(fmethod2_status)]...][ (imethod1_uid)_INTEGR:(imethod1_status) [(imethod2_uid)_INTEGR:(imethod2_status)]...][ (smethod1_uid)_SHIPPING:packet_no@shipping_status [(smethod2_uid)_SHIPPING:packet_no@shipping_status]...]\r\n```\r\n\r\n- ORDER OF SUB-STATUSES SECTIONS PAYMENT -> FISCAL & INTEGRATION -> SHIPPING IS IMPORTANT.\r\n- ORDER OF METHOD STATUSES WITHIN SAME SUB-STATUSES SECTION IS NOT IMPORTANT.\r\n- ONE AND ONLY ONE SPACE CHARACTER AS SUB-STATUSES SEPARATOR IS IMPORTANT.\r\n\r\n```shell\r\nPossible payment status:\r\n    SUCCESS (alias of PAID)\r\n    PAID\r\n    PAYING (partially paid, indicates all partial payments are on time; used for advance payments or multi-source payments)\r\n    AWAITING (waiting bank transfer, for example)\r\n    REFUNDED\r\n    PARTIALLY-REFUNDED\r\n    VOID\r\n    OVERDUE\r\n    RESERVED (amount is reserved but still not captured from buyer card)\r\n    EXPIRED (used with methods that have expiration)\r\n    OBLIGATED (same as AWAITING but when service delivery has started or there is legal means to guarantee payment will happen)\r\n    REFUSED\r\n    FAILED\r\n    CANCELED\r\n```\r\n\r\n`PAYMENT:payment_status` may not exist if HolestPay payment module is not used and you do not set it explicitly.\r\n\r\n```shell\r\nPossible fiscal module status:\r\n  - varies depending on module\r\n```\r\n\r\nFiscal\/Integration statuses exist only if fiscal\/integration modules add status and are executed.\r\n\r\n```shell\r\nPossible packet shipping status:\r\n    PREPARING - initial status if shipping address is OK; instructions can be submitted to courier from this status\r\n    READY - used by some companies to indicate goods are checked and ready for courier submission\r\n    SUBMITTED - request submitted to courier\r\n    DELIVERY - under delivery\r\n    DELIVERED - delivered\r\n    ERROR - error in courier API request\r\n    RESOLVING - shipping address (or something else) needs backend attention\r\n    FAILED - delivery permanently failed, or courier API refused the request\r\n    REVOKED - explicitly canceled by buyer or company\r\n```\r\n\r\nShipping statuses exist only when packets are handled by HolestPay shipping modules.\r\n\r\n---\r\n\r\n## How It Works \u2014 Overview\r\n\r\n```\r\nHPay Server                      Browser (your site)            Your Server\r\n     |                                   |                            |\r\n     |-- auto-serve hpay.frontcore.js -->|                            |\r\n     |   (only for whitelisted origins)  |                            |\r\n     |                                   |                            |\r\n     |<-- HPayInit() --------------------|                            |\r\n     |<-- presentHPayPayForm(request) ---|                            |\r\n     |   (no verificationhash needed)    |                            |\r\n     |                                   |                            |\r\n     |-- onHPayResult ------------------>|                            |\r\n     |                                   |-- verify & fulfil -------->|\r\n     |                                   |                            |\r\n     | (server-to-server webhook) -------|----------> notify_url ---->|\r\n```\r\n\r\nThe key difference from Standard: **no `verificationhash` field is needed** in the initial pay_request because the trusted origin acts as the authorization.\r\n\r\n---\r\n\r\n## Step 0 \u2014 Configure Origins and Get the FrontCore Script URL\r\n\r\n### 1. Whitelist your domain\/origin patterns\r\n\r\nIn the HPay panel, under your POS\/site settings, add allowed entries to **\"Frontend Script-Core Origins\"**.\r\n\r\n- Enter **one origin\/pattern per line**.\r\n- `*` wildcard is supported.\r\n- Example pattern: `*holest.com\/all-fontcore-tests\/*`  \r\n  This allows matching subdomains and subfolders that satisfy the pattern.\r\n\r\n### 2. Copy the FrontCore script tag from HPay panel\r\n\r\nAfter saving origins, the HPay panel outputs a **full `<script>` tag** that can be copied\/pasted directly below the FrontCore Origins field.\r\n\r\nFrontCore script URL format:\r\n\r\n```\r\nhttps:\/\/sandbox.pay.holest.com\/clientpay\/handlers\/pos\/{merchant_site_uid}\/frontend-script-core.js\r\n```\r\n\r\nProduction format:\r\n\r\n```\r\nhttps:\/\/pay.holest.com\/clientpay\/handlers\/pos\/{merchant_site_uid}\/frontend-script-core.js\r\n```\r\n\r\nExample (sandbox):\r\n\r\n```html\r\n<script src=\"https:\/\/sandbox.pay.holest.com\/clientpay\/handlers\/pos\/07f689c5-5bc8-44b6-a563-8facc6870fab\/frontend-script-core.js\"><\/script>\r\n```\r\n\r\nIn this URL, the `07f689c5-5bc8-44b6-a563-8facc6870fab` segment is the **Merchant Site UID**.\r\n\r\nWhen this FrontCore script loads on an allowed origin, `HolestPayCheckout` and FrontCore signing (`hpay_frontend_script_core_sign`) become available.\r\n\r\nAfter loading, the global `HolestPayCheckout` object and `HPayInit()` become available.\r\n\r\nWhen the HPay script is loaded, it also exposes these globals on `window`:\r\n- `window.presentHPayPayForm` \u2014 function\r\n- `window.HPayIsSandbox` \u2014 environment flag variable\r\n\r\nAdd a check to warn about misconfiguration (add after page loads):\r\n\r\n```javascript\r\nsetTimeout(function() {\r\n  if (typeof HolestPayCheckout === 'undefined') {\r\n    alert(\r\n      'HOLESTPAY FRONT-CORE SCRIPT IS NOT LOADED. ' +\r\n      'YOU PROBABLY FORGOT TO ADD CURRENT ORIGIN TO ' +\r\n      '\"Frontend Script-Core Origins\" PARAMETER UNDER SITE\/POS SETTINGS!'\r\n    );\r\n  }\r\n}, 5000);\r\n```\r\n\r\n---\r\n\r\n## Step 1 \u2014 Initialize HPay and Fetch POS Configuration\r\n\r\n`HPayInit()` returns a Promise resolving to `client` (= global `HPay`).\r\n\r\n```javascript\r\nHPayInit(\r\n  language   \/\/ string \u2014 e.g. \"en\", \"rs\", \"de\" \u2014 also optional.\r\n             \/\/ If omitted, HPay will use the HTML <html lang=\"...\"> attribute,\r\n             \/\/ or fall back to the fixed language configured in HPay panel POS settings.\r\n             \/\/ NOTE: merchant_site_uid and environment are NOT required for FrontCore \u2014\r\n             \/\/ they are determined automatically by the POS-specific script URL.\r\n).then(async client => {\r\n  \/\/ client.MerchantsiteUid \u2014 the Merchant Site UID read from the loaded POS config\r\n  const merchantSiteUid = client.MerchantsiteUid; \/\/ save for use in charge_request \/ admin ops\r\n  \/\/ client.POS.payment  \u2014 array of payment method objects\r\n  \/\/ client.POS.shipping \u2014 array of shipping method objects\r\n  \/\/ client.POS.fiscal   \u2014 array of fiscal method objects\r\n\r\n  \/\/ Filter by buyer country (optional but recommended):\r\n  let availablePayment = [];\r\n  let availableShipping = [];\r\n  const country = 'RS';\r\n  try {\r\n    availablePayment = await HPay.availablePaymentMethods(country, orderAmount, orderCurrency);\r\n  } catch(e) { console.error(e); }\r\n  try {\r\n    availableShipping = await HPay.availableShippingMethods(country, orderAmount, orderCurrency);\r\n  } catch(e) { console.error(e); }\r\n\r\n  \/\/ Build payment method selector from client.POS.payment:\r\n  client.POS.payment.forEach(pm => {\r\n    if (!pm.Hidden && (!availablePayment.length || availablePayment.find(m => m.Uid == pm.Uid))) {\r\n      \/\/ pm.HPaySiteMethodId \u2014 use as pay_request.payment_method value\r\n      \/\/ pm.Name             \u2014 display name\r\n      \/\/ pm.SubsciptionsType \u2014 contains \"cof\" or \"mit\" if card saving is supported\r\n      \/\/ pm.POps             \u2014 available backend operations e.g. \"charge,refund\"\r\n      \/\/ pm.PayInputUrl      \u2014 set means docking (embedded form) is supported\r\n      \/\/ pm['Use IFRAME']    \u2014 if false, method uses redirect flow\r\n    }\r\n  });\r\n\r\n  \/\/ Build shipping method selector from client.POS.shipping:\r\n  if (client.POS.shipping) {\r\n    client.POS.shipping.forEach(sm => {\r\n      if (!sm.Hidden && (!availableShipping.length || availableShipping.find(m => m.Uid == sm.Uid))) {\r\n        \/\/ sm.HPaySiteMethodId \u2014 use as pay_request.shipping_method value\r\n      }\r\n    });\r\n  }\r\n});\r\n```\r\n\r\n---\r\n\r\n## Step 2 \u2014 Build the `pay_request` Object\r\n\r\n```javascript\r\nconst pay_request = {\r\n  \/\/ NOTE: merchant_site_uid is NOT included in pay_request for FrontCore.\r\n  \/\/ The POS-specific script authenticates the request automatically.\r\n  hpaylang: \"en\",                                \/\/ optional UI language\r\n  order_uid: \"20260315-621417\",                  \/\/ required unique order ID\r\n  order_name: \"#Order 204\",                      \/\/ optional order label\r\n  order_amount: \"15000\",                         \/\/ required\r\n  order_currency: \"RSD\",                         \/\/ required ISO 4217\r\n  payment_method: \"179\",                         \/\/ required pm.HPaySiteMethodId\r\n  shipping_method: \"45\",                         \/\/ optional sm.HPaySiteMethodId\r\n  order_user_url: \"https:\/\/yoursite.com\/thanks\", \/\/ optional redirect\/thank-you URL\r\n  notify_url: \"https:\/\/yoursite.com\/webhook\",    \/\/ optional public webhook URL\r\n  order_data: {                                  \/\/ optional custom payload -> stored as order.Data\r\n    customer_segment: \"B2C\",\r\n    source: \"checkout-web\"\r\n  },\r\n  cof: \"optional\",                               \/\/ optional: optional|required|none\r\n  vault_token_uid: \"saved-token-uuid\",           \/\/ optional: saved token, or 1|true|new\r\n\r\n  \/\/ Optional billing object (template fields):\r\n  order_billing: {\r\n    email: \"customer@example.com\",\r\n    first_name: \"TEST\",\r\n    last_name: \"TEST\",\r\n    phone: \"+38111111111\",\r\n    is_company: 0,\r\n    company: \"\",        \/\/ company legal name\r\n    company_tax_id: \"\", \/\/ company tax ID in merchant's country\r\n    company_reg_id: \"\", \/\/ company registration ID in merchant's country\r\n    address: \"TEST\", \/\/ street name\r\n    address2: \"\",    \/\/ recommended for street number \/ address addition\r\n    city: \"Beograd\",\r\n    country: \"RS\",\r\n    state: \"Beograd\",\r\n    postcode: \"11000\",\r\n    lang: \"sr_RS\" \/\/ language from merchant platform\/system\r\n  },\r\n\r\n  \/\/ Optional shipping object (template fields):\r\n  order_shipping: {\r\n    shippable: false,\r\n    is_cod: 1, \/\/ set to 1 when COD logic is used\/allowed\r\n    first_name: \"\",\r\n    last_name: \"\",\r\n    phone: \"\",\r\n    company: \"\",\r\n    address: \"\", \/\/ street name\r\n    address2: \"\", \/\/ recommended for street number \/ address addition\r\n    city: \"\",\r\n    country: \"\",\r\n    state: \"\",\r\n    postcode: \"\"\r\n  },\r\n\r\n  \/\/ Optional items array (template fields):\r\n  order_items: [\r\n    {\r\n      posuid: 114, \/\/ merchant's own internal item ID (string or number)\r\n      type: \"product\",\r\n      name: \"Sample product name\",\r\n      sku: \"000550\",\r\n      qty: 1,\r\n      price: 4695.99,\r\n      subtotal: 4695.99,\r\n      refunded: 0,\r\n      refunded_qty: 0,\r\n      tax_label: \"\",\r\n      tax_amount: 0,\r\n      length: \"\",\r\n      width: \"\",\r\n      height: \"\",\r\n      weight: \"\",\r\n      split_pay_uid: \"\",\r\n      virtual: true,\r\n      tax_percent: 0\r\n    }\r\n  ]\r\n};\r\n\r\n\/\/ NOTE: For initial FrontCore checkout, do not send signature hash.\r\n\r\n\/\/ Remove empty fields:\r\nObject.keys(pay_request).forEach(k => { if (pay_request[k] === '') delete pay_request[k]; });\r\n```\r\n\r\n**Important \u2014 `order_items` naming must use HolestPay keys (do not pass raw platform keys):**\r\n\r\n- Use `name`, not `title`\r\n- Use `posuid`, not `variantId`\r\n- Use `qty`, not `quantity`\r\n- `subtotal` is mandatory for each item line\r\n- `posuid` can be any identifier from the merchant's system (SKU\/variant\/product\/internal DB ID)\r\n\r\nCommon Shopify mapping before send:\r\n- `variantId -> posuid`\r\n- `title -> name`\r\n- `quantity -> qty`\r\n- top-level `items -> order_items`\r\n\r\nThe same order payload structure is used across all three markups\/docs (Lovable prompt, FrontCore guide, Standard guide).\r\nAddress convention recommendation: use `address` for street name and `address2` for street number\/additional address details.\r\n\r\n### Full Template Field Catalog (Standalone)\r\n\r\n- `Top-level pay_request` (FrontCore \u2014 `merchant_site_uid` is **not** included here):\r\n  - `hpaylang`, `order_uid`, `order_name`, `order_amount`, `order_currency`\r\n  - `payment_method`, `shipping_method`, `order_user_url`, `notify_url`\r\n  - `order_data`, `cof`, `vault_token_uid`\r\n- `order_billing`:\r\n  - `email`, `first_name`, `last_name`, `phone`\r\n  - `is_company`, `company` (legal name), `company_tax_id` (tax ID), `company_reg_id` (registration ID)\r\n  - `address`, `address2`, `city`, `country`, `state`, `postcode`, `lang`\r\n- `order_shipping`:\r\n  - `shippable`, `is_cod`\r\n  - `first_name`, `last_name`, `phone`, `company`\r\n  - `address`, `address2`, `city`, `country`, `state`, `postcode`\r\n  - `dispenser`, `dispenser_desc`, `dispenser_method_id` (locker\/paket-shop flows)\r\n- `order_items[]`:\r\n  - `posuid`, `type`, `name`, `sku`, `qty`, `price`, `subtotal`\r\n  - Required minimum per line for reliable processing: `posuid`, `name`, `qty`, `subtotal`\r\n  - `refunded`, `refunded_qty`, `tax_label`, `tax_amount`\r\n  - `length`, `width`, `height`, `weight`, `split_pay_uid`, `virtual`, `warehouse`\r\n- `setPaymentMethodDock(...)` data:\r\n  - `order_amount`, `order_currency`, `monthly_installments`, `vault_token_uid`, `hpaylang`, `cof`\r\n- Signature input\/result fields used for backend charge and verification:\r\n  - `transaction_uid`, `status`, `order_uid`, `order_amount`, `order_currency`, `vault_token_uid`, `subscription_uid`, `rand`\r\n  - Compare generated signature with response `vhash` (not request `verificationhash`).\r\n- `Extensibility`:\r\n  - `order_data` may contain any custom key\/value pairs; it is persisted to `order.Data` in HPay order.\r\n  - `order_billing` and `order_shipping` may include additional custom fields besides the listed ones.\r\n  - Total serialized order payload should stay below **64 KB**.\r\n\r\n---\r\n\r\n## Step 3 \u2014 Present the Payment Form\r\n\r\n```javascript\r\n\/\/ Option A: Modal\r\nHPay.presentHPayPayForm(pay_request);\r\n\r\n\/\/ Option B: Docked (embedded) \u2014 only if pm.PayInputUrl is set\r\nconst dockElement = document.getElementById('paymentMethodDock');\r\nHPay.setPaymentMethodDock(\r\n  pay_request.payment_method,\r\n  {\r\n    order_amount:         pay_request.order_amount,\r\n    order_currency:       pay_request.order_currency,\r\n    monthly_installments: null,\r\n    vault_token_uid:      pay_request.vault_token_uid || null,\r\n    hpaylang:             pay_request.hpaylang,\r\n    cof:                  pay_request.cof\r\n  },\r\n  dockElement\r\n);\r\n\r\n\/\/ Trigger payment on Pay button click:\r\nHPay.presentHPayPayForm(pay_request);\r\n```\r\n\r\nDock container CSS:\r\n\r\n```css\r\n#paymentMethodDock { background: #ffffff9e; }\r\n```\r\n\r\n---\r\n\r\n## Step 4 \u2014 Handle the Result Events\r\n\r\nUse these event handlers on your FrontCore page:\r\n\r\n```javascript\r\ndocument.addEventListener('onHPayResult', function(e) {\r\n  const r = e.hpay_response;\r\n  if (!r) return;\r\n\r\n  if (r.error && r.error.code) {\r\n    HPay.presentHPayPayForm(pay_request); \/\/ retry\r\n    return;\r\n  }\r\n\r\n  if (\/PAID|RESERVED|SUCCESS|PAYING|OBLIGATED|AWAITING\/i.test(r.payment_status)) {\r\n    \/\/ r.payment_html, r.fiscal_html, r.integr_html, r.shipping_html \u2014 HTML receipts\r\n    \/\/ r.transaction_user_info \u2014 object with card\/transaction details\r\n    \/\/ r.order_user_url \u2014 redirect to thank-you page if needed\r\n    \/\/ IMPORTANT: AWAITING\/OBLIGATED are NOT failed; show r.payment_html on thank-you page\r\n    \/\/ IMPORTANT: clear cart for PAID\/RESERVED and also for PAYING\/AWAITING\/OBLIGATED.\r\n    \/\/ window.location.href = r.order_user_url;\r\n\r\n    if (r.vault_token_uid) {\r\n      \/\/ IMPORTANT: save to your database linked to the user account!\r\n      const saveCardData = {\r\n        vault_token_uid:    r.vault_token_uid,\r\n        vault_card_brand:   r.vault_card_brand,\r\n        vault_card_umask:   r.vault_card_umask,\r\n        vault_exp:          r.vault_exp,\r\n        vault_scope:        r.vault_scope,\r\n        vault_onlyforuser:  r.vault_onlyforuser,\r\n        pay_method_uid:     r.pay_method_uid\r\n      };\r\n      \/\/ TODO: POST saveCardData to your backend and store in DB\r\n    }\r\n  }\r\n  \/\/ r.status \u2014 order status (always present)\r\n});\r\n\r\ndocument.addEventListener('onHPayPanelClose', function(e) {\r\n  const r = e.hpay_response; \/\/ null if closed without completing\r\n  const reason = String((r && r.reason) || '').toLowerCase();\r\n\r\n  \/\/ If pay button was locked during payment start, unlock it on close reasons below.\r\n  if (\/^(user|timeout|cancel|error)$\/.test(reason)) {\r\n    const payBtn = document.getElementById('do-pay');\r\n    if (payBtn) payBtn.disabled = false;\r\n  }\r\n});\r\n\r\ndocument.addEventListener('onHPayOrderOpExecuted', function(e) {\r\n  \/\/ admin operation result\r\n});\r\n```\r\n\r\n### Thank-You Page Rendering Rule (Mandatory)\r\n\r\nOn the thank-you page, always render:\r\n1) `transaction_user_info` block first,  \r\n2) `payment_html`, `fiscal_html`, `shipping_html`, `integr_html` blocks.\r\n\r\nBank production approval also requires a complete order summary on this page (for all outcomes: success, failed, awaiting payment):\r\n- buyer\/customer identity data\r\n- billing and email data\r\n- shipping data\r\n- order number\r\n- ordered products list with quantity, unit price, and line totals\r\n- shipping cost\r\n- payment method name and shipping method name\r\n- grand total amount\r\n\r\nIf any field is missing in response, keep the block visible and show a placeholder message.\r\n\r\nKeys in `transaction_user_info` can be translated for UI labels, but values should be shown exactly as received.\r\n\r\n```javascript\r\nfunction renderTransactionUserInfo(info, keyMap) {\r\n  const host = document.getElementById('hpay-transaction-user-info');\r\n  if (!host) return;\r\n  host.innerHTML = '';\r\n  if (!info || typeof info !== 'object' || !Object.keys(info).length) {\r\n    host.innerHTML = '<p class=\"hpay-placeholder\">Transaction details are not available.<\/p>';\r\n    return;\r\n  }\r\n  const dl = document.createElement('dl');\r\n  Object.entries(info).forEach(([k, v]) => {\r\n    const dt = document.createElement('dt');\r\n    const dd = document.createElement('dd');\r\n    dt.textContent = keyMap[k] || k;  \/\/ translate keys only\r\n    dd.textContent = String(v ?? ''); \/\/ do not alter values\r\n    dl.appendChild(dt);\r\n    dl.appendChild(dd);\r\n  });\r\n  host.appendChild(dl);\r\n}\r\n\r\nrenderTransactionUserInfo(r.transaction_user_info, {\r\n  'Order UID': 'Order UID',\r\n  'Payment Status': 'Payment Status',\r\n  'Transaction Time': 'Transaction Time',\r\n  'Amount in order currency': 'Amount in order currency',\r\n  'Amount in payment currency': 'Amount in payment currency',\r\n  'Bank Account': 'Bank Account',\r\n  'REF MOD97 PNB': 'REF MOD97 PNB',\r\n  'Purphose': 'Purpose'\r\n});\r\n\r\nconst FALLBACK_HTML = '<p class=\"hpay-placeholder\">Not available for this payment.<\/p>';\r\ndocument.getElementById('hpay-receipt-payment').innerHTML  = r.payment_html  || FALLBACK_HTML;\r\ndocument.getElementById('hpay-receipt-fiscal').innerHTML   = r.fiscal_html   || FALLBACK_HTML;\r\ndocument.getElementById('hpay-receipt-shipping').innerHTML = r.shipping_html || FALLBACK_HTML;\r\ndocument.getElementById('hpay-receipt-integr').innerHTML   = r.integr_html   || FALLBACK_HTML;\r\n```\r\n\r\nExample payload:\r\n\r\n```json\r\n{\r\n  \"transaction_user_info\": {\r\n    \"Order UID\": \"NIPI-1777290504591-8X6YD2\",\r\n    \"Payment Status\": \"AWAITING\",\r\n    \"Transaction Time\": \"2026-04-27 13:48:27.847Z\",\r\n    \"Amount in order currency\": \"1360.00 RSD\",\r\n    \"Amount in payment currency\": \"1360.00 RSD\",\r\n    \"Bank Account\": \"160-6000002552312-02\",\r\n    \"REF MOD97 PNB\": \"(97) 2726042713\",\r\n    \"Purphose\": \"Order npinipi17772905045918x6yd2\"\r\n  }\r\n}\r\n```\r\n\r\n---\r\n\r\n## Result Verification \u2014 Webhook (Notify URL)\r\n\r\nHPay calls webhook URL via HTTP POST JSON (server-to-server).\r\nIf `notify_url` is sent in request payload, it overrides panel setting:\r\n- `I(P|S|F|I)N - Link za instant notifikacije - pla\u0107anje\/isporuka\/fiskal\/integracije`\r\n- `I(P|S|F|I)N - Instant payment\/shipping\/fiscal\/integation notification url`\r\n\r\nHPay appends query string parameter `topic` and sends POST JSON.\r\n\r\nSupported `topic` values:\r\n- `payresult`: same payload shape as `onHPayResult` (`e.hpay_response`) \/ `https:\/\/apps.holest.com\/holest-pay\/response_sample.json`.\r\n- `orderupdate`: root contains at least `order_uid`, `status`, `vhash`; payload fields are the same as `payresult`; and includes `order` object in the same format as `ord` from `HPay.getOrder(order_uid).then(ord => {...})` (`https:\/\/apps.holest.com\/holest-pay\/hpay_order_sample.json`) (`\"id\"` is ID from HPay system); verify `vhash` the same way.\r\n- `posconfig-updated`: contains POS config (`HPay.POS` compatible + non-public fields), `environment`, and `checkstr = md5(${merchant_site_uid}${secret_token})`.\r\n\r\nImportant payload mapping note:\r\n- Request and response payloads are similar in shape; response usually contains request fields plus normalization and additional result fields.\r\n- Hash field names are different by direction: request -> `verificationhash`, response -> `vhash`.\r\n- `order` payload is a different model (same as `HPay.getOrder(...)` `ord` object), so do not map it as request\/response.\r\n- Example key differences:\r\n  - request\/response `order_uid`, `order_amount`, `order_currency` -> order object `Uid`, `Amount`, `Currency`\r\n  - request\/response `order_items`, `order_billing`, `order_shipping` -> order object `Data.items`, `Data.billing`, `Data.shipping`\r\n  - fiscal method specific data -> `order.FiscalData[fiscal_method_uid] = {...}` (module-specific payload)\r\n  - integration method data -> `order.Data[integr_method_uid] = {...}` (may exist or may be empty\/missing)\r\n  - shipping method data -> `order.ShippingData[real_or_temp_shipping_code] = { method_uid: shipping_method_uid, ... }`\r\n\r\nThe same payment result may arrive from browser callback and webhook. Use `vhash` (with order\/transaction IDs) for idempotency to avoid duplicate processing.\r\n\r\n### Redirect Method POST-back Note (Bank Redirect Flows)\r\n\r\nFor redirect methods, HPay may return result to `order_user_url` with auto-submitted form:\r\n\r\n```html\r\n<form method='POST' id='frmRedirect' action='${order_user_url}'>\r\n  <input type='hidden' name='hpay_forwarded_payment_response' id='hpay_response' \/>\r\n<\/form>\r\n<script nonce='${csp_nonce}'>\r\n  document.getElementById('hpay_response').value = JSON.stringify(hpay_result);\r\n  document.getElementById('frmRedirect').submit();\r\n<\/script>\r\n```\r\n\r\nRobust parse fallback example:\r\n\r\n```javascript\r\nfunction parseForwardedHPayResponse(raw) {\r\n  if (!raw) return null;\r\n  if (typeof raw === 'object') return raw;\r\n  try { return JSON.parse(raw); } catch (_) {}\r\n  const unescaped = String(raw).replace(\/\\\\\\\\\\\"\/g, '\"').replace(\/\\\\\\\\\\\\\\\\\/g, '\\\\');\r\n  try { return JSON.parse(unescaped); } catch (_) {}\r\n  return null;\r\n}\r\n```\r\n\r\n## Step 5 \u2014 Verify Response `vhash` on Server (Node.js)\r\n\r\nRule of thumb:\r\n- request to HPay (`pay_request` \/ `charge_request`) -> field `verificationhash` -> generate with `generatePOSRequestSignature(...)`\r\n- response from HPay (browser callback \/ webhook) -> field `vhash` -> validate with `verifyHPayResponse(...)`\r\n\r\n```javascript\r\nconst crypto = require('crypto');\r\nconst md5 = require('md5');\r\n\r\nfunction generatePOSRequestSignature(merchant_site_uid, secretKey, payload) {\r\n  const amount = Number(payload.order_amount ?? 0).toFixed(8);\r\n  const src =\r\n    String(payload.transaction_uid ?? '').trim() + '|' +\r\n    String(payload.status ?? '').trim() + '|' +\r\n    String(payload.order_uid ?? '').trim() + '|' +\r\n    amount + '|' +\r\n    String(payload.order_currency ?? '').trim() + '|' +\r\n    String(payload.vault_token_uid ?? '').trim() + '|' +\r\n    String(payload.subscription_uid ?? '').trim() +\r\n    String(payload.rand ?? '').trim();\r\n\r\n  const srcMd5 = md5(src + merchant_site_uid);\r\n  return crypto.createHash('sha512').update(srcMd5 + secretKey).digest('hex').toLowerCase();\r\n}\r\n\r\nfunction verifyHPayResponse(result, merchant_site_uid, secretKey) {\r\n  if (!result || !result.vhash) return false;\r\n  if (!result.order_uid || !String(result.order_uid).trim()) return false;\r\n\r\n  const expected = generatePOSRequestSignature(merchant_site_uid, secretKey, {\r\n    transaction_uid: result.transaction_uid ?? '',\r\n    status: result.status ?? '',\r\n    order_uid: result.order_uid,\r\n    order_amount: result.order_amount ?? 0,\r\n    order_currency: result.order_currency ?? '',\r\n    vault_token_uid: result.vault_token_uid ?? '',\r\n    subscription_uid: result.subscription_uid ?? '',\r\n    rand: result.rand ?? ''\r\n  });\r\n\r\n  return expected === String(result.vhash).toLowerCase();\r\n}\r\n```\r\n\r\n---\r\n\r\n## Backend Charge (Server Side \u2014 Still Requires Secret Key)\r\n\r\nFrontCore does not expose the Secret Key on the frontend, but backend charges still need it on your server.\r\n\r\n`charge_request` uses the same structure as `pay_request`. The practical difference is that `order_user_url` has no effect for backend charge (no buyer to redirect), so you should omit it. `cof` is also not needed for backend charge requests.\r\n\r\n```javascript\r\n\/\/ Node.js backend\r\nconst crypto = require('crypto');\r\nconst md5 = require('md5');\r\n\r\nfunction generatePOSRequestSignature(merchant_site_uid, secretkey, request) {\r\n  const amt = parseFloat(request.order_amount || 0).toFixed(8);\r\n  let cstr  = String(request.transaction_uid  || '').trim() + '|';\r\n  cstr     += String(request.status           || '').trim() + '|';\r\n  cstr     += String(request.order_uid        || '').trim() + '|';\r\n  cstr     += String(amt).trim() + '|';\r\n  cstr     += String(request.order_currency   || '').trim() + '|';\r\n  cstr     += String(request.vault_token_uid  || '').trim() + '|';\r\n  cstr     += String(request.subscription_uid || '').trim();\r\n  cstr     += String(request.rand             || '').trim();\r\n  const md5hash = md5(cstr + merchant_site_uid);\r\n  return crypto.createHash('sha512').update(md5hash + secretkey).digest('hex').toLowerCase();\r\n}\r\n\r\nconst charge_request = {\r\n  merchant_site_uid: \"YOUR-MERCHANT-SITE-UID\",\r\n  order_uid: \"20260315-999999\",\r\n  order_amount: \"15000\",\r\n  order_currency: \"RSD\",\r\n  payment_method: \"179\",\r\n  vault_token_uid: \"saved-token-uuid\",  \/\/ required\r\n  order_data: { source: \"subscription-renewal\" }, \/\/ optional custom data -> order.Data\r\n  \/\/ same optional fields as pay_request (billing, shipping, items, notify_url, etc.)\r\n  \/\/ omit order_user_url: no user exists to be redirected in backend charge flow\r\n  \/\/ omit cof: not applicable for backend charge\r\n};\r\n\r\ncharge_request.verificationhash = generatePOSRequestSignature(\r\n  charge_request.merchant_site_uid, SECRET_KEY, charge_request\r\n);\r\n\r\nconst baseUrl = 'https:\/\/sandbox.pay.holest.com'; \/\/ or pay.holest.com\r\nconst response = await fetch(`${baseUrl}\/s-blue\/v1\/clientpay\/charge`, {\r\n  method: 'POST',\r\n  headers: { 'Content-Type': 'application\/json' },\r\n  body: JSON.stringify(charge_request)\r\n});\r\nconst result = await response.json();\r\n\r\nif (\/PAID|PAYING|RESERVED\/.test(result.payment_status)) {\r\n  \/\/ success\r\n}\r\n```\r\n\r\nPHP equivalent:\r\n\r\n```php\r\nfunction generatePOSRequestSignature($merchant_site_uid, $secretkey, $request) {\r\n    $amt = number_format((float)($request['order_amount'] ?? 0), 8, '.', '');\r\n    $cstr  = trim($request['transaction_uid'] ?? '') . '|';\r\n    $cstr .= trim($request['status'] ?? '') . '|';\r\n    $cstr .= trim($request['order_uid'] ?? '') . '|';\r\n    $cstr .= trim($amt) . '|';\r\n    $cstr .= trim($request['order_currency'] ?? '') . '|';\r\n    $cstr .= trim($request['vault_token_uid'] ?? '') . '|';\r\n    $cstr .= trim($request['subscription_uid'] ?? '');\r\n    $cstr .= trim($request['rand'] ?? '');\r\n    return hash('sha512', md5($cstr . $merchant_site_uid) . $secretkey);\r\n}\r\n\r\n$charge_request['verificationhash'] = generatePOSRequestSignature(\r\n    $merchant_site_uid, $secret_key, $charge_request\r\n);\r\n\r\n$body = json_encode($charge_request);\r\n$ch = curl_init($base_url . '\/s-blue\/v1\/clientpay\/charge');\r\ncurl_setopt_array($ch, [\r\n    CURLOPT_RETURNTRANSFER => true,\r\n    CURLOPT_POST           => true,\r\n    CURLOPT_POSTFIELDS     => $body,\r\n    CURLOPT_HTTPHEADER     => ['Content-Type: application\/json'],\r\n    \/\/ for simplicity only - do not disable in production:\r\n    CURLOPT_SSL_VERIFYHOST => 0,\r\n    CURLOPT_SSL_VERIFYPEER => false,\r\n]);\r\n$result = json_decode(curl_exec($ch), true);\r\ncurl_close($ch);\r\n```\r\n\r\n---\r\n\r\n## Admin Operations (Requires Secret Key \u2014 Backend or Admin Page Only)\r\n\r\nFor admin pages, use the **normal HPay script URL**, not the FrontCore handler script:\r\n\r\n```html\r\n<!-- Sandbox admin page -->\r\n<script src=\"https:\/\/sandbox.pay.holest.com\/clientpay\/cscripts\/hpay.js\"><\/script>\r\n\r\n<!-- Production admin page -->\r\n<script src=\"https:\/\/pay.holest.com\/clientpay\/cscripts\/hpay.js\"><\/script>\r\n```\r\n\r\nDo not use `...\/frontend-script-core.js` for admin tooling.\r\n\r\n```javascript\r\n\/\/ Use the 4th parameter of HPayInit to enable admin mode\r\nHPayInit(\r\n  merchant_site_uid,\r\n  language,\r\n  environment,\r\n  secret_key   \/\/ 4th param \u2014 enables admin\/backend operations\r\n).then(client => client.loadHPayUI())\r\n .then(() => {\r\n   HPay.getOrder(order_uid).then(ord => {\r\n    \/\/ `ord` format sample: https:\/\/apps.holest.com\/holest-pay\/hpay_order_sample.json\r\n     const toolbox = document.getElementById('admin_toolbox');\r\n     toolbox.innerHTML = '';\r\n\r\n     [HPay.POS.payment, HPay.POS.fiscal, HPay.POS.shipping].forEach(methods => {\r\n       (methods || []).forEach(pm => {\r\n         if (pm.initActions) {\r\n           if (typeof pm.initActions === 'string') eval('pm.initActions = ' + pm.initActions);\r\n           pm.initActions();\r\n         }\r\n         if (pm.orderActions) {\r\n           if (typeof pm.orderActions === 'string') eval('pm.orderActions = ' + pm.orderActions);\r\n           const actions = pm.orderActions(ord);\r\n           if (actions && actions.length) {\r\n             const h6 = document.createElement('h6');\r\n             h6.innerHTML = pm.SystemTitle;\r\n             toolbox.appendChild(h6);\r\n             actions.forEach(action => {\r\n               if (action.Run) {\r\n                 const btn = document.createElement('button');\r\n                 btn.innerHTML = action.Caption;\r\n                 btn.addEventListener('click', e => { e.preventDefault(); action.Run(ord); });\r\n                 toolbox.appendChild(btn);\r\n               } else if (action.actions) {\r\n                 const p = document.createElement('p');\r\n                 p.innerHTML = action.Caption;\r\n                 toolbox.appendChild(p);\r\n                 action.actions.forEach(sub => {\r\n                   const sbtn = document.createElement('button');\r\n                   sbtn.innerHTML = sub.Caption;\r\n                   sbtn.addEventListener('click', e => { e.preventDefault(); sub.Run(ord); });\r\n                   p.appendChild(sbtn);\r\n                 });\r\n               }\r\n             });\r\n           }\r\n         }\r\n       });\r\n     });\r\n   });\r\n });\r\n```\r\n\r\n---\r\n\r\n## Difference Summary: FrontCore vs Standard\r\n\r\n| Feature | FrontCore | Standard |\r\n|---------|-----------|----------|\r\n| Script source | Auto-served from HPay server (whitelisted origin) | Manual `<script>` tag |\r\n| `HPayInit()` signature | `HPayInit(language)` \u2014 no UID or env needed | `HPayInit(merchant_site_uid, language, environment)` |\r\n| `merchant_site_uid` in `pay_request` | **Not required** \u2014 script authenticates automatically | Required |\r\n| `merchant_site_uid` for charges\/admin | Required (read from `client.MerchantsiteUid`) | Required |\r\n| Admin script URL | Use normal `...\/clientpay\/cscripts\/hpay.js` | Use normal `...\/clientpay\/cscripts\/hpay.js` |\r\n| `verificationhash` in pay_request | Not required (FrontCore initial checkout) | Required (server-side) |\r\n| Secret Key on frontend | Never needed | Never needed (generate signature server-side) |\r\n| Secret Key for backend charges | Required (server-side) | Required (server-side) |\r\n| Secret Key for admin ops | Required (server-side or admin page) | Required (server-side or admin page) |\r\n| Origin whitelist required | Yes \u2014 configure in HPay panel | No |\r\n| Result event handling | Identical | Identical |\r\n\r\n---\r\n\r\n## Implementation Precision Addendum\r\n\r\nUse this section as an execution checklist to reduce integration mistakes and speed up debugging.\r\n\r\n### FrontCore Safety Rules (Most Important)\r\n\r\n- FrontCore initial checkout request does not require `verificationhash`; trusted origin performs signing.\r\n- FrontCore script must be loaded from `...\/handlers\/pos\/{merchant_site_uid}\/frontend-script-core.js`.\r\n- Current page origin must match `Frontend Script-Core Origins` rule list; otherwise `HolestPayCheckout` will not initialize.\r\n- Keep one origin\/pattern per line in HPay panel and verify wildcard scope carefully before production.\r\n- Use normal `...\/clientpay\/cscripts\/hpay.js` for admin tooling pages; do not use FrontCore handler script for admin.\r\n\r\n### Request Validation Rules (Before Calling HPay)\r\n\r\n- `order_uid` must be unique per order attempt in your system; do not reuse successful IDs for new purchases.\r\n- `merchant_site_uid` must belong to the same environment where script\/API is used (`sandbox` with sandbox, `production` with production).\r\n- `order_amount` must be stable across checkout form, dock config, and presented request.\r\n- `order_currency` must be a valid ISO 4217 code configured on your POS methods.\r\n- `payment_method` must be `pm.HPaySiteMethodId` (not `pm.Uid`).\r\n- `shipping_method` should be omitted if shipping is not used; send it only when user selected a supported shipping option.\r\n- `notify_url` should be a stable server endpoint; avoid temporary dev tunnels for production behavior testing.\r\n- `order_user_url` should point to your own order\/thank-you page and include enough context to restore order state.\r\n- `order_data` is ideal for stable metadata like source channel, cart fingerprint, CRM ID, campaign tags, or internal correlation keys.\r\n- Remove empty fields before sending; avoid null\/empty noise in logs and state snapshots.\r\n\r\n### Signature and Verification Precision (Server Side)\r\n\r\n- Even with FrontCore, backend charge and result verification still require Secret Key and signature validation.\r\n- Use exactly the documented signature field order and amount formatting (`toFixed(8)` \/ `number_format(..., 8)`).\r\n- Compare signatures using exact lowercase hex strings.\r\n- `verificationhash` is used for requests to HPay; `vhash` is used in responses from HPay.\r\n- Verify signatures in both places: browser event result handling path and webhook path.\r\n- Treat unsigned or signature-invalid payload as non-authoritative and do not fulfill order.\r\n\r\n### Webhook Reliability Pattern\r\n\r\n- Implement webhook idempotency keyed by transaction\/order identifiers from result payload.\r\n- Accept webhook retry behavior; return success response only after durable persistence.\r\n- Process business actions once, even if webhook arrives multiple times or after browser callback.\r\n- Record webhook receive timestamp and raw payload snapshot for auditability.\r\n- Never rely on callback ordering between browser event and webhook; either can arrive first.\r\n- Use webhook as final server authority for fulfillment transitions.\r\n\r\n### Status Handling Strategy\r\n\r\n- Handle `PAID`, `SUCCESS`, `PAYING`, `RESERVED`, `OBLIGATED`, `AWAITING`, `FAILED`, `REFUSED`, `CANCELED`, `VOID`, `REFUNDED`, `PARTIALLY-REFUNDED`, `EXPIRED`, `OVERDUE`.\r\n- Map payment status to business actions explicitly:\r\n- `PAID`\/`SUCCESS`: fulfill when signature is valid on server.\r\n- `PAYING`: partially paid; keep order open according to your policy.\r\n- `RESERVED`: authorization only; wait for capture before final fulfillment.\r\n- `AWAITING`\/`OBLIGATED`: pending\/offline or legally committed flows; these are **not failed** statuses. Do not treat them as card-capture success, but show `payment_html` on thank-you page (often contains account\/invoice payment instructions).\r\n- Cart handling rule: clear cart for `PAID`, `RESERVED`, `PAYING`, `AWAITING`, and `OBLIGATED` (same cart behavior for all these statuses).\r\n- `FAILED`\/`REFUSED`\/`CANCELED`\/`EXPIRED`\/`OVERDUE`: stop fulfillment and show retry\/payment-link path.\r\n- Parse composed HolestPay status (`PAYMENT ... _FISCAL\/_INTEGR ... _SHIPPING ...`) as operational context, not payment result only.\r\n\r\n### Shipping and Dispenser Precision\r\n\r\n- `dispenser`, `dispenser_desc`, and `dispenser_method_id` are meaningful only for shipping methods that support locker\/paket-shop mode.\r\n- `dispenser_method_id` must match top-level `shipping_method` to be considered in downstream processing.\r\n- Keep shipping address and shipping method logically aligned (country\/state\/postcode compatibility).\r\n\r\n### Payload Size and Data Modeling\r\n\r\n- Keep total serialized order payload below `64 KB`.\r\n- Prefer short stable keys in `order_data`; avoid embedding huge documents.\r\n- Do not place sensitive secrets in `order_data`, `order_items`, or any client-visible payload field.\r\n- Put only metadata needed for reconciliation\/debugging into request payload.\r\n\r\n### Frontend and Backend Responsibility Split\r\n\r\n- Frontend: collect user choices, call initialized HPay APIs, listen to events, render status.\r\n- Backend: verify result signatures, process webhook, enforce idempotency, persist state.\r\n- Backend charge\/admin: generate signatures and run protected operations with Secret Key.\r\n\r\n### Recommended Test Matrix (Minimal but Complete)\r\n\r\n- Test FrontCore script load on an allowed origin and verify failure on a non-allowed origin.\r\n- Test success payment and failed payment (validation\/card failure).\r\n- Test close panel before completion and verify no accidental fulfillment.\r\n- Test retry after `onHPayResult` error flow.\r\n- Test with and without shipping section.\r\n- Test one request containing `order_data` and confirm data appears in resulting order `Data`.\r\n- Test one backend charge request with existing `vault_token_uid`.\r\n- Test webhook replay (send same payload twice) and confirm idempotent behavior.\r\n- Test signature mismatch scenario and verify order is not fulfilled.\r\n- Test one `RESERVED`\/authorization-capable method if available and confirm capture-dependent flow.\r\n\r\n### Logging and Observability\r\n\r\n- Log correlation tuple for each request: `order_uid`, `merchant_site_uid`, environment, method IDs, and internal user\/session key.\r\n- Store raw HPay response payloads for troubleshooting (with sensitive-data policy applied).\r\n- Keep separate logs for browser callback processing and webhook processing.\r\n- Add clear audit events: `PAYMENT_INIT`, `PAYMENT_RESULT_RECEIVED`, `WEBHOOK_RECEIVED`, `SIGNATURE_VALIDATED`, `FULFILLMENT_TRIGGERED`.\r\n\r\n### Common Integration Mistakes to Avoid\r\n\r\n- Forgetting to whitelist the exact test\/prod origin for FrontCore.\r\n- Using `pm.Uid` instead of `pm.HPaySiteMethodId`.\r\n- Mixing sandbox credentials with production script\/API domain.\r\n- Trusting browser callback without server-side verification.\r\n- Reusing old `order_uid` across retries\/orders.\r\n- Treating all non-error statuses as final success.\r\n- Sending huge custom payloads over `64 KB`.\r\n\r\n### Field Constraints Quick Reference\r\n\r\n| Field | Required | Typical format | Precision note |\r\n|---|---|---|---|\r\n| `merchant_site_uid` | Yes | UUID-like string | Must match FrontCore script path and environment POS |\r\n| `hpaylang` | No | `en`, `rs`, `de`, ... | Keep consistent across UI and requests |\r\n| `order_uid` | Yes | App-specific unique string | Unique per purchase attempt |\r\n| `order_name` | No | Human-readable label | Useful for support and logs |\r\n| `order_amount` | Yes | Numeric string | Use same value across client\/server |\r\n| `order_currency` | Yes | ISO 4217 | Must be supported by selected method |\r\n| `payment_method` | Yes | `HPaySiteMethodId` string | Never use `pm.Uid` here |\r\n| `shipping_method` | No | `HPaySiteMethodId` string | Send only when shipping is used |\r\n| `order_user_url` | No | HTTPS URL | Browser flow only |\r\n| `notify_url` | No | Public HTTPS URL | Webhook authority endpoint |\r\n| `order_data` | No | Object\/map | Stored in resulting order `Data` |\r\n| `cof` | No | `optional|required|none` | Checkout card-save behavior |\r\n| `vault_token_uid` | No | Token UUID or `1|true|new` | Saved card or save-request hint |\r\n| `verificationhash` | No (initial FrontCore pay) | SHA-512 lowercase hex | Required for backend charge\/verification |\r\n| `order_billing.email` | No | Email | Validate syntax before send |\r\n| `order_billing.phone` | No | E.164-like | Keep normalized |\r\n| `order_billing.country` | No | ISO-2 | Use uppercase (`RS`, `US`) |\r\n| `order_shipping.country` | No | ISO-2 | Align with shipping method support |\r\n| `order_shipping.dispenser` | No | Provider ID | Locker\/paket-shop only |\r\n| `order_shipping.dispenser_method_id` | No | Method ID string | Must match `shipping_method` |\r\n| `order_items[].posuid` | Yes | String\/number | Merchant-defined internal item identifier (not a fixed HolestPay enum) |\r\n| `order_items[].name` | Yes | String | Human-readable item name (not `title`) |\r\n| `order_items[].qty` | Yes | Number\/string | Positive quantity expected (not `quantity`) |\r\n| `order_items[].price` | No | Number\/string | Keep pricing model consistent |\r\n| `order_items[].subtotal` | Yes | Number\/string | Mandatory line subtotal |\r\n| `order_items[].virtual` | No | `0|1` | Shipping relevance hint |\r\n\r\n### FrontCore Origin and Script Diagnostics\r\n\r\n- Validate script URL format exactly: `\/clientpay\/handlers\/pos\/{merchant_site_uid}\/frontend-script-core.js`.\r\n- Confirm script host matches environment (`sandbox.pay.holest.com` vs `pay.holest.com`).\r\n- Confirm browser page origin is included in `Frontend Script-Core Origins`.\r\n- Use explicit startup guard for missing global:\r\n\r\n```javascript\r\nsetTimeout(() => {\r\n  if (typeof HolestPayCheckout === 'undefined') {\r\n    console.error('FrontCore script not loaded: verify origin whitelist and script URL.');\r\n  }\r\n}, 5000);\r\n```\r\n\r\n- Keep whitelist entries minimal and deliberate; avoid broad wildcards on production unless absolutely necessary.\r\n- Re-check whitelist when moving from local\/dev domain to staging\/prod domain.\r\n\r\n### Server Workflow Blueprint (FrontCore)\r\n\r\n```text\r\n1) Frontend loads FrontCore script from handlers\/pos\/{merchant_site_uid} URL.\r\n2) Frontend initializes HPay and builds pay_request (without verificationhash).\r\n3) Frontend calls HPay.presentHPayPayForm(pay_request).\r\n4) Frontend receives onHPayResult and forwards payload to backend for logging.\r\n5) Backend verifies result signature for authoritative processing.\r\n6) Webhook arrives (possibly before\/after browser callback).\r\n7) Backend verifies webhook signature and applies idempotent state transition.\r\n8) Fulfillment runs only once after verified terminal\/success policy conditions.\r\n9) For MIT\/COF backend charge, backend builds charge_request + verificationhash and calls charge endpoint.\r\n```\r\n\r\n### Idempotent Webhook Handling Example (Node.js Pseudocode)\r\n\r\n```javascript\r\n\/\/ Pseudocode only: adapt to your DB and framework.\r\napp.post('\/webhooks\/hpay', async (req, res) => {\r\n  const payload = req.body || {};\r\n  const orderUid = String(payload.order_uid || '');\r\n  const txUid = String(payload.transaction_uid || '');\r\n  const idempotencyKey = `${orderUid}:${txUid}:${payload.status || ''}`;\r\n\r\n  if (!orderUid) return res.status(400).json({ ok: false, error: 'missing order_uid' });\r\n\r\n  \/\/ Signature validation is still mandatory for backend authority\r\n  const calculated = generatePOSRequestSignature(MERCHANT_SITE_UID, SECRET_KEY, payload);\r\n  const valid = calculated === String(payload.vhash || '').toLowerCase();\r\n  if (!valid) return res.status(400).json({ ok: false, error: 'invalid signature' });\r\n\r\n  const alreadyProcessed = await db.idempotency.exists(idempotencyKey);\r\n  if (alreadyProcessed) return res.status(200).json({ ok: true, duplicate: true });\r\n\r\n  await db.tx(async trx => {\r\n    await trx.idempotency.insert({ key: idempotencyKey, createdAt: new Date() });\r\n    await trx.hpayEvents.insert({\r\n      orderUid,\r\n      transactionUid: txUid,\r\n      paymentStatus: payload.payment_status || null,\r\n      orderStatus: payload.status || null,\r\n      raw: JSON.stringify(payload),\r\n      source: 'webhook'\r\n    });\r\n\r\n    const paymentStatus = String(payload.payment_status || '').toUpperCase();\r\n    if (\/PAID|SUCCESS|PAYING|RESERVED|OBLIGATED\/.test(paymentStatus)) {\r\n      await trx.orders.markAsPaidOrReserved(orderUid, paymentStatus);\r\n      await trx.jobs.enqueue({ type: 'FULFILLMENT_DECISION', orderUid });\r\n    } else if (\/AWAITING\/.test(paymentStatus)) {\r\n      \/\/ Not failed: keep order pending and show payment_html instructions on return\/thank-you page\r\n      await trx.orders.markAsPendingPaymentInstructions(orderUid, paymentStatus);\r\n    } else {\r\n      await trx.orders.markAsPaymentNotCompleted(orderUid, paymentStatus);\r\n    }\r\n  });\r\n\r\n  return res.status(200).json({ ok: true });\r\n});\r\n```\r\n\r\n### Browser Callback + Webhook Reconciliation Rules\r\n\r\n- If browser callback succeeds first, store it as provisional until webhook check completes.\r\n- If webhook succeeds first, mark server truth immediately; browser callback only enriches logs\/UI.\r\n- If callback says success but webhook never arrives, run delayed status query before fulfillment.\r\n- If callback and webhook disagree, trust verified server-side signature + latest authoritative status policy.\r\n- Never ship goods\/services exclusively from client-side event data.\r\n\r\n### Operational State Model (Recommended)\r\n\r\n- `ORDER_CREATED`: order exists locally before payment started.\r\n- `PAYMENT_INITIATED`: FrontCore request sent to payment UI.\r\n- `PAYMENT_CALLBACK_RECEIVED`: browser event stored.\r\n- `WEBHOOK_RECEIVED`: webhook accepted and verified.\r\n- `PAYMENT_CONFIRMED`: business rule says financial completion met.\r\n- `FULFILLMENT_PENDING`: queued for shipping\/fiscal\/integration actions.\r\n- `FULFILLED`: goods\/services delivered or process completed.\r\n- `PAYMENT_FAILED`: terminal non-success status.\r\n- `CLOSED`: final immutable state with audit trail.\r\n\r\n### Production Readiness Checklist\r\n\r\n- Keep `Frontend Script-Core Origins` strict and reviewed per deployment.\r\n- Protect admin\/backend code paths that collect\/use Secret Key.\r\n- Add request\/response size limits and payload schema validation at backend edge.\r\n- Enable TLS-only endpoints for checkout and webhook.\r\n- Add monitoring alarms for missing FrontCore script load events and webhook signature failures.\r\n- Capture dashboard metrics: conversion by method, callback-to-webhook lag, duplicate webhook rate.\r\n- Create runbook for manual recovery: status query, refund\/void\/capture decisions, replay-safe reprocessing.\r\n- Test environment cutover with explicit checklist (POS UID, methods, notify URLs, allowed origins, secrets).\r\n\r\n### Troubleshooting Cookbook\r\n\r\n- Symptom: `HolestPayCheckout` is undefined.\r\n- Check: origin whitelist entry, script URL path includes correct `merchant_site_uid`, environment host.\r\n- Fix: add exact HTTPS test origin to `Frontend Script-Core Origins` and reload.\r\n\r\n- Symptom: FrontCore works on one domain but not another.\r\n- Check: wildcard pattern coverage and path-level matching assumptions.\r\n- Fix: add explicit origin\/pattern entry for each domain stage (dev\/stage\/prod) and retest.\r\n\r\n- Symptom: payment UI opens but methods list is empty.\r\n- Check: POS method activation in panel, country\/amount filters, currency compatibility.\r\n- Fix: verify `availablePaymentMethods(...)` inputs and POS configuration in same environment.\r\n\r\n- Symptom: result arrives in browser but order not updated on server.\r\n- Check: callback forwarding endpoint, webhook delivery logs, signature verification result.\r\n- Fix: persist callback as provisional and complete state transition on verified webhook.\r\n\r\n- Symptom: signature mismatch on webhook\/backend verification.\r\n- Check: amount normalization to 8 decimals, field order in signature string, correct secret key\/environment.\r\n- Fix: use one shared signature utility across all server handlers and add test vectors.\r\n\r\n- Symptom: webhook endpoint receives duplicates.\r\n- Check: idempotency key existence and transaction wrapping around insert\/process logic.\r\n- Fix: move idempotency write before business action and enforce unique DB constraint on key.\r\n\r\n- Symptom: charge request fails with saved token.\r\n- Check: token validity\/scope, method supports `charge`, signature correctness, environment match.\r\n- Fix: use fresh token tied to same merchant scope and verify method `POps` includes `charge`.\r\n\r\n- Symptom: user is not redirected to thank-you URL.\r\n- Check: method iframe\/redirect behavior and callback handling.\r\n- Fix: redirect manually in success branch using `r.order_user_url` when appropriate.\r\n\r\n### Suggested CI\/QA Validation Cases\r\n\r\n- Validate JSON schema for `pay_request` and `charge_request` in unit tests.\r\n- Validate signature generation against fixed fixtures in PHP and Node.js implementations.\r\n- Validate idempotency behavior by replaying same webhook payload at least 3 times.\r\n- Validate parser for composed status strings with and without fiscal\/shipping segments.\r\n- Validate route-level access control for admin\/backend operations requiring Secret Key.\r\n- Validate payload size guard rejects requests above `64 KB`.\r\n- Validate environment guard rejects sandbox credentials on production host and vice versa.\r\n- Validate origin guard behavior: whitelisted origin passes, non-whitelisted origin fails predictably.\r\n- Validate correlation logs always include `order_uid` and `merchant_site_uid`.\r\n\r\n### Security No-Go List\r\n\r\n- Never hardcode `secret_key` in browser JavaScript, HTML templates, or mobile bundle assets.\r\n- Never process fulfillment on unverified callback payload.\r\n- Never expose raw webhook endpoint without rate limiting and request body size limits.\r\n- Never trust query params from `order_user_url` alone as proof of payment.\r\n- Never log secret values or full PAN\/cardholder data in application logs.\r\n- Never reuse one `order_uid` for multiple independent purchases.\r\n\r\n### Terminology Quick Map\r\n\r\n- `POS` in HolestPay = your app\/site sales endpoint, not a physical cashier terminal.\r\n- `FrontCore` = JS-first flow where initial checkout signing is origin-trust based.\r\n- `status` = composed platform state across payment\/fiscal\/shipping modules.\r\n- `payment_status` = payment module status focus.\r\n- `verificationhash` = request signature hash used for backend charge\/operations.\r\n- `vhash` = response signature hash received from HPay result\/webhook.\r\n- `order_data` = custom merchant metadata persisted to order `Data`.\r\n\r\n---\r\n\r\n## Key Object Reference\r\n\r\nAll fields supported by the template are listed in `Step 2` under `Full Template Field Catalog (Standalone)`.\r\n\r\n---\r\n\r\n## Important Notes\r\n\r\n- The page must be served over **HTTPS**. HPay will not work on `file:\/\/` or plain HTTP.\r\n- `payment_method` value is `pm.HPaySiteMethodId` \u2014 **not** `pm.Uid` (these are different).\r\n- `availablePaymentMethods(country, amount, currency)` returns methods available for the given buyer country and order. Use billing country for payment, shipping country for shipping.\r\n- If `pm['Use IFRAME'] === false`, the method uses redirect \u2014 your page cannot receive the postback inline. Proper server-side integration (webhook) is required.\r\n- After a successful payment, generate a new `order_uid` for the next order \u2014 never reuse UIDs.\r\n- `order_data` supports custom metadata and is stored as HPay order `Data`.\r\n- `order_billing` and `order_shipping` can include additional custom fields, but keep total serialized order payload under **64 KB**.\r\n- If your FrontCore page also needs to display the Secret Key prompt (e.g. for admin ops), collect it via a prompt\/dialog at runtime \u2014 never hardcode it in the page source.\r\n- The Secret Key may be collected once per session (e.g. via `prompt()` or an admin login flow) and stored in a local variable for use during the current session only.\r\n\r\n---\r\n\r\n## Required for Bank Production Approval \u2014 Logotypes and Terms of Service\r\n\r\n> ?? **This section has nothing to do with payment functionality** \u2014 payments work without it. However, **almost all banks in the region will require these elements to be present on the site before approving the POS for production use.** Implement these alongside the payment integration.\r\n\r\n---\r\n\r\n### Footer Logotypes (Card logos, Bank logos, 3DS logos)\r\n\r\nBanks require card brand logos, acquiring bank logos, and 3DS security logos to appear in the site footer \u2014 in a single horizontal line, **no taller than approximately 1 cm visually**. Order: card logos first, bank logos in the middle (with some spacing on both sides), 3DS logos on the right. Bank and 3DS logos must be linked. The footer strip must be visible on **all pages of the website**, not only on checkout.\r\n\r\nHolestPay provides all logo image URLs and link targets via `HPay.POS.pos_parameters` after `HPayInit()`. Use them \u2014 do not source logos elsewhere.\r\n\r\n**CSS for the footer branding strip:**\r\n\r\n```css\r\n.hpay_footer_branding_wrapper {\r\n  width: 100%;\r\n  padding: 8px 0;\r\n  border-top: 1px solid #e0e0e0;\r\n}\r\n.hpay_footer_branding {\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 0;\r\n  flex-wrap: nowrap;\r\n}\r\n.hpay-footer-branding-cards,\r\n.hpay-footer-branding-bank,\r\n.hpay-footer-branding-3ds {\r\n  display: flex;\r\n  align-items: center;\r\n  flex-wrap: nowrap;\r\n}\r\n.hpay-footer-branding-bank  { margin: 0 12px; }\r\n.hpay_footer_branding img   { height: 1cm; width: auto; display: inline-block; }\r\n```\r\n\r\n**JavaScript \u2014 call this after `HPayInit()` resolves:**\r\n\r\n```javascript\r\nfunction renderHPayFooterLogotypes() {\r\n  if (!(typeof HPay !== 'undefined' && HPay && HPay.POS)) return;\r\n  if (!HPay.POS.pos_parameters) return;\r\n\r\n  let card_images_html = '';\r\n  let banks_html       = '';\r\n  let threes_html      = '';\r\n\r\n  if (HPay.POS.pos_parameters['Logotypes Card Images']) {\r\n    HPay.POS.pos_parameters['Logotypes Card Images'].split('\\n').forEach(src => {\r\n      if (src.trim()) card_images_html += `<img decoding=\"async\" src=\"${src.trim()}\" alt=\"Card\" \/>`;\r\n    });\r\n  }\r\n\r\n  if (HPay.POS.pos_parameters['Logotypes Banks']) {\r\n    HPay.POS.pos_parameters['Logotypes Banks'].split('\\n').forEach(line => {\r\n      if (!line.trim()) return;\r\n      \/\/ format: \"imageUrl:linkUrl\" \u2014 colons in URLs are escaped before splitting\r\n      const t = line.replace(\/https:\/gi, '-PS-').replace(\/http:\/gi, '-P-')\r\n        .split(':').map(r => r.replace(\/-P-\/g, 'http:').replace(\/-PS-\/g, 'https:'));\r\n      banks_html += t.length > 1\r\n        ? `<a href=\"${t[1]}\" target=\"_blank\"><img decoding=\"async\" src=\"${t[0]}\" alt=\"Bank\" \/><\/a>`\r\n        : `<img decoding=\"async\" src=\"${t[0]}\" alt=\"Bank\" \/>`;\r\n    });\r\n  }\r\n\r\n  if (HPay.POS.pos_parameters['Logotypes 3DS']) {\r\n    HPay.POS.pos_parameters['Logotypes 3DS'].split('\\n').forEach(line => {\r\n      if (!line.trim()) return;\r\n      const t = line.replace(\/https:\/gi, '-PS-').replace(\/http:\/gi, '-P-')\r\n        .split(':').map(r => r.replace(\/-P-\/g, 'http:').replace(\/-PS-\/g, 'https:'));\r\n      threes_html += t.length > 1\r\n        ? `<a href=\"${t[1]}\" target=\"_blank\"><img decoding=\"async\" src=\"${t[0]}\" alt=\"3DS\" \/><\/a>`\r\n        : `<img decoding=\"async\" src=\"${t[0]}\" alt=\"3DS\" \/>`;\r\n    });\r\n  }\r\n\r\n  const logotypesDiv = document.createElement('div');\r\n  logotypesDiv.className = 'hpay_footer_branding';\r\n  logotypesDiv.innerHTML =\r\n    `<div class=\"hpay-footer-branding-cards\">${card_images_html}<\/div>` +\r\n    `<div class=\"hpay-footer-branding-bank\">${banks_html}<\/div>` +\r\n    `<div class=\"hpay-footer-branding-3ds\">${threes_html}<\/div>`;\r\n\r\n  const wrapper = document.createElement('div');\r\n  wrapper.className = 'hpay_footer_branding_wrapper';\r\n  wrapper.appendChild(logotypesDiv);\r\n\r\n  (document.querySelector('footer') || document.querySelector('main') || document.body)\r\n    .appendChild(wrapper);\r\n}\r\n\r\n\/\/ Call after HPayInit resolves:\r\n\/\/ HPayInit().then(client => { renderHPayFooterLogotypes(); });\r\n```\r\n\r\n---\r\n\r\n### Terms of Service Acceptance Checkbox\r\n\r\nAlmost all banks in the region require the customer to explicitly accept the site Terms of Service before payment \u2014 typically a checkbox with a link to the TOS content.\r\n\r\n**HolestPay provides a ready-to-use Terms of Service \/ Purchase Conditions template page for each POS\/site:**\r\n\r\n```\r\nhttps:\/\/sandbox.pay.holest.com\/clientpay\/tos\/<merchant_site_uid>?lang=rs\r\nhttps:\/\/pay.holest.com\/clientpay\/tos\/<merchant_site_uid>?lang=en\r\n```\r\n\r\nSupported `lang` values: `en`, `rs`, `bs`, `hr`, `me`, `de`, `es`, `gr`, `tr`, `mk`, and others.\r\n\r\nThis URL returns HTML content (no `<html>` wrapper) and can be:\r\n- Loaded in an `<iframe>`\r\n- Fetched with `fetch()` \/ AJAX and injected into a modal\r\n- Opened directly in the browser (use `target=\"_blank\"`)\r\n\r\n> ?? **Important:** This is **not** a separate \"second HolestPay Terms\" that replaces your site Terms. The `\/clientpay\/tos\/<merchant_site_uid>` content is a merchant-specific **template\/proposal of your own purchase Terms** (with your POS\/merchant context), meant to be used as the same Terms of Service page your checkout checkbox points to. Keep one coherent TOS page for customers (your site TOS), and ensure it includes the required payment clauses.\r\n\r\n**Implementation \u2014 TOS modal using HolestPay built-in dialog** (`hpay_dialog_open` becomes available after `client.loadHPayUI()` resolves):\r\n\r\n```javascript\r\n\/\/ Call client.loadHPayUI() first to make hpay_dialog_open available:\r\n\/\/ HPayInit().then(client => client.loadHPayUI()).then(() => { \/* hpay_dialog_open is ready *\/ });\r\n\r\nfunction openTOS(merchant_site_uid, lang) {\r\n  const tos_url = `https:\/\/sandbox.pay.holest.com\/clientpay\/tos\/${merchant_site_uid}?lang=${lang || 'en'}`;\r\n  \/\/ Replace sandbox host with pay.holest.com for production\r\n  hpay_dialog_open('tos', 'Terms of Service', tos_url, 'large', {\r\n    ['I understand and accept these Terms of Service']: {\r\n      Run: function(dlg) {\r\n        dlg.close();\r\n        if (dlg.parentNode) dlg.parentNode.removeChild(dlg);\r\n        \/\/ Mark TOS as accepted \u2014 enable the Pay button or proceed with payment\r\n      },\r\n      action_position: 'center',\r\n      style: { 'min-width': '120px' }\r\n    }\r\n  });\r\n}\r\n```\r\n\r\n**Minimum required UI \u2014 add this near the Pay button:**\r\n\r\n```html\r\n<label style=\"display:flex;align-items:center;gap:8px;margin-bottom:8px;\">\r\n  <input type=\"checkbox\" id=\"tos-accept\" required \/>\r\n  <span>\r\n    I accept the\r\n    <a href=\"#\" onclick=\"openTOS(MERCHANT_SITE_UID, 'en'); return false;\">Terms of Service<\/a>\r\n  <\/span>\r\n<\/label>\r\n<!-- Disable Pay button until checkbox is checked -->\r\n```\r\n\r\n```javascript\r\ndocument.getElementById('tos-accept').addEventListener('change', function() {\r\n  document.getElementById('do-pay').disabled = !this.checked;\r\n});\r\n```\r\n\r\n**Checklist for bank approval:**\r\n- [ ] Card brand logos visible in footer (from `HPay.POS.pos_parameters['Logotypes Card Images']`)\r\n- [ ] Bank logos visible in footer, linked (from `HPay.POS.pos_parameters['Logotypes Banks']`)\r\n- [ ] 3DS logos visible in footer, linked (from `HPay.POS.pos_parameters['Logotypes 3DS']`)\r\n- [ ] All logos in a single horizontal line, max ~1 cm height\r\n- [ ] TOS checkbox present near the Pay button, Pay button disabled until checked\r\n- [ ] TOS content matches or includes HolestPay-provided TOS (`\/clientpay\/tos\/<uid>`)\r\n\r\n---\r\n\r\n## Test Cards for Sandbox Testing\r\n\r\nTest card numbers for almost all supported banks and payment methods can be found directly on the **dashboard page** of the HolestPay sandbox panel at https:\/\/sandbox.pay.holest.com \u2014 no need to search elsewhere. Log in and check the dashboard.\r\n\r\n---\r\n\r\n## Terms of Service Page \u2014 Required Content\r\n\r\nThe \"Terms of Service\" page that the customer checks before payment must be **comprehensive and self-contained**. Because the customer ticks a single \"I accept the Terms of Service\" checkbox that links to this one page, **everything required by the bank and by law must be covered on that exact page** \u2014 not spread across separate pages.\r\n\r\nThe following must be present in the Terms of Service content (either written directly or clearly summarized with a reference to the full document inline):\r\n\r\n- **Purchase conditions** \u2014 what is being sold, pricing, order confirmation process\r\n- **Delivery policy** \u2014 delivery methods, timeframes, costs, geographic coverage\r\n- **Refund and return policy** \u2014 conditions, process, and timeframes for refunds and returns\r\n- **PCI DSS statement** \u2014 explicit confirmation that card data transfer\/processing is secured according to PCI DSS standards and that the merchant does not store raw card data (HolestPay handles card processing on the merchant's behalf)\r\n- **Merchant contact information** \u2014 legal business name, address, email, and phone number for customer support\r\n\r\n> The merchant may have separate dedicated pages for privacy policy, delivery conditions, refund policy, etc. \u2014 that is fine. But **all of the above topics must also appear on the single Terms of Service page** that the payment checkbox links to, because that is the only page the customer is asked to review and accept before paying.\r\n\r\n> HolestPay provides a base TOS\/purchase-conditions text via `\/clientpay\/tos\/<merchant_site_uid>` that already includes payment-security wording (including PCI DSS transfer handling). Treat it as baseline content for the merchant's own single Terms page, then extend it with delivery, refund, and contact details \u2014 not as a separate second TOS document.\r\n\r\n---\r\n\r\n## Before Requesting Bank Production Approval \u2014 Site Readiness\r\n\r\nBefore the merchant contacts their bank to activate the POS for production, the site must be genuinely complete. A bank technician will review the site manually and will reject it if it does not look like a real, finished online store.\r\n\r\n**The following will cause immediate rejection:**\r\n\r\n- **Placeholder content** \u2014 any \"Lorem ipsum\", \"Coming soon\", \"Test product\", dummy images, or unfilled template text anywhere on the site\r\n- **Mixed languages** \u2014 site content, product names, descriptions, and all pages must be consistently in one language (or properly localized if multilingual); a mix of languages on the same page signals an unfinished site\r\n- **Missing or incomplete pages** \u2014 the site must have real products\/services with actual descriptions and prices, a working checkout flow, and all required legal pages (Terms of Service, Privacy Policy, Contact\/About)\r\n- **Non-functional links or broken pages** \u2014 every linked page must load and contain real content\r\n- **Test or sandbox data visible** \u2014 no test order UIDs, sandbox URLs, or debug output visible to visitors\r\n\r\n> Tell the user: *\"Before you ask your bank to approve the POS for production, make sure the site looks like a finished, real store. Bank technicians visit the site in person before approving. If they see Lorem ipsum text, empty product pages, mixed languages, or anything that looks unfinished, they will ask you to complete the site first and resubmit \u2014 which adds weeks to the process.\"*\r\n\r\n---\r\n\r\n## Bank Validation Testing Before Production Approval\r\n\r\nMost banks require a **formal validation test** to be performed before activating the POS for production. This is separate from the developer's own sandbox testing.\r\n\r\nWhat this typically involves:\r\n\r\n- The bank provides a set of **specific test scenarios** that must be completed successfully (e.g. approved payment, declined payment, 3DS challenge flow, cancellation)\r\n- Tests are performed using **test cards provided by the bank** (which may differ from the general sandbox test cards on the HPay dashboard)\r\n- Results are reported back to the bank (sometimes automatically via their test monitoring system, sometimes manually by the merchant)\r\n- **Whether validation testing is required, and exactly how it is done, depends on the bank and the specific payment method** \u2014 some banks require it for every new integration, others only for certain card brands or 3DS configurations\r\n\r\n> Tell the user: *\"Before your bank activates the production POS, they will likely ask you to run a set of validation test payments using specific test cards they provide. This is a standard bank requirement and is separate from the testing you did during development. Contact your bank's technical integration team to find out exactly what tests are required for your payment method \u2014 HolestPay sandbox will be used for these tests as well.\"*\r\n\r\n---\r\n\r\n## Shipping Method UI \u2014 Required `hpay-sm-options` Placeholder\r\n\r\nIn the UI element that displays a shipping method's description (the area shown to the buyer when they select a shipping method), you **must include an empty `<span>` with the class `hpay-sm-options` and attribute `hpay_shipping_method_id=\"<hpay_shipping_method.HPaySiteMethodId>\"`**:\r\n\r\n```html\r\n<span class=\"hpay-sm-options\" hpay_shipping_method_id=\"<hpay_shipping_method.HPaySiteMethodId>\"><\/span>\r\n```\r\n\r\nHolestPay uses this element to inject additional shipping options and information (e.g. parcel locker selector, delivery time slots, carrier tracking info) for the selected shipping method. Without this placeholder, those features will silently fail to render.\r\n\r\nPlace it inside the shipping method description container, after any static description text:\r\n\r\n```html\r\n<div class=\"shipping-method-description\">\r\n  <!-- your static description text here -->\r\n  <span class=\"hpay-sm-options\" hpay_shipping_method_id=\"45\"><\/span>\r\n<\/div>\r\n```\r\n\r\n---\r\n\r\n## Content Security Policy (CSP) and Site Configuration Requirements\r\n\r\nFor HolestPay to function correctly, the site must permit the following \u2014 ensure these are not blocked by Content Security Policy headers, server configuration, or any firewall\/proxy:\r\n\r\n### Allowed origins (iframes, scripts, XHR\/fetch, content)\r\n\r\nThe site must allow iframes, scripts, and content loaded from:\r\n\r\n```\r\nhttps:\/\/pay.holest.com\r\nhttps:\/\/sandbox.pay.holest.com\r\n```\r\n\r\nIf the site sets a `Content-Security-Policy` header, include these in all relevant directives:\r\n\r\n```\r\nContent-Security-Policy:\r\n  script-src  'self' https:\/\/pay.holest.com https:\/\/sandbox.pay.holest.com ...;\r\n  frame-src   'self' https:\/\/pay.holest.com https:\/\/sandbox.pay.holest.com ...;\r\n  connect-src 'self' https:\/\/pay.holest.com https:\/\/sandbox.pay.holest.com ...;\r\n  img-src     'self' https:\/\/pay.holest.com https:\/\/sandbox.pay.holest.com data: ...;\r\n```\r\n\r\n### `eval()` must be permitted\r\n\r\nHolestPay internally uses `eval()` to deserialize and execute certain POS method action functions returned from the server (`initActions`, `orderActions`). The site must **not** block `eval()`.\r\n\r\nIf a CSP header is set, `'unsafe-eval'` must be present in `script-src`:\r\n\r\n```\r\nContent-Security-Policy:\r\n  script-src 'self' 'unsafe-eval' https:\/\/pay.holest.com https:\/\/sandbox.pay.holest.com ...;\r\n```\r\n\r\n> ?? If the site uses a strict CSP that blocks `eval()` or restricts iframes\/scripts to specific origins, HolestPay will fail silently or partially. This is one of the most common causes of \"nothing happens when I click Pay\" during integration.\r\n\r\n---\r\n\r\n## Dock Container Placement\r\n\r\nFor each payment method row, use this visual order:\r\n1) method **name\/label**\r\n2) method **description**\r\n3) method **dock container** (if that method supports docking)\r\n\r\nThe dock container must be below the description of the same method, and before the next method name is rendered. It must **not** be placed after the full list of all payment methods.\r\n\r\nEach payment method in the list that supports docking (`pm.PayInputUrl` is set) needs its own dock container adjacent to it. Best practice is to wrap **description + dock** in one method-details wrapper that is shown only when that method is selected. When the buyer selects that method, `HPay.setPaymentMethodDock()` renders the embedded payment input directly inside that container.\r\n\r\n**Correct structure:**\r\n```html\r\n<label>\r\n  <input type=\"radio\" name=\"payment_method\" value=\"179\" \/>\r\n  Credit Card\r\n<\/label>\r\n<div class=\"method-details is-selected\" data-method=\"179\">\r\n  <div class=\"method-description\">Card payment description...<\/div>\r\n  <div id=\"paymentMethodDock-179\" class=\"hpay-dock-container\"><\/div>\r\n<\/div>\r\n\r\n<label>\r\n  <input type=\"radio\" name=\"payment_method\" value=\"180\" \/>\r\n  Bank Transfer\r\n<\/label>\r\n<div class=\"method-details\" data-method=\"180\">\r\n  <div class=\"method-description\">Bank transfer description...<\/div>\r\n  <!-- no dock here \u2014 this method does not support docking -->\r\n<\/div>\r\n```\r\n\r\n---\r\n\r\n## Always Render Method Description When Selected\r\n\r\nWhenever a payment method or shipping method is selected by the buyer, **always display its description**. Do not skip or hide descriptions.\r\n\r\nMethod descriptions sometimes contain **functionally important content** \u2014 instructions the buyer must read (e.g. bank account number for manual transfer, pickup location details, special conditions). Hiding or omitting descriptions can cause buyer confusion and failed orders.\r\n\r\nFor shipping methods, the description container must also include the `<span class=\"hpay-sm-options\" hpay_shipping_method_id=\"<hpay_shipping_method.HPaySiteMethodId>\"><\/span>` placeholder (see earlier section) so HolestPay can inject carrier-specific UI into it.\r\n\r\n---\r\n\r\n## HolestPay Language Codes\r\n\r\nHolestPay uses its own language code convention \u2014 **do not use standard ISO\/BCP-47 codes** for Serbian:\r\n\r\n| Language | HolestPay code | ? Do NOT use |\r\n|---|---|---|\r\n| Serbian Latin | `rs` | `sr`, `sr-Latn`, `sr-YU`, `sr-lat` |\r\n| Serbian Cyrillic | `rs-cyr` | `sr-Cyrl`, `sr-cyr`, `cyr` |\r\n| Bosnian | `bs` | |\r\n| Croatian | `hr` | |\r\n| Slovenian | `si` | |\r\n| Macedonian | `mk` | |\r\n| English | `en` | |\r\n| German | `de` | |\r\n| Italian | `it` | |\r\n| Spanish | `es` | |\r\n| French | `fr` | |\r\n| Portuguese | `pt` | |\r\n| Dutch | `nl` | |\r\n| Greek | `el` | `gr` |\r\n| Turkish | `tr` | |\r\n\r\nPass this value as the `language` parameter to `HPayInit()` and as `hpaylang` in `pay_request`.\r\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Tell your AI coding assistant (such as Cursor, for example) that you want to integrate HolestPay for payments, fiscalization, and shipping, and provide it with this MD markup: https:\/\/apps.holest.com\/holest-pay\/AI_INTEGRATION_GUIDE_FRONTCORE.md.txt Your AI assistant will then know exactly how to guide you through the process. Make sure to SET UP FIRST ALL methods you plan to use [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_hpay_column_layout":"0","_hpay_name_price":"0","footnotes":""},"categories":[68],"tags":[],"class_list":["post-6653","post","type-post","status-publish","format-standard","hentry","category-hpay-ai-integration"],"_links":{"self":[{"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/posts\/6653","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/comments?post=6653"}],"version-history":[{"count":11,"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/posts\/6653\/revisions"}],"predecessor-version":[{"id":6685,"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/posts\/6653\/revisions\/6685"}],"wp:attachment":[{"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/media?parent=6653"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/categories?post=6653"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ecommerce.holest.com\/en\/wp-json\/wp\/v2\/tags?post=6653"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}