# Embedded Checkout (/sk/online/integration/embedded_checkout)



Vložte zabezpečené polia pre kartu priamo do svojej pokladne. Formulár aj odosielacie tlačidlo máte vo svojej réžii; Tapaya zabezpečuje
validáciu karty, tokenizáciu a 3-D Secure. Vložené platby sú dostupné v ostrej prevádzke pre schválených obchodníkov a na testovanie
je k dispozícii sandbox. Tento sprievodca používa Tapaya.js **0.2.0** a Node.js SDK **0.3.0**.

<EmbeddedPaymentFlowDiagram />

<Callout title="Platobné metódy" type="info">
  Embedded Checkout podporuje karty, Apple Pay a Google Pay. Pre karty použite vlastné tlačidlo Pay a pre Apple Pay
  a Google Pay oficiálne tlačidlá peňaženiek. Pozrite si [Pridanie Apple Pay a Google Pay](#add-apple-pay-and-google-pay).
</Callout>

## Skôr ako začnete [#before-you-start]

### Inštalácia SDK [#install-the-sdk]

Jeden balík obsahuje serverového klienta, SDK pre prehliadač aj React hook:

<CodeBlockTabs defaultValue="npm" groupId="package-manager">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      Bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```sh
    npm install @tapayadot/checkout
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```sh
    pnpm add @tapayadot/checkout
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```sh
    bun add @tapayadot/checkout
    ```
  </CodeBlockTab>
</CodeBlockTabs>

| Import                       | Kde                                                                                                                     |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `@tapayadot/checkout`        | Iba na vašom serveri. Import v zostavení pre prehliadač vyhodí chybu, takže sa váš API kľúč nikdy nedostane na stránku. |
| `@tapayadot/checkout/client` | Vaša stránka pokladne.                                                                                                  |
| `@tapayadot/checkout/react`  | Vaša stránka pokladne, ak používate React.                                                                              |

### Konfigurácia servera [#configure-your-server]

Vložené platby nepotrebujú návratové URL. Serverové príklady nižšie sú vo verziách &#x2A;*HTTP (fetch)**, **Node.js SDK**
a **cURL**. Všetky volajú to isté API obchodníka a polia pre kartu v prehliadači nechávajú bez zmeny.

|                     | Node.js SDK                                                                                                                                                                | HTTP (fetch) a cURL                                                                                                        |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Kľúč**            | `TAPAYA_SECRET_KEY`, ktorý načíta `new Tapaya()`                                                                                                                           | `TAPAYA_CHECKOUT_API_KEY`                                                                                                  |
| **Prostredie**      | `TAPAYA_ENVIRONMENT=sandbox` alebo `production`. Bez tejto premennej kľúč `tapaya_live_` zvolí produkčné prostredie a pri ostatných kľúčoch sa predvolene použije sandbox. | `TAPAYA_CHECKOUT_API_URL`: `https://api.sandbox.tapaya.com` alebo `https://api.tapaya.com`                                 |
| **Režim platformy** | Odovzdávajte `merchantToken` objednávky v každom volaní alebo ho nastavte pomocou `new Tapaya(key, { merchantToken })`                                                     | Posielajte `X-Tapaya-Merchant-Token` v každej požiadavke. Pomocná funkcia pre fetch ho načítava z `TAPAYA_MERCHANT_TOKEN`. |

Checkout kľúč obchodníka nič ďalšie nepotrebuje. V režime platformy použite Secret key organizácie a backend,
ktorý obsluhuje viacerých obchodníkov, nechajte vyberať token podľa objednávky z autentifikovanej relácie zákazníka. Pozrite si [Autentifikácia
Checkoutu](/online/integration/api_integration#authentication). Client secrets sú špecifické pre každú platbu.

Dokončite [onboarding](/merchants/onboarding) s online platbami a overte, že je obchodník v
[nastaveniach Checkoutu](/online/integration/checkout_settings) v stave **Ready**.

### Povolenie Tapaya vo vašej CSP [#allow-tapaya-in-your-csp]

Loader sťahuje Tapaya.js **0.2.0** z `https://js.tapaya.com/checkout/0.2.0/checkout.js`; verzia skriptu pre prehliadač
je nezávislá od verzie Node.js SDK. Pridajte origin assetov Tapaya do svojej existujúcej Content Security
Policy:

```text
script-src https://js.tapaya.com
frame-src https://js.tapaya.com
```

Prehliadač dostane publishable key a client secret špecifický pre danú platbu. Váš tajný API kľúč obchodníka
zostáva na vašom serveri a údaje o karte aj skripty poskytovateľa platieb zostávajú vnútri rámca Tapaya. Na lokálne testovanie
hostujte Tapaya.js sami a jeho origin odovzdajte loaderu, napríklad
`loadTapaya(publishableKey, { apiOrigin, origin: 'http://localhost:4191' })`.

## Ako to funguje [#how-it-works]

1. Vaša stránka pripojí polia pre kartu od Tapaya a nasmeruje ich na endpoint na vašom serveri.
2. Pri načítaní stránky a pri platbe zákazníka volajú polia pre kartu tento endpoint. Ten vráti platbu objednávky
   ako `clientSecret`: prihlasovací údaj pre jednu platbu, pomocou ktorého polia pre kartu potvrdia platbu priamo
   v Tapaya. Kód vašej stránky ho nikdy nečíta.
3. Vaša stránka sleduje `outcome` platby. Keď sa platba dokončí, váš server ju overí a až potom
   objednávku vybavíte.

## Prijatie platby [#accept-a-payment]

<div className="fd-steps [&_h3]:fd-step">
  ### Získanie nastavení pre prehliadač [#get-the-browser-settings]

  Načítajte nastavenia pre prehliadač raz na serveri a pridajte ich do konfigurácie svojho frontendu.

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      Túto pomocnú funkciu majte na serveri. Kontroluje chyby HTTP, na požiadanie považuje chýbajúcu platbu objednávky za `null`
      a požiadavky automaticky neopakuje.

      ```js title="server/tapaya.js"
      const apiOrigin = new URL(process.env.TAPAYA_CHECKOUT_API_URL).origin;
      const apiKey = process.env.TAPAYA_CHECKOUT_API_KEY;
      const merchantToken = process.env.TAPAYA_MERCHANT_TOKEN;

      export async function paymentRequest(path, { method = 'GET', body, key, allowMissing = false } = {}) {
      	const response = await fetch(new URL(path, apiOrigin), {
      		method,
      		headers: {
      			Authorization: `Bearer ${apiKey}`,
      			...(merchantToken ? { 'X-Tapaya-Merchant-Token': merchantToken } : {}),
      			...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
      			...(key ? { 'Idempotency-Key': key } : {}),
      		},
      		...(body === undefined ? {} : { body: JSON.stringify(body) }),
      		redirect: 'error',
      		cache: 'no-store',
      		signal: AbortSignal.timeout(35000),
      	});
      	if (allowMissing && response.status === 404) return null;
      	if (!response.ok) throw new Error(`Payment request failed (${response.status})`);
      	return response.json();
      }

      const { publishableKey } = await paymentRequest('/merchant/payments/configuration');
      export const config = { publishableKey, apiOrigin };
      ```

      API vracia `publishableKey` a `environment`. Hodnotu `apiOrigin` nastavte podľa URL API, ktoré ste volali; nie je to pole
      odpovede s konfiguráciou.
    </TabsContent>

    <TabsContent value="node">
      ```ts title="server/config.ts"
      import { Tapaya } from '@tapayadot/checkout'

      const tapaya = new Tapaya()
      const { publishableKey, apiOrigin } = await tapaya.checkout.configuration()
      // Sandbox: publishableKey: 'pk_test_…', apiOrigin: 'https://api.sandbox.tapaya.com'
      // Production: publishableKey: 'pk_live_…', apiOrigin: 'https://api.tapaya.com'
      ```
    </TabsContent>

    <TabsContent value="curl">
      Spustite tento príkaz na svojom serveri alebo v dôveryhodnom termináli. V režime platformy pridajte
      `--header "X-Tapaya-Merchant-Token: $TAPAYA_MERCHANT_TOKEN"` ku každému príkazu cURL v tomto sprievodcovi.

      ```bash
      curl "$TAPAYA_CHECKOUT_API_URL/merchant/payments/configuration" \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Ako konfiguráciu pre prehliadač použite vrátený `publishableKey` a origin vašej `TAPAYA_CHECKOUT_API_URL`.
    </TabsContent>
  </Tabs>

  Obe hodnoty môžete bezpečne vložiť do stránky. Publishable key má predponu `pk_test_` pri testovacích kľúčoch brány a `pk_live_`
  pri ostrých kľúčoch brány. Loaderu vždy odovzdávajte `apiOrigin` vrátený vaším serverom, aby prehliadač aj server
  zostali v tom istom prostredí.

  ### Pridanie polí pre kartu [#add-the-card-fields]

  Pridajte kontajner do svojho existujúceho formulára pokladne:

  ```html title="checkout.html"
  <form id="checkout">
  	<!-- Your other checkout inputs go here. -->
  	<div id="card-fields"></div>
  	<button type="submit" disabled>Pay</button>
  	<p id="message" role="status"></p>
  </form>
  ```

  Načítajte Tapaya a pripojte polia. `prepare` je endpoint, ktorý pridáte v ďalšom kroku.

  ```ts title="checkout.ts"
  import { loadTapaya } from '@tapayadot/checkout/client'
  import { config } from './config' // { publishableKey, apiOrigin } from your server

  const form = document.querySelector<HTMLFormElement>('#checkout')!
  const button = form.querySelector<HTMLButtonElement>('button')!
  const message = document.querySelector<HTMLElement>('#message')!
  const tapaya = await loadTapaya(config.publishableKey, { apiOrigin: config.apiOrigin })
  const payment = tapaya.createPayment({
  	prepare: '/api/payments',
  	onChange: ({ canSubmit, outcome }) => {
  		button.disabled = !canSubmit
  		if (outcome === 'completed') location.assign('/checkout/confirmation')
  		if (outcome === 'ended') message.textContent = 'This payment cannot be completed. Start a new checkout.'
  	},
  })
  await payment.mount('#card-fields')
  ```

  Po pripojení polia zavolajú `prepare`, aby nadviazali na existujúcu platbu objednávky. Opätovné načítanie stránky počas platby
  nikdy nevedie k ďalšiemu zaťaženiu karty: stránka namiesto toho zobrazí aktuálny stav platby.

  ### Vrátenie platby objednávky [#return-the-orders-payment]

  Pripojte tento handler k `POST /api/payments`. `getCheckoutOrder()` je funkcia vašej vlastnej aplikácie: autentifikuje
  zákazníka, skontroluje jeho prístup k objednávke a vráti jej uložené `id`, `amount`, `currency` a `status`. Na túto routu
  uplatnite svoju obvyklú ochranu proti CSRF.

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      Skôr ako vytvoríte pokus, načítajte existujúcu platbu. Nevyriešenú platbu použite znova a ďalší pokus vytvorte
      iba po zamietnutí alebo zrušení s `retryAllowed: true`.

      ```js title="server/payments.js"
      import { paymentRequest } from './tapaya.js';
      import { getCheckoutOrder } from './orders.js';

      export async function findPayment(order) {
      	const query = new URLSearchParams({ merchantOrderId: order.id });
      	const payment = await paymentRequest(`/merchant/payments?${query}`, { allowMissing: true });
      	if (payment && (payment.merchantOrderId !== order.id ||
      		payment.amount !== order.amount || payment.currency !== order.currency)) {
      		throw new Error('Payment does not match the order');
      	}
      	return payment;
      }

      export function browserStatus(payment) {
      	if (payment.status === 'successful') return 'completed';
      	if (['declined', 'cancelled'].includes(payment.status) && !payment.retryAllowed) return 'ended';
      	return 'open';
      }

      export async function POST(request) {
      	const order = await getCheckoutOrder(request);
      	const payable = order.status === 'pending_payment';
      	let payment = await findPayment(order);
      	if (payable && (!payment ||
      		(['declined', 'cancelled'].includes(payment.status) && payment.retryAllowed))) {
      		// Derive the same key on retries, including after a lost creation response.
      		let key = `${order.id}:${payment ? `after:${payment.id}` : 'initial'}`;
      		if (/[^\x21-\x7e]/.test(key)) {
      			const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(key));
      			key = `prepare:${Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('')}`;
      		}
      		payment = await paymentRequest('/merchant/payments', {
      			method: 'POST', key,
      			body: { merchantOrderId: order.id, amount: order.amount, currency: order.currency },
      		});
      		if (payment.merchantOrderId !== order.id || payment.amount !== order.amount || payment.currency !== order.currency) {
      			throw new Error('Payment does not match the order');
      		}
      	}
      	if (!payment) return Response.json({ error: 'Order cannot accept payment' }, { status: 409 });
      	return Response.json(
      		{ clientSecret: payable ? payment.clientSecret : null, status: browserStatus(payment) },
      		{ headers: { 'Cache-Control': 'no-store' } },
      	);
      }
      ```

      Používajte kód meny veľkými písmenami a uložené ID objednávky s dĺžkou najviac 200 znakov. Po začatí platby
      údaje objednávky nemeňte. Pri vypršaní časového limitu znova načítajte platbu objednávky a až potom rozhodnite, či je potrebný nový pokus.
      Nikdy negenerujte nový kľúč len preto, že sa stratila odpoveď.
    </TabsContent>

    <TabsContent value="node">
      ```ts title="server/payments.ts"
      import { Tapaya } from '@tapayadot/checkout'
      import type { PreparedPayment } from '@tapayadot/checkout'
      import { getCheckoutOrder } from './orders'

      const tapaya = new Tapaya()

      export async function POST(request: Request) {
      	const order = await getCheckoutOrder(request)
      	const details = { merchantOrderId: order.id, amount: order.amount, currency: order.currency }
      	// Only an order awaiting payment gets a payable session. Other orders report their payment's outcome.
      	const payable = order.status === 'pending_payment'
      	const session = payable
      		? await tapaya.checkout.sessions.prepare(details)
      		: await tapaya.checkout.sessions.verify(details)
      	if (!session) return Response.json({ error: 'Order cannot accept payment' }, { status: 409 })
      	// The card fields read both values. Your page code never handles them.
      	return Response.json(
      		{ clientSecret: payable ? session.clientSecret : null, status: session.status } satisfies PreparedPayment,
      		{ headers: { 'Cache-Control': 'no-store' } },
      	)
      }
      ```

      `prepare()` vracia platbu objednávky. Vytvorí prvý pokus, nevyriešený pokus vráti bez zmeny a po zamietnutí
      vytvorí ďalší pokus. Idempotency keys si odvodzuje sám, takže jeho volanie pri každom načítaní stránky a
      odoslaní nikdy nevedie k druhému zaťaženiu karty. Keď sa platba začne, zafixujte celkovú sumu: `prepare()` vyhodí
      `TapayaOrderMismatchError`, ak suma alebo mena objednávky už nezodpovedajú jej platbe.
    </TabsContent>

    <TabsContent value="curl">
      Najprv vyhľadajte platbu objednávky. Tieto príklady používajú objednávku na 121,00 EUR uloženú na vašom serveri. `404` znamená,
      že sa nenašla žiadna platba; iné zlyhania HTTP neoprávňujú na vytvorenie ďalšieho pokusu.

      ```bash
      curl --get "$TAPAYA_CHECKOUT_API_URL/merchant/payments" \
      	--data-urlencode 'merchantOrderId=order-1001' \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Ak žiadna platba neexistuje a objednávka môže platbu stále prijať, vytvorte prvý pokus:

      ```bash
      curl --request POST "$TAPAYA_CHECKOUT_API_URL/merchant/payments" \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY" \
      	--header 'Content-Type: application/json' \
      	--header 'Idempotency-Key: order-1001:initial' \
      	--data '{"merchantOrderId":"order-1001","amount":12100,"currency":"EUR"}'
      ```

      Existujúcu nevyriešenú platbu použite znova. Po `declined` alebo `cancelled` vytvorte ďalší pokus iba vtedy, keď je
      `retryAllowed` rovné `true`. Pre tento pokus zopakujte POST s rovnakými údajmi objednávky a kľúčom
      `order-1001:after:<previous-payment-id>`, kde zástupný text nahradíte vráteným `id`. Tento kľúč používajte aj pri
      opakovaní požiadavky. Po vypršaní časového limitu objednávku znova vyhľadajte; nepredpokladajte, že POST zlyhal.

      cURL ukazuje volania API. Handler `POST /api/payments` vašej aplikácie, chránený autentifikáciou, musí poliam pre kartu vracať
      `{ clientSecret, status }`. Stav z API preveďte podľa tabuľky nižšie a pošlite
      `clientSecret: null`, keď vaša objednávka už platbu neprijíma. Tieto odpovede nikdy nevkladajte do zdieľaných logov terminálu.
    </TabsContent>
  </Tabs>

  REST API vracia stavy platby. Node SDK ich prevádza na `status`, ktorý polia pre kartu očakávajú; integrácie cez fetch a cURL
  používajú rovnaký prevod vo svojom vlastnom endpointe prepare:

  | `status` v API                                                                            | `status` endpointu prepare |
  | ----------------------------------------------------------------------------------------- | -------------------------- |
  | `successful`                                                                              | `completed`                |
  | `declined` alebo `cancelled` s `retryAllowed: false`                                      | `ended`                    |
  | `ready`, `pending`, `action_required` alebo zamietnutie či zrušenie, ktoré možno opakovať | `open`                     |

  <Accordions>
    <Accordion title="Pravidlá pre váš endpoint prepare">
      * **Sumu berte z uloženej objednávky,** v celých najmenších jednotkách meny, nikdy z prehliadača. Keď sa platba začne, zafixujte
        celkovú sumu.
      * **Odpovedajte aj pri dokončených objednávkach.** Objednávka, ktorá je už zaplatená alebo ktorú dokončila vaša plánovaná úloha, stále vracia
        `status` svojej platby, takže opätovné načítanie zobrazí výsledok namiesto chyby. Nikdy nedostane client secret,
        takže zákazník nemôže zaplatiť objednávku, za ktorú už platbu neprijímate.
      * **Jedna úspešná platba na objednávku.** Každé zamietnutie nastaví `retryAllowed`, takže zákazník môže skúsiť inú kartu pre
        tú istú objednávku. Limit je zdieľaný s [hostovaným Checkoutom](/online/integration/api_integration#one-payment-per-order).
      * **Chráňte client secret.** Posielajte ho iba zákazníkovi, ktorý je oprávnený túto objednávku zaplatiť, a nikdy ho nelogujte.
      * **Počítajte s holou požiadavkou POST.** Polia pre kartu posielajú POST na `prepare` na vašom vlastnom origine s cookies a bez tela. Odmietajú
        presmerovania a nikdy požiadavku automaticky neopakujú. Pre vlastné hlavičky, CSRF tokeny alebo telo požiadavky odovzdajte namiesto URL
        funkciu, ktorá vracia rovnaký JSON: `prepare: async () => (await fetch(…)).json()`.
    </Accordion>
  </Accordions>

  ### Odoslanie z vášho handlera [#submit-from-your-handler]

  Pridajte toto do toho istého skriptu. Váš handler zvaliduje formulár a zavolá `submit()`:

  ```ts title="checkout.ts"
  form.addEventListener('submit', async (event) => {
  	event.preventDefault()
  	if (!form.reportValidity() || !payment.snapshot.canSubmit) return
  	await payment.submit().catch(() => {
  		// The card fields show the reason. The shopper can try again when canSubmit allows it.
  	})
  })
  ```

  `submit()` zavolá pre platbu `prepare`, potom zvaliduje kartu, vykoná 3-D Secure a potvrdí platbu
  v Tapaya.

  ### Zobrazenie výsledku [#show-the-outcome]

  `onChange` dostane `snapshot` platby pri každej jeho zmene, vrátane načítania stránky s platbou, ktorá
  je už dokončená. Riaďte sa hodnotou `snapshot.outcome`:

  | `outcome`   | Čo zobraziť                                                                                                                                    |
  | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
  | `open`      | Tlačidlo Pay, aktívne, kým je `canSubmit` true. Po zamietnutí zostáva `open` a ďalšie `submit()` dostane nový pokus.                           |
  | `pending`   | Indikátor priebehu. Výsledok zatiaľ nie je známy, takže nemožno nič zaplatiť znova. SDK kontroluje stav automaticky, kým je stránka viditeľná. |
  | `completed` | Vašu potvrdzovaciu stránku. Tam váš server objednávku overí.                                                                                   |
  | `ended`     | Novú pokladňu. Pre túto objednávku už nie je možný žiadny ďalší pokus.                                                                         |

  Keď sa zobrazenie vašej pokladne odpája (unmount), zavolajte `payment.destroy()`. V Reacte použite `usePayment()` z
  `@tapayadot/checkout/react`. Prijíma rovnaké možnosti a navyše `publishableKey` a `apiOrigin` a vracia
  `fieldRef` pre kontajner, aktuálny `snapshot` a `submit()`.

  ### Overenie pred vybavením [#verify-before-fulfilling]

  Výsledok v prehliadači slúži iba na zobrazenie. Na potvrdzovacej stránke načítajte platbu objednávky na svojom serveri a objednávku vybavte
  iba vtedy, keď je platba dokončená:

  <Tabs defaultValue="fetch" groupId="checkout-server" className="border-0 bg-transparent">
    <TabsList>
      <TabsTrigger value="fetch">
        HTTP (fetch)
      </TabsTrigger>

      <TabsTrigger value="node">
        Node.js SDK
      </TabsTrigger>

      <TabsTrigger value="curl">
        cURL
      </TabsTrigger>
    </TabsList>

    <TabsContent value="fetch">
      ```js title="server/confirmation.js"
      import { findPayment } from './payments.js';

      const order = await getCheckoutOrder(request);
      const payment = await findPayment(order); // Checks order ID, amount, and currency.
      if (payment?.status === 'successful') await markOrderPaid(order.id, payment.id);
      ```
    </TabsContent>

    <TabsContent value="node">
      ```ts title="server/confirmation.ts"
      const order = await getCheckoutOrder(request)
      const session = await tapaya.checkout.sessions.verify({
      	merchantOrderId: order.id,
      	amount: order.amount,
      	currency: order.currency,
      })
      if (session?.status === 'completed') await markOrderPaid(order.id, session.id)
      ```

      `verify()` nikdy nevytvára platbu a pred prvým pokusom vracia `null`. Vyhodí
      `TapayaOrderMismatchError`, keď sa suma alebo mena líšia od vašej objednávky.
    </TabsContent>

    <TabsContent value="curl">
      Aktuálnu platbu objednávky načítajte potom, ako prehliadač skončí, alebo keď kontrolujete neistý výsledok:

      ```bash
      curl --get "$TAPAYA_CHECKOUT_API_URL/merchant/payments" \
      	--data-urlencode 'merchantOrderId=order-1001' \
      	--fail-with-body --silent --show-error --max-time 35 \
      	--header "Authorization: Bearer $TAPAYA_CHECKOUT_API_KEY"
      ```

      Pred vybavením ukážkovej objednávky overte `merchantOrderId: "order-1001"`, `amount: 12100`, `currency: "EUR"`
      a `status: "successful"`. Vrátené `id` platby uložte k svojej objednávke. Samotná odpoveď HTTP 200 platbu nedokazuje.
    </TabsContent>
  </Tabs>

  Zabezpečte, aby `markOrderPaid()` zaznamenala platbu iba raz, aj keď sa volá opakovane, a vybavenie spúšťajte z tohto
  uloženého stavu.

  <Callout title="Odsúhlasenie objednávok, ktoré zákazník opustil" type="warn">
    Zákazník môže zatvoriť kartu prehliadača skôr, ako sa otvorí potvrdzovacia stránka. Spustite plánovanú úlohu, ktorá pre nezaplatené objednávky volá `verify()` alebo
    číta `GET /merchant/payments?merchantOrderId=...`, s rovnakými kontrolami. Zamietnuté a
    zrušené pokusy umožňujú nový pokus, takže nezaplatená objednávka zostáva `open`: uvoľnite ju podľa vlastného harmonogramu, a
    to iba vtedy, keď jej `paymentStatus` nie je `pending`. Neúspešná požiadavka alebo vypršanie časového limitu nie je zamietnutie, preto objednávku nechajte
    rezervovanú, kým platba nemá známy výsledok.
  </Callout>
</div>

## Pridanie Apple Pay a Google Pay [#add-apple-pay-and-google-pay]

Peňaženky sa zobrazujú vnútri rámca Tapaya vedľa polí pre kartu. Aby sa zobrazili, overte, že:

* Peňaženky sú povolené v [nastaveniach Checkoutu](/online/integration/checkout_settings#wallets-and-appearance).
* **Apple Pay:** obchod má [aktívnu doménu obchodu](/online/integration/checkout_settings#register-apple-pay-shop-domains).
* **Google Pay v produkčnom prostredí:** je nakonfigurované Google Pay merchant ID.
* Zariadenie, prehliadač a peňaženka zákazníka danú metódu podporujú.

Odovzdajte `wallets` s celkovou sumou pokladne v najmenších jednotkách meny. Tapaya zobrazí dostupné metódy a nepodporované alebo
vypnuté peňaženky zostanú skryté. Použite tento kód namiesto príkladu `createPayment()` iba pre karty:

```ts title="checkout-wallets.ts"
const payment = tapaya.createPayment({
	prepare: '/api/payments',
	wallets: { amount: order.totalInMinorUnits, currency: order.currency },
	onWalletClick: async () => {
		if (!form.reportValidity()) throw new Error('Complete the checkout details.')
		// Validate the cart and reserve the order on your server.
		const response = await fetch('/api/payments', { method: 'POST' })
		if (!response.ok) throw new Error('Could not prepare the payment.')
		return response.json() // { clientSecret, status }
	},
	onChange: (snapshot) => {
		button.hidden = snapshot.paymentMethod !== 'card'
		button.disabled = !snapshot.canSubmit
		if (snapshot.outcome === 'completed') location.assign('/checkout/confirmation')
	},
})
await payment.mount('#card-fields')
```

Celková suma zobrazená v prehliadači musí pochádzať z vášho servera, ktorý ju pri príprave platby aj tak sám
vypočíta a overí.

### Kliknutia na peňaženku [#wallet-clicks]

Oficiálne tlačidlo peňaženky otvorí natívne platobné okno peňaženky na základe kliknutia zákazníka. `onWalletClick` zvaliduje vašu
pokladňu a rezervuje jej platbu vo chvíli, keď sa na toto tlačidlo klikne, nikdy pri pripojení ani pri výbere metódy. Bez tohto
callbacku používajú peňaženky `prepare` alebo `clientSecret`. Ak objednávku rezervujete až pri kliknutí, zabezpečte, aby callback
`prepare` volaný pri pripojení nadviazal na existujúcu objednávku bez toho, aby vytvoril novú.

Vlastné tlačidlo Pay nechajte viditeľné iba vtedy, keď je `snapshot.paymentMethod` rovné `card`; peňaženky namiesto
`payment.submit()` používajú svoje značkové tlačidlá. Výsledky platieb peňaženkou prechádzajú rovnakým postupom overenia a obnovy ako karty. Zatvorenie
platobného okna peňaženky ponechá pokladňu dostupnú; neistá platba zostane čakajúca a zablokuje ďalšie zaťaženie karty.

### Aktualizácia celkovej sumy [#update-the-total]

Kým nie sú vyplnené povinné údaje pokladne, odovzdávajte `wallets: { amount, currency, disabled: true }`. Celkovú sumu
aktualizujte alebo tlačidlá povoľte bez opätovného pripojenia:

```ts
payment.update({ wallets: { amount: 2450, currency: 'EUR', disabled: false } })
```

Odovzdaním `wallets: null` peňaženky skryjete, kým nebude celková suma pripravená. Tapaya odmietne potvrdenie, ak sa pripravená platba
líši od celkovej sumy peňaženky. React hook `usePayment()` tieto možnosti prijíma a celkovú sumu aktualizuje, keď sa
`wallets` zmení.

### Vlastný výber metódy [#your-own-method-selector]

Ak výber metódy spravuje vaša stránka, odovzdajte `paymentMethod: 'card'`, `'apple_pay'` alebo `'google_pay'`, aby sa vykreslila
iba táto metóda. Nedostupná vybraná peňaženka zobrazí popisok `walletUnavailable` a nahlási
`snapshot.error.code: 'wallet_unavailable'`, ak možno bezpečne začať inú platbu; ponúknite vo svojom
výbere inú metódu. Výber zamknite, kým je `snapshot.busy` true alebo je platba v stave `pending` či `action_required`.

### Rámce a CSP [#frames-and-csp]

SDK pridáva k svojmu rámcu `allow="payment"`. Ak je váš obchod vnútri iného rámca, musí platby povoliť aj tento vonkajší
rámec. Apple Pay naprieč originmi vyžaduje Safari 17 alebo novší. Skripty poskytovateľov peňaženiek zostávajú vnútri rámca Tapaya, takže
vaša stránka nepotrebuje vo svojej CSP žiadne ďalšie originy poskytovateľov.

## Spracovanie vypršaného prístupu prehliadača [#handle-expired-browser-access]

Client secret vyprší po 24 hodinách a `clientSecret` je potom `null`. Platba, ktorá nebola nikdy odoslaná, sa
potom zruší a ďalšie `prepare()` začne pre tú istú objednávku nový pokus. Čakajúca platba zostáva v prehliadači `pending`
a SDK naďalej volá váš endpoint, kým váš server nenahlási jej výsledok ako `status`. Čítanie na serveri
stále funguje, takže ju vaša plánovaná úloha v každom prípade dokončí.

## Diagnostika chýb servera [#diagnose-server-errors]

Node SDK pri odmietnutých požiadavkách na API vyhadzuje `PaymentsApiError`. Na svojom serveri skontrolujte jeho `status`, `code`, `message`,
`fieldErrors` a `requestId`. Prihlasovacie údaje a client secrets nezapisujte do logov a zákazníkovi zobrazujte vlastné
texty.

## Prispôsobenie vašej značke [#match-your-branding]

Polia pre kartu používajú ako farbu zvýraznenia pri fokuse (`colorFocus`) výslednú farbu pokladne obchodníka z [nastavení
Checkoutu](/online/integration/checkout_settings#wallets-and-appearance).
Hodnoty, ktoré odovzdáte v `appearance.variables`, ju prepíšu, a posielajú sa iba premenné, ktoré nastavíte.
