Reci Lovable-u (ili sličnom sistemu) da želiš da integrišeš HolestPay plaćanja/fiskalizaciju/isporuku i daj mu ovaj MD markup: https://apps.holest.com/holest-pay/Lovable_or_similar_ai_system.MD.txt
Lovable će tada znati šta treba da te pita i koje instrukcije da ti da…
PODEŠITE PRVO SVE metode koje planirate da koristite (placanje | fiskalizacija | isporuka-API) na https://sandbox.pay.holest.com/ i testirajte ih putem „pay-by-link“ opcije (potpuno eksterni proces). Ako je sve ispravno konfigurisano, integracija će teći glatko uz svega nekoliko upita.
HolestPay FrontCore Guide for Lovable and Similar AI Site Builder Systems – MD file…
# HolestPay FrontCore Integration — Guide for Lovable / AI Code Generators
> **Full technical reference:** https://apps.holest.com/holest-pay/AI_INTEGRATION_GUIDE_FRONTCORE.md.txt
> **Working sample (sandbox):** https://apps.holest.com/holest-pay/test/sandbox-164-hpay-frontcore-sample.html
> (View page source and inspect `` tags for real implementation examples.)
>
> **Sample data files — read these to understand real data structures:**
> - `pos_as_read.json` — https://apps.holest.com/holest-pay/pos_as_read.json
> 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.). **Read this to know what payment/shipping methods look like before building any selector UI.**
> - `response_sample.json` — https://apps.holest.com/holest-pay/response_sample.json
> 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. **Read this to know what data is available when displaying the thank-you / order confirmation page.**
---
## ?? FIRST ACTION — Ask the User for Their FrontCore Script URL
**Before writing any code, ask the user:**
> *"Please give me your HolestPay FrontCore script URL. It looks like this:*
> ``
> *Do you have it?"*
**If the user says they don't have it, or doesn't know what it is, explain:**
> *"This is a unique `
```
The UID segment in the URL (`07f689c5-...`) is the Merchant Site UID — it is embedded in the URL itself.
**If the user confirms they need backend implementation** (admin actions, backend charges — see pre-build questions above), they must also provide:
- **Merchant Site UID** — visible in HPay panel ? Site Settings (also in the FrontCore script URL)
- **Merchant Site Secret Key** — visible in HPay panel ? Site Settings (keep this server-side only, never expose on frontend)
### ?? Configuration values must be easily changeable by the user
Store all three of these as **user-editable configuration** (environment variables, a settings screen, or a clearly marked config file) — **never hardcode them**:
| Value | Where used | Why it changes |
|---|---|---|
| FrontCore script `src` URL | Frontend, global `
```
When the HPay script is loaded, it also exposes these globals on `window`:
- `window.presentHPayPayForm` — function
- `window.HPayIsSandbox` — environment flag variable
Add a startup check to catch misconfiguration early:
```javascript
// After page load — warn developer if FrontCore failed to initialize
setTimeout(function() {
if (typeof HolestPayCheckout === 'undefined') {
console.error(
'HolestPay FrontCore script did not load. ' +
'Check that the current origin is listed in "Frontend Script-Core Origins" in the HPay panel.'
);
}
}, 5000);
```
---
### Step 2 — Initialize HPay and Load Payment/Shipping Methods
Call `HPayInit()` once when the checkout page loads (or when the user reaches it). It returns a Promise.
- `merchant_site_uid` and environment (`sandbox`/`production`) are **not** parameters — they come from the script URL automatically.
- Language is optional — if omitted, HPay uses the `` attribute or the language set in HPay panel POS settings.
```javascript
// Save this after HPayInit so it can be used in charge_request and admin ops later
let hpayMerchantSiteUid = null;
HPayInit(/* language optional, e.g. 'en' */).then(async client => {
hpayMerchantSiteUid = client.MerchantsiteUid;
// Filter available methods by buyer's country (recommended)
const country = 'RS'; // use actual buyer country, ISO 3166-1 alpha-2
let availablePayment = [], availableShipping = [];
try { availablePayment = await HPay.availablePaymentMethods(country, orderAmount, orderCurrency); } catch(e) {}
try { availableShipping = await HPay.availableShippingMethods(country, orderAmount, orderCurrency); } catch(e) {}
// Populate payment method selector
client.POS.payment.forEach(pm => {
if (!pm.Hidden && (!availablePayment.length || availablePayment.find(m => m.Uid == pm.Uid))) {
// Add pm to your UI selector
// pm.HPaySiteMethodId ? use as pay_request.payment_method
// pm.Name ? display name (use this — do NOT invent a name)
// pm.SubsciptionsType ? contains "cof" or "mit" ? card saving is supported for this method
// pm.POps ? e.g. "charge,refund" ? backend operations available
// pm.PayInputUrl ? if set, this method supports docking (embedded payment input)
// pm['Use IFRAME'] ? if false, method uses redirect flow (bank page POST redirect)
}
});
// Populate shipping method selector (if POS has shipping configured)
if (client.POS.shipping) {
client.POS.shipping.forEach(sm => {
if (!sm.Hidden && (!availableShipping.length || availableShipping.find(m => m.Uid == sm.Uid))) {
// sm.HPaySiteMethodId ? use as pay_request.shipping_method
// sm.Name ? display name
}
});
}
});
```
> **Make sure existing payment methods in your UI have a name and description.** Use `pm.Name` from the POS config — do not hardcode or invent names.
---
### Step 3 — Build the `pay_request` and Initiate Payment
```javascript
const pay_request = {
// NOTE: merchant_site_uid is NOT included here for FrontCore — the script handles auth automatically
hpaylang: 'en', // optional
order_uid: 'ORDER-20260425-001', // required — unique per order
order_name: '#Order 204', // optional
order_amount: '15000.00', // required — string, in smallest currency unit or decimal
order_currency: 'RSD', // required — ISO 4217
payment_method: '179', // required — pm.HPaySiteMethodId from Step 2
shipping_method: '45', // optional — sm.HPaySiteMethodId
order_user_url: 'https://yoursite.com/thanks', // recommended — redirect/thank-you page URL
notify_url: 'https://yoursite.com/webhook', // optional — server-to-server webhook (must be publicly reachable)
cof: 'optional', // optional — 'optional'|'required'|'none' — enables card saving
vault_token_uid: '', // optional — saved token UUID, or 1|true|new to force new token
order_billing: { // optional but recommended
email: 'customer@example.com',
first_name: 'TEST',
last_name: 'TEST',
phone: '+38111111111',
is_company: 0,
company: '', // company legal name
company_tax_id: '', // company tax ID in merchant's country
company_reg_id: '', // company registration ID in merchant's country
address: 'TEST', // street name
address2: '', // recommended for street number / address addition
city: 'Beograd',
country: 'RS',
state: 'Beograd',
postcode: '11000',
lang: 'sr_RS' // language from merchant platform/system
},
order_shipping: { // optional
shippable: false,
is_cod: 1, // set to 1 when COD logic is used/allowed
first_name: '',
last_name: '',
phone: '',
company: '',
address: '', // street name
address2: '', // recommended for street number / address addition
city: '',
country: '',
state: '',
postcode: ''
},
order_items: [ // strongly recommended for reconciliation
{
posuid: 114, // merchant's own internal item ID (string or number)
type: 'product',
name: 'Sample product name',
sku: '000550',
qty: 1,
price: 4695.99,
subtotal: 4695.99,
tax_label: '',
tax_amount: 0,
length: '', // always in centimeters (cm)
width: '', // always in centimeters (cm)
height: '', // always in centimeters (cm)
weight: '', // always in grams (g)
split_pay_uid: '',
virtual: true,
tax_percent: 0
}
]
};
// Remove empty fields before sending
Object.keys(pay_request).forEach(k => { if (pay_request[k] === '') delete pay_request[k]; });
// Initiate payment — shows modal or triggers redirect depending on payment method
HPay.presentHPayPayForm(pay_request);
```
**Important — `order_items` field naming must match HolestPay template keys:**
- Use `name`, not `title`
- Use `posuid`, not `variantId`
- Use `qty`, not `quantity`
- `subtotal` is mandatory for each item line
- `posuid` can be any identifier from the merchant's system (SKU/variant/product/internal DB ID)
If source platform fields are Shopify-style, map them explicitly before sending:
- `variantId -> posuid`
- `title -> name`
- `quantity -> qty`
- top-level `items -> order_items`
The same order payload structure is used across all three markups/docs (Lovable prompt, FrontCore guide, Standard guide).
Address convention recommendation: use `address` for street name and `address2` for street number/additional address details.
---
### Step 4 — Docking (Embedded Payment Input)
Some payment methods support an embedded payment input directly in the page (e.g. card form inline). Check `pm.PayInputUrl` — if it is set, docking is supported for that method.
Call `HPay.setPaymentMethodDock()` **when the user selects a payment method or when any checkout field changes** (amount, currency, etc.).
```html
```
```css
#paymentMethodDock { background: #ffffff9e; }
```
```javascript
function updateDock() {
const selectedPm = /* pm object for selected payment method */;
if (selectedPm && selectedPm.PayInputUrl) {
HPay.setPaymentMethodDock(
pay_request.payment_method,
{
order_amount: pay_request.order_amount,
order_currency: pay_request.order_currency,
monthly_installments: null,
vault_token_uid: pay_request.vault_token_uid || null,
hpaylang: pay_request.hpaylang,
cof: pay_request.cof
},
document.getElementById('paymentMethodDock')
);
} else {
document.getElementById('paymentMethodDock').innerHTML = '';
}
}
```
---
### Step 5 — Handle Results
#### A — Event handler (iframe / modal methods)
Most payment methods return the result via this event, fired on the current page:
```javascript
document.addEventListener('onHPayResult', function(e) {
const r = e.hpay_response;
if (!r) return;
if (r.error && r.error.code) {
// Payment error — retry
HPay.presentHPayPayForm(pay_request);
return;
}
if (/PAID|RESERVED|SUCCESS|PAYING|OBLIGATED|AWAITING/i.test(r.payment_status)) {
// Payment completed or pending payment instructions (AWAITING/OBLIGATED are NOT failed)
// Clear cart for PAID/RESERVED and also for PAYING/AWAITING/OBLIGATED.
// Display HTML receipts in dedicated containers on your thank-you page:
// r.payment_html ? payment receipt HTML
// r.fiscal_html ? fiscal receipt HTML (if fiscal module active)
// r.integr_html ? integration module HTML
// r.shipping_html ? shipping label/receipt HTML (if shipping module active)
// r.transaction_user_info ? object with card brand, masked number, etc.
if (r.vault_token_uid) {//only if card save is on for some reason subscription or simply token save for faster checkout
// IMPORTANT: save this to your database linked to the user account — do NOT only store in browser
const saveCardData = {
vault_token_uid: r.vault_token_uid, //e.g. hJ0khUi67856765rtyrytrytry 12 up to 128 characters
vault_card_brand: r.vault_card_brand, // e.g. "MASTERCARD"
vault_card_umask: r.vault_card_umask, // e.g. "544358******4639"
vault_exp: r.vault_exp, // e.g. "12/26"
vault_scope: r.vault_scope, // e.g. can be Terminal ID fro payment method config or soemthinglike that
vault_onlyforuser: r.vault_onlyforuser, //subscription falsy | fast checkout 0
pay_method_uid: r.pay_method_uid //Uid of payment method from HPay.POS.payment[N].Uid
};
// POST saveCardData to your backend
}
// Redirect to thank-you page if needed:
// window.location.href = r.order_user_url;
}
// r.status ? order status (always present)
});
document.addEventListener('onHPayPanelClose', function(e) {
const r = e.hpay_response; // null if user closed without completing
const reason = String((r && r.reason) || '').toLowerCase();
// If pay button was locked during payment start, unlock it on close reasons below.
if (/^(user|timeout|cancel|error)$/.test(reason)) {
const payBtn = document.getElementById('do-pay');
if (payBtn) payBtn.disabled = false;
}
if (r && r.reason === '' && /* user wants retry */ false) {
setTimeout(() => HPay.presentHPayPayForm(pay_request), 300);
}
});
```
#### B — POST-back redirect (redirect methods)
Some payment methods (e.g. certain bank integrations) require a **full page redirect to the bank**. `HPay.presentHPayPayForm()` handles this automatically with a POST redirect. After the bank interaction, the user is returned to `order_user_url` via a POST-back.
On `order_user_url` (your thank-you/return page), read the result from the POST body:
```javascript
// Server-side (Node.js example)
// The POST body contains: hpay_forwarded_payment_response = JSON string
const result = JSON.parse(req.body.hpay_forwarded_payment_response);
// result is the same structure as e.hpay_response from onHPayResult
```
> Implement **both** the event handler (Step 5A) and the POST-back handler (Step 5B). Which one fires depends on the payment method the buyer chooses — you cannot predict it at build time.
#### C — Webhook topics, panel URL, and `notify_url` override
If server-to-server notifications are needed, configure panel setting:
- `I(P|S|F|I)N - Link za instant notifikacije - plaćanje/isporuka/fiskal/integracije`
- `I(P|S|F|I)N - Instant payment/shipping/fiscal/integation notification url`
If request payload includes `notify_url`, it overrides panel-level `I(P|S|F|I)N` URL for that request.
HPay appends query string parameter `topic` and sends POST JSON.
`topic` can be:
- `payresult` — same shape as `onHPayResult` / `https://apps.holest.com/holest-pay/response_sample.json`
- `orderupdate` — root includes `order_uid`, `status`, `vhash`; payload fields are the same as `payresult`; and has `order` property with HolestPay 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); validate `vhash` the same way as `payresult`
- `posconfig-updated` — contains POS object (`HPay.POS` format plus non-public fields), `environment`, and `checkstr = md5(${merchant_site_uid}${secret_token})`
Important payload mapping note:
- Request and response payloads are similar in shape; response usually contains request fields plus normalization and additional result fields.
- Hash field names are different by direction: request -> `verificationhash`, response -> `vhash`.
- `order` payload is a different model (same as `HPay.getOrder(...)` `ord` object), so do not map it as request/response.
- Example key differences:
- request/response `order_uid`, `order_amount`, `order_currency` -> order object `Uid`, `Amount`, `Currency`
- request/response `order_items`, `order_billing`, `order_shipping` -> order object `Data.items`, `Data.billing`, `Data.shipping`
- fiscal method specific data -> `order.FiscalData[fiscal_method_uid] = {...}` (module-specific payload)
- integration method data -> `order.Data[integr_method_uid] = {...}` (may exist or may be empty/missing)
- shipping method data -> `order.ShippingData[real_or_temp_shipping_code] = { method_uid: shipping_method_uid, ... }`
The same result may arrive from browser callback and webhook; use `vhash` (plus order/transaction IDs) to prevent duplicate processing.
#### D — Redirect POST-back parse robustness
For redirect methods, HPay can return result via:
```html
```
If `JSON.parse` fails on first try, normalize escaped payload and retry:
```javascript
function parseForwardedHPayResponse(raw) {
if (!raw) return null;
if (typeof raw === 'object') return raw;
try { return JSON.parse(raw); } catch (_) {}
const unescaped = String(raw).replace(/\\\\\"/g, '"').replace(/\\\\\\\\/g, '\\');
try { return JSON.parse(unescaped); } catch (_) {}
return null;
}
```
---
### Step 6 — Display HTML Receipts on Thank-You Page
On the thank-you page, always render data in this order:
1) `transaction_user_info` block first
2) `payment_html`, `fiscal_html`, `shipping_html`, `integr_html` blocks
For bank production approval, thank-you page must also show complete order summary for all outcomes (success, failed, awaiting payment):
- buyer/customer identity data
- billing and email data
- shipping data
- order number
- ordered products list with quantity, unit price, and line totals
- shipping cost
- payment method name and shipping method name
- grand total amount
If any field is missing in response, keep the block visible and show a placeholder message.
Create containers for transaction details and each receipt type:
```html
```
```javascript
function renderTransactionUserInfo(info, keyMap) {
const host = document.getElementById('hpay-transaction-user-info');
if (!host) return;
host.innerHTML = '';
if (!info || typeof info !== 'object' || !Object.keys(info).length) {
host.innerHTML = 'Transaction details are not available.
';
return;
}
const dl = document.createElement('dl');
Object.entries(info).forEach(([k, v]) => {
const dt = document.createElement('dt');
const dd = document.createElement('dd');
dt.textContent = keyMap[k] || k; // keys can be translated
dd.textContent = String(v ?? ''); // values must stay original
dl.appendChild(dt);
dl.appendChild(dd);
});
host.appendChild(dl);
}
renderTransactionUserInfo(r.transaction_user_info, {
'Order UID': 'Order UID',
'Payment Status': 'Payment Status',
'Transaction Time': 'Transaction Time',
'Amount in order currency': 'Amount in order currency',
'Amount in payment currency': 'Amount in payment currency',
'Bank Account': 'Bank Account',
'REF MOD97 PNB': 'REF MOD97 PNB',
'Purphose': 'Purpose'
});
const FALLBACK_HTML = 'Not available for this payment.
';
document.getElementById('hpay-receipt-payment').innerHTML = r.payment_html || FALLBACK_HTML;
document.getElementById('hpay-receipt-fiscal').innerHTML = r.fiscal_html || FALLBACK_HTML;
document.getElementById('hpay-receipt-shipping').innerHTML = r.shipping_html || FALLBACK_HTML;
document.getElementById('hpay-receipt-integr').innerHTML = r.integr_html || FALLBACK_HTML;
```
Example `transaction_user_info` payload:
```json
{
"transaction_user_info": {
"Order UID": "NIPI-1777290504591-8X6YD2",
"Payment Status": "AWAITING",
"Transaction Time": "2026-04-27 13:48:27.847Z",
"Amount in order currency": "1360.00 RSD",
"Amount in payment currency": "1360.00 RSD",
"Bank Account": "160-6000002552312-02",
"REF MOD97 PNB": "(97) 2726042713",
"Purphose": "Order npinipi17772905045918x6yd2"
}
}
```
Rule: keys may be translated for UI labels, but values should be displayed exactly as received. If any section is missing, render a visible placeholder instead of hiding the section.
For `AWAITING` and `OBLIGATED`, always render `payment_html` on the thank-you page. These are not failed statuses; they are commonly used for account/invoice payment instructions (bank transfer details).
Cart handling rule: clear cart for `PAID`, `RESERVED`, `PAYING`, `AWAITING`, and `OBLIGATED` (same cart behavior for all these statuses).
---
### Step 7 — Verify Response `vhash` on Server (Node.js)
Important naming:
- request to HPay (`pay_request` / `charge_request`) -> field `verificationhash` -> generate with `generatePOSRequestSignature(...)`
- response from HPay (browser callback / webhook) -> field `vhash` -> validate with `verifyHPayResponse(...)`
Always verify response `vhash` server-side before marking order as paid/fulfilled.
```javascript
const crypto = require('crypto');
const md5 = require('md5');
function generatePOSRequestSignature(merchant_site_uid, secretKey, payload) {
const amount = Number(payload.order_amount ?? 0).toFixed(8);
const src =
String(payload.transaction_uid ?? '').trim() + '|' +
String(payload.status ?? '').trim() + '|' +
String(payload.order_uid ?? '').trim() + '|' +
amount + '|' +
String(payload.order_currency ?? '').trim() + '|' +
String(payload.vault_token_uid ?? '').trim() + '|' +
String(payload.subscription_uid ?? '').trim() +
String(payload.rand ?? '').trim();
const srcMd5 = md5(src + merchant_site_uid);
return crypto.createHash('sha512').update(srcMd5 + secretKey).digest('hex').toLowerCase();
}
function verifyHPayResponse(result, merchant_site_uid, secretKey) {
if (!result || !result.vhash) return false;
if (!result.order_uid || !String(result.order_uid).trim()) return false;
const expected = generatePOSRequestSignature(merchant_site_uid, secretKey, {
transaction_uid: result.transaction_uid ?? '',
status: result.status ?? '',
order_uid: result.order_uid,
order_amount: result.order_amount ?? 0,
order_currency: result.order_currency ?? '',
vault_token_uid: result.vault_token_uid ?? '',
subscription_uid: result.subscription_uid ?? '',
rand: result.rand ?? ''
});
return expected === String(result.vhash).toLowerCase();
}
```
---
## Optional — Advanced: Backend Charges (Server-Side, Subscriptions / MIT)
> **Skip this section** unless the user explicitly asked for subscriptions or automatic recurring charges. This is used by a small fraction of integrations. The standard checkout flow (Steps 1–6—6) is completely independent of this.
### What it is
A backend charge lets your **server** silently charge a customer's saved card without any buyer interaction — no payment form, no redirect. This is used for:
- Recurring subscription billing (charge every month automatically)
- MIT (Merchant Initiated Transactions) — charging after service delivery
### Prerequisites for backend charge
- A payment method with `pm.SubsciptionsType` containing `"cof"` or `"mit"` must be active on the POS
- `pm.POps` must contain `"charge"` for that method
- A `vault_token_uid` saved from a previous successful checkout (from `r.vault_token_uid` in `onHPayResult`) must exist in your database for that customer
- **Merchant Site UID** and **Merchant Site Secret Key** (server-side only)
### How the token gets saved
During a normal checkout (Step 5), if card saving was enabled (`cof: 'optional'|'required'`), the `onHPayResult` response will contain a `vault_token_uid`. You **must save this to your database** linked to the user's account. The backend charge uses this token later.
### Backend charge request (Node.js)
```javascript
// SERVER-SIDE ONLY — never run this on the frontend
const crypto = require('crypto');
const md5 = require('md5');
// Signature function — required to authenticate the charge request
function generatePOSRequestSignature(merchant_site_uid, secretKey, request) {
const amt = parseFloat(request.order_amount || 0).toFixed(8);
let cstr = [request.transaction_uid, request.status, request.order_uid,
amt, request.order_currency, request.vault_token_uid,
request.subscription_uid].map(v => String(v || '').trim()).join('|');
cstr += String(request.rand || '').trim();
return crypto.createHash('sha512')
.update(md5(cstr + merchant_site_uid) + secretKey)
.digest('hex').toLowerCase();
}
// charge_request has same structure as pay_request but:
// - merchant_site_uid IS required (backend operation)
// - vault_token_uid IS required (the saved token from your DB)
// - order_user_url is NOT needed (no buyer to redirect)
// - cof is NOT needed
const charge_request = {
merchant_site_uid: MERCHANT_SITE_UID, // from config — never hardcode
order_uid: 'ORDER-20260425-001', // unique per charge attempt
order_amount: '15000',
order_currency: 'RSD',
payment_method: '179', // pm.HPaySiteMethodId
vault_token_uid: 'saved-token-from-db', // from your database for this customer
};
charge_request.verificationhash = generatePOSRequestSignature(
charge_request.merchant_site_uid, MERCHANT_SITE_SECRET, charge_request
);
const BASE_URL = 'https://sandbox.pay.holest.com'; // or https://pay.holest.com for production
const response = await fetch(BASE_URL + '/clientpay/charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(charge_request)
});
const result = await response.json();
// result has same structure as e.hpay_response from onHPayResult
// check result.payment_status for PAID|RESERVED|SUCCESS|PAYING|OBLIGATED|AWAITING
if (/PAID|PAYING|RESERVED|OBLIGATED|AWAITING/i.test(result.payment_status)) {
// charge succeeded
}
```
---
## Optional: Shipping Address Autocomplete (AdaptCheckout)
If the user uses HolestPay shipping modules, some methods provide address autocomplete/suggestion UI. Call `adaptCurrentShipping()` whenever the selected shipping method changes. Pass `null` to destroy the adapter when no shipping method is selected.
The selector strings map your existing checkout input fields to HolestPay's address model — **update them to match your actual input selectors**.
```javascript
let adapted_checkout_destroy = null;
let adapted_shipping_method_uid = null;
function adaptCurrentShipping(shipping_method_uid) {
try {
if (shipping_method_uid) {
if (shipping_method_uid === adapted_shipping_method_uid) return;
const smethod = HPay.POS.shipping.find(s => s.Uid == shipping_method_uid);
if (smethod && smethod.AdaptCheckout) {
adapted_checkout_destroy = smethod.AdaptCheckout({
billing: {
address: "#addressInput[name='address1']", // update selectors to match your inputs
address_num: "#addressLine2Input[name='address2']",
postcode: "#postCodeInput[name='postalCode']",
city: "#cityInput[name='city']",
municipality: "#provinceInput[name='stateOrProvince']",
phone: "#phoneInput[name='phone']",
country: "#countryCodeInput[name='countryCode']",
is_company: "#companyInput[name='company']",
company: "#companyInput[name='company']",
company_tax_id: "",
company_reg_id: ""
},
shipping: {
address: "#addressInput[name='shippingAddress.address1']",
address_num: "#addressLine2Input[name='shippingAddress.address2']",
postcode: "#postCodeInput[name='shippingAddress.postalCode']",
city: "#cityInput[name='shippingAddress.city']",
municipality: "#provinceInput[name='shippingAddress.stateOrProvince']",
phone: "#phoneInput[name='shippingAddress.phone']",
country: "#countryCodeInput[name='shippingAddress.countryCode']",
is_company: "#companyInput[name='shippingAddress.company']",
company: "#companyInput[name='shippingAddress.company']",
company_tax_id: "",
company_reg_id: ""
}
}) || null;
adapted_shipping_method_uid = shipping_method_uid;
}
} else {
if (adapted_checkout_destroy) {
adapted_checkout_destroy();
adapted_checkout_destroy = null;
adapted_shipping_method_uid = null;
}
}
} catch(ex) { console.error(ex); }
}
```
---
## No Backend Needed for Basic Checkout
If the site runs on Shopify, BigCommerce, or any platform where you cannot run custom server code, the FrontCore flow (Steps 1–6—6) is fully self-contained — no backend is required for standard checkout. Backend code is only needed if you implement:
- **Backend charges** (subscription / MIT auto-charge)
- **Admin operations** (refunds, captures, etc.)
- **Webhook verification** (verify response `vhash` from `notify_url` payload)
For webhooks and backend operations you also need the **Merchant Site Secret Key** (from HPay panel ? Site Settings).
---
## Checklist
- [ ] FrontCore `
```
In this URL, the `07f689c5-5bc8-44b6-a563-8facc6870fab` segment is the **Merchant Site UID**.
When this FrontCore script loads on an allowed origin, `HolestPayCheckout` and FrontCore signing (`hpay_frontend_script_core_sign`) become available.
After loading, the global `HolestPayCheckout` object and `HPayInit()` become available.
When the HPay script is loaded, it also exposes these globals on `window`:
- `window.presentHPayPayForm` — function
- `window.HPayIsSandbox` — environment flag variable
Add a check to warn about misconfiguration (add after page loads):
```javascript
setTimeout(function() {
if (typeof HolestPayCheckout === 'undefined') {
alert(
'HOLESTPAY FRONT-CORE SCRIPT IS NOT LOADED. ' +
'YOU PROBABLY FORGOT TO ADD CURRENT ORIGIN TO ' +
'"Frontend Script-Core Origins" PARAMETER UNDER SITE/POS SETTINGS!'
);
}
}, 5000);
```
---
## Step 1 — Initialize HPay and Fetch POS Configuration
`HPayInit()` returns a Promise resolving to `client` (= global `HPay`).
```javascript
HPayInit(
language // string — e.g. "en", "rs", "de" — also optional.
// If omitted, HPay will use the HTML attribute,
// or fall back to the fixed language configured in HPay panel POS settings.
// NOTE: merchant_site_uid and environment are NOT required for FrontCore —
// they are determined automatically by the POS-specific script URL.
).then(async client => {
// client.MerchantsiteUid — the Merchant Site UID read from the loaded POS config
const merchantSiteUid = client.MerchantsiteUid; // save for use in charge_request / admin ops
// client.POS.payment — array of payment method objects
// client.POS.shipping — array of shipping method objects
// client.POS.fiscal — array of fiscal method objects
// Filter by buyer country (optional but recommended):
let availablePayment = [];
let availableShipping = [];
const country = 'RS';
try {
availablePayment = await HPay.availablePaymentMethods(country, orderAmount, orderCurrency);
} catch(e) { console.error(e); }
try {
availableShipping = await HPay.availableShippingMethods(country, orderAmount, orderCurrency);
} catch(e) { console.error(e); }
// Build payment method selector from client.POS.payment:
client.POS.payment.forEach(pm => {
if (!pm.Hidden && (!availablePayment.length || availablePayment.find(m => m.Uid == pm.Uid))) {
// pm.HPaySiteMethodId — use as pay_request.payment_method value
// pm.Name — display name
// pm.SubsciptionsType — contains "cof" or "mit" if card saving is supported
// pm.POps — available backend operations e.g. "charge,refund"
// pm.PayInputUrl — set means docking (embedded form) is supported
// pm['Use IFRAME'] — if false, method uses redirect flow
}
});
// Build shipping method selector from client.POS.shipping:
if (client.POS.shipping) {
client.POS.shipping.forEach(sm => {
if (!sm.Hidden && (!availableShipping.length || availableShipping.find(m => m.Uid == sm.Uid))) {
// sm.HPaySiteMethodId — use as pay_request.shipping_method value
}
});
}
});
```
---
## Step 2 — Build the `pay_request` Object
```javascript
const pay_request = {
// NOTE: merchant_site_uid is NOT included in pay_request for FrontCore.
// The POS-specific script authenticates the request automatically.
hpaylang: "en", // optional UI language
order_uid: "20260315-621417", // required unique order ID
order_name: "#Order 204", // optional order label
order_amount: "15000", // required
order_currency: "RSD", // required ISO 4217
payment_method: "179", // required pm.HPaySiteMethodId
shipping_method: "45", // optional sm.HPaySiteMethodId
order_user_url: "https://yoursite.com/thanks", // optional redirect/thank-you URL
notify_url: "https://yoursite.com/webhook", // optional public webhook URL
order_data: { // optional custom payload -> stored as order.Data
customer_segment: "B2C",
source: "checkout-web"
},
cof: "optional", // optional: optional|required|none
vault_token_uid: "saved-token-uuid", // optional: saved token, or 1|true|new
// Optional billing object (template fields):
order_billing: {
email: "customer@example.com",
first_name: "TEST",
last_name: "TEST",
phone: "+38111111111",
is_company: 0,
company: "", // company legal name
company_tax_id: "", // company tax ID in merchant's country
company_reg_id: "", // company registration ID in merchant's country
address: "TEST", // street name
address2: "", // recommended for street number / address addition
city: "Beograd",
country: "RS",
state: "Beograd",
postcode: "11000",
lang: "sr_RS" // language from merchant platform/system
},
// Optional shipping object (template fields):
order_shipping: {
shippable: false,
is_cod: 1, // set to 1 when COD logic is used/allowed
first_name: "",
last_name: "",
phone: "",
company: "",
address: "", // street name
address2: "", // recommended for street number / address addition
city: "",
country: "",
state: "",
postcode: ""
},
// Optional items array (template fields):
order_items: [
{
posuid: 114, // merchant's own internal item ID (string or number)
type: "product",
name: "Sample product name",
sku: "000550",
qty: 1,
price: 4695.99,
subtotal: 4695.99,
refunded: 0,
refunded_qty: 0,
tax_label: "",
tax_amount: 0,
length: "",
width: "",
height: "",
weight: "",
split_pay_uid: "",
virtual: true,
tax_percent: 0
}
]
};
// NOTE: For initial FrontCore checkout, do not send signature hash.
// Remove empty fields:
Object.keys(pay_request).forEach(k => { if (pay_request[k] === '') delete pay_request[k]; });
```
**Important — `order_items` naming must use HolestPay keys (do not pass raw platform keys):**
- Use `name`, not `title`
- Use `posuid`, not `variantId`
- Use `qty`, not `quantity`
- `subtotal` is mandatory for each item line
- `posuid` can be any identifier from the merchant's system (SKU/variant/product/internal DB ID)
Common Shopify mapping before send:
- `variantId -> posuid`
- `title -> name`
- `quantity -> qty`
- top-level `items -> order_items`
The same order payload structure is used across all three markups/docs (Lovable prompt, FrontCore guide, Standard guide).
Address convention recommendation: use `address` for street name and `address2` for street number/additional address details.
### Full Template Field Catalog (Standalone)
- `Top-level pay_request` (FrontCore — `merchant_site_uid` is **not** included here):
- `hpaylang`, `order_uid`, `order_name`, `order_amount`, `order_currency`
- `payment_method`, `shipping_method`, `order_user_url`, `notify_url`
- `order_data`, `cof`, `vault_token_uid`
- `order_billing`:
- `email`, `first_name`, `last_name`, `phone`
- `is_company`, `company` (legal name), `company_tax_id` (tax ID), `company_reg_id` (registration ID)
- `address`, `address2`, `city`, `country`, `state`, `postcode`, `lang`
- `order_shipping`:
- `shippable`, `is_cod`
- `first_name`, `last_name`, `phone`, `company`
- `address`, `address2`, `city`, `country`, `state`, `postcode`
- `dispenser`, `dispenser_desc`, `dispenser_method_id` (locker/paket-shop flows)
- `order_items[]`:
- `posuid`, `type`, `name`, `sku`, `qty`, `price`, `subtotal`
- Required minimum per line for reliable processing: `posuid`, `name`, `qty`, `subtotal`
- `refunded`, `refunded_qty`, `tax_label`, `tax_amount`
- `length`, `width`, `height`, `weight`, `split_pay_uid`, `virtual`, `warehouse`
- `setPaymentMethodDock(...)` data:
- `order_amount`, `order_currency`, `monthly_installments`, `vault_token_uid`, `hpaylang`, `cof`
- Signature input/result fields used for backend charge and verification:
- `transaction_uid`, `status`, `order_uid`, `order_amount`, `order_currency`, `vault_token_uid`, `subscription_uid`, `rand`
- Compare generated signature with response `vhash` (not request `verificationhash`).
- `Extensibility`:
- `order_data` may contain any custom key/value pairs; it is persisted to `order.Data` in HPay order.
- `order_billing` and `order_shipping` may include additional custom fields besides the listed ones.
- Total serialized order payload should stay below **64 KB**.
---
## Step 3 — Present the Payment Form
```javascript
// Option A: Modal
HPay.presentHPayPayForm(pay_request);
// Option B: Docked (embedded) — only if pm.PayInputUrl is set
const dockElement = document.getElementById('paymentMethodDock');
HPay.setPaymentMethodDock(
pay_request.payment_method,
{
order_amount: pay_request.order_amount,
order_currency: pay_request.order_currency,
monthly_installments: null,
vault_token_uid: pay_request.vault_token_uid || null,
hpaylang: pay_request.hpaylang,
cof: pay_request.cof
},
dockElement
);
// Trigger payment on Pay button click:
HPay.presentHPayPayForm(pay_request);
```
Dock container CSS:
```css
#paymentMethodDock { background: #ffffff9e; }
```
---
## Step 4 — Handle the Result Events
Use these event handlers on your FrontCore page:
```javascript
document.addEventListener('onHPayResult', function(e) {
const r = e.hpay_response;
if (!r) return;
if (r.error && r.error.code) {
HPay.presentHPayPayForm(pay_request); // retry
return;
}
if (/PAID|RESERVED|SUCCESS|PAYING|OBLIGATED|AWAITING/i.test(r.payment_status)) {
// r.payment_html, r.fiscal_html, r.integr_html, r.shipping_html — HTML receipts
// r.transaction_user_info — object with card/transaction details
// r.order_user_url — redirect to thank-you page if needed
// IMPORTANT: AWAITING/OBLIGATED are NOT failed; show r.payment_html on thank-you page
// IMPORTANT: clear cart for PAID/RESERVED and also for PAYING/AWAITING/OBLIGATED.
// window.location.href = r.order_user_url;
if (r.vault_token_uid) {
// IMPORTANT: save to your database linked to the user account!
const saveCardData = {
vault_token_uid: r.vault_token_uid,
vault_card_brand: r.vault_card_brand,
vault_card_umask: r.vault_card_umask,
vault_exp: r.vault_exp,
vault_scope: r.vault_scope,
vault_onlyforuser: r.vault_onlyforuser,
pay_method_uid: r.pay_method_uid
};
// TODO: POST saveCardData to your backend and store in DB
}
}
// r.status — order status (always present)
});
document.addEventListener('onHPayPanelClose', function(e) {
const r = e.hpay_response; // null if closed without completing
const reason = String((r && r.reason) || '').toLowerCase();
// If pay button was locked during payment start, unlock it on close reasons below.
if (/^(user|timeout|cancel|error)$/.test(reason)) {
const payBtn = document.getElementById('do-pay');
if (payBtn) payBtn.disabled = false;
}
});
document.addEventListener('onHPayOrderOpExecuted', function(e) {
// admin operation result
});
```
### Thank-You Page Rendering Rule (Mandatory)
On the thank-you page, always render:
1) `transaction_user_info` block first,
2) `payment_html`, `fiscal_html`, `shipping_html`, `integr_html` blocks.
Bank production approval also requires a complete order summary on this page (for all outcomes: success, failed, awaiting payment):
- buyer/customer identity data
- billing and email data
- shipping data
- order number
- ordered products list with quantity, unit price, and line totals
- shipping cost
- payment method name and shipping method name
- grand total amount
If any field is missing in response, keep the block visible and show a placeholder message.
Keys in `transaction_user_info` can be translated for UI labels, but values should be shown exactly as received.
```javascript
function renderTransactionUserInfo(info, keyMap) {
const host = document.getElementById('hpay-transaction-user-info');
if (!host) return;
host.innerHTML = '';
if (!info || typeof info !== 'object' || !Object.keys(info).length) {
host.innerHTML = 'Transaction details are not available.
';
return;
}
const dl = document.createElement('dl');
Object.entries(info).forEach(([k, v]) => {
const dt = document.createElement('dt');
const dd = document.createElement('dd');
dt.textContent = keyMap[k] || k; // translate keys only
dd.textContent = String(v ?? ''); // do not alter values
dl.appendChild(dt);
dl.appendChild(dd);
});
host.appendChild(dl);
}
renderTransactionUserInfo(r.transaction_user_info, {
'Order UID': 'Order UID',
'Payment Status': 'Payment Status',
'Transaction Time': 'Transaction Time',
'Amount in order currency': 'Amount in order currency',
'Amount in payment currency': 'Amount in payment currency',
'Bank Account': 'Bank Account',
'REF MOD97 PNB': 'REF MOD97 PNB',
'Purphose': 'Purpose'
});
const FALLBACK_HTML = 'Not available for this payment.
';
document.getElementById('hpay-receipt-payment').innerHTML = r.payment_html || FALLBACK_HTML;
document.getElementById('hpay-receipt-fiscal').innerHTML = r.fiscal_html || FALLBACK_HTML;
document.getElementById('hpay-receipt-shipping').innerHTML = r.shipping_html || FALLBACK_HTML;
document.getElementById('hpay-receipt-integr').innerHTML = r.integr_html || FALLBACK_HTML;
```
Example payload:
```json
{
"transaction_user_info": {
"Order UID": "NIPI-1777290504591-8X6YD2",
"Payment Status": "AWAITING",
"Transaction Time": "2026-04-27 13:48:27.847Z",
"Amount in order currency": "1360.00 RSD",
"Amount in payment currency": "1360.00 RSD",
"Bank Account": "160-6000002552312-02",
"REF MOD97 PNB": "(97) 2726042713",
"Purphose": "Order npinipi17772905045918x6yd2"
}
}
```
---
## Result Verification — Webhook (Notify URL)
HPay calls webhook URL via HTTP POST JSON (server-to-server).
If `notify_url` is sent in request payload, it overrides panel setting:
- `I(P|S|F|I)N - Link za instant notifikacije - plaćanje/isporuka/fiskal/integracije`
- `I(P|S|F|I)N - Instant payment/shipping/fiscal/integation notification url`
HPay appends query string parameter `topic` and sends POST JSON.
Supported `topic` values:
- `payresult`: same payload shape as `onHPayResult` (`e.hpay_response`) / `https://apps.holest.com/holest-pay/response_sample.json`.
- `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.
- `posconfig-updated`: contains POS config (`HPay.POS` compatible + non-public fields), `environment`, and `checkstr = md5(${merchant_site_uid}${secret_token})`.
Important payload mapping note:
- Request and response payloads are similar in shape; response usually contains request fields plus normalization and additional result fields.
- Hash field names are different by direction: request -> `verificationhash`, response -> `vhash`.
- `order` payload is a different model (same as `HPay.getOrder(...)` `ord` object), so do not map it as request/response.
- Example key differences:
- request/response `order_uid`, `order_amount`, `order_currency` -> order object `Uid`, `Amount`, `Currency`
- request/response `order_items`, `order_billing`, `order_shipping` -> order object `Data.items`, `Data.billing`, `Data.shipping`
- fiscal method specific data -> `order.FiscalData[fiscal_method_uid] = {...}` (module-specific payload)
- integration method data -> `order.Data[integr_method_uid] = {...}` (may exist or may be empty/missing)
- shipping method data -> `order.ShippingData[real_or_temp_shipping_code] = { method_uid: shipping_method_uid, ... }`
The same payment result may arrive from browser callback and webhook. Use `vhash` (with order/transaction IDs) for idempotency to avoid duplicate processing.
### Redirect Method POST-back Note (Bank Redirect Flows)
For redirect methods, HPay may return result to `order_user_url` with auto-submitted form:
```html
```
Robust parse fallback example:
```javascript
function parseForwardedHPayResponse(raw) {
if (!raw) return null;
if (typeof raw === 'object') return raw;
try { return JSON.parse(raw); } catch (_) {}
const unescaped = String(raw).replace(/\\\\\"/g, '"').replace(/\\\\\\\\/g, '\\');
try { return JSON.parse(unescaped); } catch (_) {}
return null;
}
```
## Step 5 — Verify Response `vhash` on Server (Node.js)
Rule of thumb:
- request to HPay (`pay_request` / `charge_request`) -> field `verificationhash` -> generate with `generatePOSRequestSignature(...)`
- response from HPay (browser callback / webhook) -> field `vhash` -> validate with `verifyHPayResponse(...)`
```javascript
const crypto = require('crypto');
const md5 = require('md5');
function generatePOSRequestSignature(merchant_site_uid, secretKey, payload) {
const amount = Number(payload.order_amount ?? 0).toFixed(8);
const src =
String(payload.transaction_uid ?? '').trim() + '|' +
String(payload.status ?? '').trim() + '|' +
String(payload.order_uid ?? '').trim() + '|' +
amount + '|' +
String(payload.order_currency ?? '').trim() + '|' +
String(payload.vault_token_uid ?? '').trim() + '|' +
String(payload.subscription_uid ?? '').trim() +
String(payload.rand ?? '').trim();
const srcMd5 = md5(src + merchant_site_uid);
return crypto.createHash('sha512').update(srcMd5 + secretKey).digest('hex').toLowerCase();
}
function verifyHPayResponse(result, merchant_site_uid, secretKey) {
if (!result || !result.vhash) return false;
if (!result.order_uid || !String(result.order_uid).trim()) return false;
const expected = generatePOSRequestSignature(merchant_site_uid, secretKey, {
transaction_uid: result.transaction_uid ?? '',
status: result.status ?? '',
order_uid: result.order_uid,
order_amount: result.order_amount ?? 0,
order_currency: result.order_currency ?? '',
vault_token_uid: result.vault_token_uid ?? '',
subscription_uid: result.subscription_uid ?? '',
rand: result.rand ?? ''
});
return expected === String(result.vhash).toLowerCase();
}
```
---
## Backend Charge (Server Side — Still Requires Secret Key)
FrontCore does not expose the Secret Key on the frontend, but backend charges still need it on your server.
`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.
```javascript
// Node.js backend
const crypto = require('crypto');
const md5 = require('md5');
function generatePOSRequestSignature(merchant_site_uid, secretkey, request) {
const amt = parseFloat(request.order_amount || 0).toFixed(8);
let cstr = String(request.transaction_uid || '').trim() + '|';
cstr += String(request.status || '').trim() + '|';
cstr += String(request.order_uid || '').trim() + '|';
cstr += String(amt).trim() + '|';
cstr += String(request.order_currency || '').trim() + '|';
cstr += String(request.vault_token_uid || '').trim() + '|';
cstr += String(request.subscription_uid || '').trim();
cstr += String(request.rand || '').trim();
const md5hash = md5(cstr + merchant_site_uid);
return crypto.createHash('sha512').update(md5hash + secretkey).digest('hex').toLowerCase();
}
const charge_request = {
merchant_site_uid: "YOUR-MERCHANT-SITE-UID",
order_uid: "20260315-999999",
order_amount: "15000",
order_currency: "RSD",
payment_method: "179",
vault_token_uid: "saved-token-uuid", // required
order_data: { source: "subscription-renewal" }, // optional custom data -> order.Data
// same optional fields as pay_request (billing, shipping, items, notify_url, etc.)
// omit order_user_url: no user exists to be redirected in backend charge flow
// omit cof: not applicable for backend charge
};
charge_request.verificationhash = generatePOSRequestSignature(
charge_request.merchant_site_uid, SECRET_KEY, charge_request
);
const baseUrl = 'https://sandbox.pay.holest.com'; // or pay.holest.com
const response = await fetch(`${baseUrl}/s-blue/v1/clientpay/charge`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(charge_request)
});
const result = await response.json();
if (/PAID|PAYING|RESERVED/.test(result.payment_status)) {
// success
}
```
PHP equivalent:
```php
function generatePOSRequestSignature($merchant_site_uid, $secretkey, $request) {
$amt = number_format((float)($request['order_amount'] ?? 0), 8, '.', '');
$cstr = trim($request['transaction_uid'] ?? '') . '|';
$cstr .= trim($request['status'] ?? '') . '|';
$cstr .= trim($request['order_uid'] ?? '') . '|';
$cstr .= trim($amt) . '|';
$cstr .= trim($request['order_currency'] ?? '') . '|';
$cstr .= trim($request['vault_token_uid'] ?? '') . '|';
$cstr .= trim($request['subscription_uid'] ?? '');
$cstr .= trim($request['rand'] ?? '');
return hash('sha512', md5($cstr . $merchant_site_uid) . $secretkey);
}
$charge_request['verificationhash'] = generatePOSRequestSignature(
$merchant_site_uid, $secret_key, $charge_request
);
$body = json_encode($charge_request);
$ch = curl_init($base_url . '/s-blue/v1/clientpay/charge');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
// for simplicity only - do not disable in production:
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => false,
]);
$result = json_decode(curl_exec($ch), true);
curl_close($ch);
```
---
## Admin Operations (Requires Secret Key — Backend or Admin Page Only)
For admin pages, use the **normal HPay script URL**, not the FrontCore handler script:
```html
```
Do not use `.../frontend-script-core.js` for admin tooling.
```javascript
// Use the 4th parameter of HPayInit to enable admin mode
HPayInit(
merchant_site_uid,
language,
environment,
secret_key // 4th param — enables admin/backend operations
).then(client => client.loadHPayUI())
.then(() => {
HPay.getOrder(order_uid).then(ord => {
// `ord` format sample: https://apps.holest.com/holest-pay/hpay_order_sample.json
const toolbox = document.getElementById('admin_toolbox');
toolbox.innerHTML = '';
[HPay.POS.payment, HPay.POS.fiscal, HPay.POS.shipping].forEach(methods => {
(methods || []).forEach(pm => {
if (pm.initActions) {
if (typeof pm.initActions === 'string') eval('pm.initActions = ' + pm.initActions);
pm.initActions();
}
if (pm.orderActions) {
if (typeof pm.orderActions === 'string') eval('pm.orderActions = ' + pm.orderActions);
const actions = pm.orderActions(ord);
if (actions && actions.length) {
const h6 = document.createElement('h6');
h6.innerHTML = pm.SystemTitle;
toolbox.appendChild(h6);
actions.forEach(action => {
if (action.Run) {
const btn = document.createElement('button');
btn.innerHTML = action.Caption;
btn.addEventListener('click', e => { e.preventDefault(); action.Run(ord); });
toolbox.appendChild(btn);
} else if (action.actions) {
const p = document.createElement('p');
p.innerHTML = action.Caption;
toolbox.appendChild(p);
action.actions.forEach(sub => {
const sbtn = document.createElement('button');
sbtn.innerHTML = sub.Caption;
sbtn.addEventListener('click', e => { e.preventDefault(); sub.Run(ord); });
p.appendChild(sbtn);
});
}
});
}
}
});
});
});
});
```
---
## Difference Summary: FrontCore vs Standard
| Feature | FrontCore | Standard |
|---------|-----------|----------|
| Script source | Auto-served from HPay server (whitelisted origin) | Manual `
```
After loading, the global `HPay` object and the `HPayInit()` function become available.
When the HPay script is loaded, it also exposes these globals on `window`:
- `window.presentHPayPayForm` — function
- `window.HPayIsSandbox` — environment flag variable
---
## Step 1 — Initialize HPay and Fetch POS Configuration
Call `HPayInit()` once after the script loads. It returns a Promise that resolves with `client` (same reference as global `HPay`).
```javascript
HPayInit(
merchant_site_uid, // string — your Merchant Site UID
language, // string — e.g. "en", "rs", "de"
environment // string — "sandbox" or "production"
).then(async client => {
// client === HPay (global)
// client.POS.payment — array of payment method objects
// client.POS.shipping — array of shipping method objects
// client.POS.fiscal — array of fiscal method objects
// Optionally filter by buyer country:
let availablePayment = [];
let availableShipping = [];
const country = 'RS'; // ISO 2-letter code from billing or shipping address
try {
availablePayment = await HPay.availablePaymentMethods(country, orderAmount, orderCurrency);
} catch(e) { console.error(e); }
try {
availableShipping = await HPay.availableShippingMethods(country, orderAmount, orderCurrency);
} catch(e) { console.error(e); }
// Build your payment method








