# Aplikácia Tapaya Terminal (/sk/in-store/integration/card_terminal)



Prijímajte bezkontaktné platby kartou zo svojej aplikácie pre Android tak, že prácu s kartou odovzdáte pluginovej aplikácii **Tapaya Terminal**.
Predaj zostáva pod kontrolou vašej aplikácie; plugin zabezpečí priloženie karty cez NFC, zadanie PIN a spracovanie
karty a výsledok sa potom vráti cez prúd udalostí `Accept.payments.pay()`.

Túto integráciu použite, ak chcete Tap to Pay bez toho, aby ste do vlastnej aplikácie vkladali spracovateľa platieb. Ak má vaša
platforma namiesto toho povolené vstavané Tap to Pay, prečítajte si sprievodcu *Embedded Tap to Pay*.

## Možnosti integrácie [#integration-options]

| Možnosť                     | Vhodné pre                                    | Doručenie výsledku                                                                                                             |
| :-------------------------- | :-------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
| **Accept SDK** (odporúčané) | Natívne aplikácie pre Android                 | `Accept.payments.pay()` vysiela `Flow<PaymentEvent>`: vytvorenie, spustenie aj konečný výsledok z terminálu sa spracujú za vás |
| **Deeplink**                | WebView, multiplatformové frameworky, QR kódy | Platbu vytvoríte cez Tapaya API a priebežne sa dopytujete na jej stav; plugin sa otvorí cez deeplink                           |

Najprv je popísaná cesta cez SDK; kontrakt deeplinku nájdete v časti [Integrácia bez SDK](#integrate-without-the-sdk).

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

1. Vaša aplikácia spustí platbu volaním `Accept.payments.pay(amount, currency)`.
2. SDK zaregistruje platbu v backende Tapaya a spustí plugin Tapaya Terminal na tom istom zariadení.
3. Zákazník priloží kartu alebo telefón k obrazovke Tapaya Terminal.
4. Tapaya Terminal dokončí spracovanie platby s backendom Tapaya.
5. SDK sleduje platbu, kým nedosiahne konečný stav, a vyšle `PaymentEvent.Result` s
   `PayResult`. Ak platbu vôbec nemožno vytvoriť, vyšle namiesto toho `PaymentEvent.CreationFailed`, zvyčajne
   do jednej sekundy.

## Predpoklady [#prerequisites]

* **Plugin Tapaya Terminal** nainštalovaný v zariadení a **aktivovaný** pre vášho obchodníka (pozrite
  [Inštalácia a aktivácia terminálu](#install-and-activate-the-terminal)).
* Zariadenie s NFC so systémom Android 11 (API 30) alebo novším.
* Accept SDK [nainštalované a inicializované](../integration/sdk_integration#initialization), s
  [prihláseným](../integration/sdk_integration#step-2-authenticate) obchodníkom.
* Oprávnenie na polohu (`ACCESS_FINE_LOCATION`) udelené vašej aplikácii počas behu.

## Inštalácia a aktivácia terminálu [#install-and-activate-the-terminal]

Aktiváciu vykonajte raz pre každé zariadenie. Aktivácia prepojí plugin Tapaya Terminal s tým istým účtom obchodníka, pod
ktorým je prihlásená vaša aplikácia.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    if (!Accept.plugin.isInstalled()) {
        // Opens the store listing so the user can install Tapaya Terminal
        Accept.plugin.install()
        return
    }

    // suspend; launches the plugin's activation flow and waits for the outcome
    when (val result = Accept.plugin.activateTerminal()) {
        ActivateTerminalResult.Success -> { /* device linked, ready to take payments */ }
        ActivateTerminalResult.Canceled -> { /* user backed out */ }
        is ActivateTerminalResult.Failed -> result.reason // ActivateTerminalFailureReason
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    if (!Accept.plugin.isInstalled()) {
        // Opens the store listing so the user can install Tapaya Terminal
        await Accept.plugin.install();
        return;
    }

    // launches the plugin's activation flow and waits for the outcome
    const result = await Accept.plugin.activateTerminal();
    switch (result.type) {
        case 'success': /* device linked, ready to take payments */ break;
        case 'canceled': /* user backed out */ break;
        case 'failed': handle(result.reason); break; // ActivateTerminalFailureReason
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`ActivateTerminalFailureReason` je jedna z hodnôt `INVALID_INTENT`, `HANDOVER_FAILED`, `CONFIRM_FAILED`,
`MERCHANT_NOT_ONBOARDED` alebo `UNKNOWN`.

<Callout title="Poznámka" type="info">
  `Accept.plugin.isInstalled()` vracia `false`, keď plugin Tapaya Terminal chýba. Aktuálny stav pluginu
  kedykoľvek zistíte volaním `Accept.plugin.status()`; podľa neho ošetrite svoje platobné UI.
</Callout>

`activateTerminal()` navyše **vyhadzuje** `NoTidsAvailableForMerchant`, keď obchodníkovi nezostáva žiadny nepriradený terminál
(TID) na aktiváciu, a to oddelene od vyššie uvedených výsledkov `ActivateTerminalResult.Failed`. Ak chcete tento prípad
zobraziť osobitne, napríklad vyzvať obchodníka, aby uvoľnil alebo si vyžiadal ďalší TID, obaľte volanie do
`try-catch`.

### Kontrola aktualizácií pluginu [#checking-for-plugin-updates]

`Accept.plugin.updateInfo()` hlási, či je na Google Play k dispozícii novšie zostavenie pluginu Tapaya Terminal,
takže môžete obchodníka vyzvať na aktualizáciu skôr, než platba narazí na rozdiel verzií.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    when (val info = Accept.plugin.updateInfo()) {
        PluginUpdateInfo.UpToDate -> { /* nothing to do */ }
        is PluginUpdateInfo.UpdateAvailable -> promptUpdate(info.priority) // Int, higher is more urgent
        PluginUpdateInfo.Unknown -> { /* not installed, or an older plugin build that predates this check */ }
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    const info = await Accept.plugin.updateInfo();
    switch (info.type) {
        case 'upToDate': /* nothing to do */ break;
        case 'updateAvailable': promptUpdate(info.priority); break; // higher is more urgent
        case 'unknown': /* not installed, or a plugin build that predates this check */ break;
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<Callout title="Poznámka" type="info">
  SDK sa nikdy nedopytuje Google Play priamo; pluginová aplikácia si dostupnosť aktualizácie overuje sama cez Play Core a výsledok hlási
  svojím existujúcim stavovým kanálom. Zostavenie pluginu staršie než to, s ktorým bola táto funkcia vydaná, vždy hlási
  `Unknown`.
</Callout>

## Vytvorenie platby [#create-a-payment]

Sumy sú vždy v **najmenšej jednotke meny**: haliere pre CZK, centy pre EUR. `1500` s
menou `"CZK"` naúčtuje 15,00 Kč. Meny sa zadávajú ako kódy ISO 4217.

`pay()` vracia `Flow<PaymentEvent>`; zbierajte ho, aby ste sledovali priebeh a konečný výsledok. Nikdy nevyhadzuje výnimku;
zlyhania sa prejavia ako `PaymentEvent.CreationFailed`.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    Accept.payments.pay(amount = 1500, currency = "CZK") // 15.00 Kč
        .collect { event ->
            when (event) {
                PaymentEvent.Creating -> { /* registering the payment */ }
                is PaymentEvent.Created -> { /* backend payment created: event.paymentToken */ }
                PaymentEvent.Launched -> { /* Tapaya Terminal is in the foreground */ }
                is PaymentEvent.Result -> when (val r = event.payResult) {
                    is PayResult.Success -> showReceipt(r.paymentToken, r.authCode)
                    is PayResult.Declined -> showDeclined(r.metadata)
                    is PayResult.Canceled -> showCancelled(r.metadata)
                    is PayResult.Failed -> showUnexpected(r.reason) // PayFailureReason
                }
                is PaymentEvent.CreationFailed -> handleError(event.cause) // AcceptException
            }
        }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    Accept.payments.pay({ amount: 1500, currency: 'CZK' }, (event) => { // 15.00 Kč
        switch (event.type) {
            case 'creating': /* registering the payment */ break;
            case 'created': /* backend payment created: event.paymentToken */ break;
            case 'launched': /* Tapaya Terminal is in the foreground */ break;
            case 'result':
                switch (event.payResult.type) {
                    case 'success': showReceipt(event.payResult.paymentToken, event.payResult.authCode); break;
                    case 'declined': showDeclined(event.payResult.metadata); break;
                    case 'canceled': showCancelled(event.payResult.metadata); break;
                    case 'failed': showUnexpected(event.payResult.reason); break; // PayFailureReason
                }
                break;
            case 'creationFailed': handleError(event.error); break; // AcceptError payload
        }
    });
    ```
  </CodeBlockTab>
</CodeBlockTabs>

**Parametre:**

| Parameter            | Typ                     | Povinné | Popis                                                                                                                                                                                                                                                                   |
| :------------------- | :---------------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`             | `Long`                  | ✓       | Suma, ktorá sa má naúčtovať, v najmenšej jednotke meny `currency` (napr. centy pre USD, haliere pre CZK).                                                                                                                                                               |
| `currency`           | `String`                | ✓       | Kód meny ISO 4217.                                                                                                                                                                                                                                                      |
| `paymentToken`       | `String?`               |         | Obnoví existujúcu platobnú reláciu namiesto spustenia novej. Predvolená hodnota je `null`.                                                                                                                                                                              |
| `receiptConfig`      | `PaymentReceiptConfig?` |         | Prepíše obrazovku účtenky zobrazenú po **schválenej** platbe, len pre toto volanie. Predvolená hodnota je `null`, pri ktorej sa použije predvolené nastavenie z `Accept.payments.setOptions()`.                                                                         |
| `nfcPosition`        | `NfcPositionConfig?`    |         | Prepíše, kam mieri animácia priloženia karty, len pre toto volanie. Predvolená hodnota je `null`, pri ktorej sa použije predvolené nastavenie z `Accept.payments.setOptions()`.                                                                                         |
| `metadata`           | `Map<String, String>`   |         | Údaje vo forme kľúč-hodnota pripojené k platbe. Odosielajú sa pluginu Tapaya Terminal v intente `PAY` a vracajú sa späť vo výslednom `PayResult`, ak ich plugin vráti. Predvolená hodnota je `emptyMap()`.                                                              |
| `tip`                | `Long?`                 |         | Pevné prepitné v najmenšej jednotke meny `currency`. Ak je nastavené, plugin preskočí vlastnú interaktívnu obrazovku prepitného a naúčtuje `amount + tip`. Predvolená hodnota je `null` (plugin sa platiteľa na prepitné opýta).                                        |
| `transactionTimeout` | `Duration?`             |         | Prepíše, ako dlho terminál čaká na priloženie karty, len pre toto volanie. Predvolená hodnota je `null`, pri ktorej sa použije predvolené nastavenie z `Accept.payments.setOptions()`. Pozrite [Nastavenie časového limitu transakcie](#setting-a-transaction-timeout). |
| `display`            | `PaymentDisplay?`       |         | Prepíše, na ktorej obrazovke beží platobné UI pluginu, len pre toto volanie. Predvolená hodnota je `null`, pri ktorej sa použije predvolené nastavenie z `Accept.payments.setOptions()`. Pozrite [Voľba displeja](#targeting-a-display).                                |

Počas platby je plugin Tapaya Terminal v popredí. Po dokončení platby sa vaša aplikácia automaticky
vráti do popredia. Ak chcete obnoviť prerušenú platbu, odovzdajte jej `paymentToken` späť do
`pay(amount, currency, paymentToken)`.

<Callout title="Vrátenie metadata nie je zaručené" type="info">
  `metadata` sa v `PayResult` vracajú len vtedy, keď ich plugin vráti, preto ich berte ako doplnkový kontext bez záruky
  (napr. ID objednávky pre vlastné logovanie), nie ako údaje, od ktorých vrátenia vaša aplikácia závisí.
</Callout>

### Voľba meny [#choosing-the-currency]

`pay()` účtuje v mene `currency`, ktorú odovzdáte ako kód ISO 4217 veľkými písmenami, so sumou `amount` v najmenšej
jednotke tejto meny. Stránka [Podporované meny](/merchants/supported_currencies) uvádza všetky meny, ktoré Tapaya podporuje, no obchodník
má zvyčajne povolené len niektoré z nich.

Kódy ISO 4217, ktoré má obchodník povolené, zistíte volaním `Accept.merchant.availableCurrencies()`. Platba v akejkoľvek inej
mene zlyhá s `CurrencyNotAvailableForMerchant`.

Každá mena má tiež minimálnu sumu na zaplatenie, ktorá sa vynucuje v celej platforme. Aktuálne minimá načítate pomocou
`Accept.sdk.minimumAmounts()`, ktoré vracia `List<MinimumAmount>` (`currency: String, amount: Long`, v najmenšej
jednotke meny). Volanie `pay()` pod týmto minimom zlyhá s
`PaymentEvent.CreationFailed(AmountBelowMinimum(minimum, currency))`. Úplný zoznam výnimiek nájdete v časti
[Spracovanie chýb](./sdk_integration#error-handling).

### Nastavenie obrazovky účtenky [#configuring-the-receipt-screen]

`receiptConfig` určuje, čo plugin Tapaya Terminal zobrazí platiteľovi po **schválenej** platbe, ako dlho
to zostane na obrazovke a či sa účtenka vytlačí. Nemá žiadny vplyv na zamietnuté a stornované platby ani na platby čakajúce
na storno; tie vždy zobrazia vlastnú obrazovku výsledku bez ohľadu na toto nastavenie.

| `PaymentReceiptConfig`                                    | Popis                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| :-------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DismissImmediately`                                      | Platiteľovi sa nič nezobrazí.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `Show(showQrCodeReceipt, dismissBehavior, printBehavior)` | Zobrazí obrazovku účtenky. `showQrCodeReceipt: Boolean = true` zapína alebo vypína QR kód s odkazom na účtenku. `dismissBehavior: DismissBehavior = DismissBehavior.Manual` určuje, ako sa obrazovka zatvorí: `Manual` (zostane, kým ju platiteľ nezatvorí) alebo `Delayed(delay: Duration)` (automaticky sa zatvorí po uplynutí `delay`, ak ju platiteľ nezatvoril skôr). `printBehavior: ReceiptPrintBehavior = ReceiptPrintBehavior.OnDemand` riadi fyzickú účtenku: `Automatic` (vytlačí sa hneď, ako sa obrazovka zobrazí), `OnDemand` (len keď tlač spustí pokladník) alebo `Disabled` (nikdy sa netlačí). |

Predvolené nastavenie pre celé zariadenie nastavte raz pomocou `Accept.payments.setOptions()` a pre jednotlivú platbu ho prepíšte
cez `pay(..., receiptConfig = ...)`. Kým nezavoláte `setOptions()`, predvolená hodnota je `Show` s QR účtenkou, ručným
zatvorením a tlačou na požiadanie, čo zodpovedá vlastnému záložnému nastaveniu pluginu.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    // Set a default, e.g. once at app startup
    Accept.payments.setOptions(
        receiptConfig = PaymentReceiptConfig.Show(
            showQrCodeReceipt = true,
            dismissBehavior = DismissBehavior.Delayed(30.seconds),
            printBehavior = ReceiptPrintBehavior.Automatic,
        ),
    )

    // Override it for a single payment
    Accept.payments.pay(
        amount = 1500,
        currency = "CZK",
        receiptConfig = PaymentReceiptConfig.DismissImmediately,
    )
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    // Set a default, e.g. once at app startup
    Accept.payments.setOptions({
        receiptConfig: {
            type: 'show',
            showQrCodeReceipt: true,
            dismissBehavior: { type: 'delayed', delayMs: 30_000 },
            printBehavior: 'automatic',
        },
    });

    // Override it for a single payment
    await Accept.payments.payAsync({
        amount: 1500,
        currency: 'CZK',
        receiptConfig: { type: 'dismissImmediately' },
    });
    ```
  </CodeBlockTab>
</CodeBlockTabs>

### Umiestnenie animácie priloženia cez NFC [#positioning-the-nfc-tap-animation]

`nfcPosition` je nápoveda, ktorá pluginu Tapaya Terminal oznamuje, kde má toto zariadenie anténu NFC, aby mohol
správne umiestniť animáciu „priložte sem“. Plugin najprv určí polohu antény sám (živou detekciou platformy,
potom z pribalenej databázy zariadení a nakoniec z čohokoľvek, čo obchodník nastavil priamo v plugine); túto hodnotu zadávajte len
vtedy, keď chcete toto určenie prepísať, napríklad pri zariadení, ktoré plugin nedokáže rozpoznať alebo ho má vo svojej databáze nesprávne.

| `NfcPositionConfig`                       | Popis                                                                                                                                                                |
| :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Unspecified`                             | Bez prepísania (predvolené). Plugin určí polohu sám a zachová polohu, ktorú v ňom obchodník už nastavil.                                                             |
| `OnDevice(surface, xFraction, yFraction)` | Anténa je priamo v zariadení; animácia mieri na bod `(xFraction, yFraction)` na ploche `surface`, čo je `OnDeviceSurface.BACK` alebo `OnDeviceSurface.FRONT_SCREEN`. |
| `ExternalAbove`                           | Samostatný modul čítačky je umiestnený nad telefónom (držiak smart-POS); na zariadení teda nie je žiadny bod, na ktorý by animácia mierila.                          |

Predvolené nastavenie pre celé zariadenie nastavte pomocou `Accept.payments.setOptions(nfcPosition = ...)`, alebo ho prepíšte pre jednotlivé volanie
cez `pay(..., nfcPosition = ...)`. `setOptions()` mení len tie voľby, ktoré odovzdáte, takže samotné `setOptions(nfcPosition = ...)`
nenaruší skôr nastavenú predvolenú hodnotu `receiptConfig` a naopak.

### Nastavenie časového limitu transakcie [#setting-a-transaction-timeout]

`transactionTimeout` určuje, ako dlho plugin Tapaya Terminal čaká, kým zákazník priloží kartu, než to
vzdá. Prijíma akúkoľvek hodnotu `Duration` od nuly do 120 sekúnd; čokoľvek mimo tohto rozsahu sa prejaví ako
`TransactionTimeoutOutOfRange(transactionTimeout)` cez `PaymentEvent.CreationFailed`, pretože `pay()` nikdy nevyhadzuje výnimku
synchrónne.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    Accept.payments.pay(
        amount = 1500,
        currency = "CZK",
        transactionTimeout = 90.seconds,
    )
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    await Accept.payments.payAsync({
        amount: 1500,
        currency: 'CZK',
        transactionTimeoutMs: 90_000, // milliseconds, 0…120000
    });
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Ponechajte predvolenú hodnotu `null`, ak chcete použiť vlastný záložný limit sprievodnej aplikácie (približne 60 sekúnd), alebo nastavte predvolenú
hodnotu pre celé zariadenie pomocou `Accept.payments.setOptions(transactionTimeout = ...)`.

### Voľba displeja [#targeting-a-display]

Pri obojstrannom zariadení („double-sided“) je jeden panel otočený k obchodníkovi a druhý, zákaznícky panel k
platiteľovi. `Accept.displays` hlási, aké displeje má zariadenie práve teraz, a voľba `display` hovorí pluginu,
na ktorom paneli má spustiť svoje platobné UI.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    if (Accept.displays.isDualScreen) {
        val customerPanel = Accept.displays.customerFacing() // AcceptDisplay?, null on single-screen devices
    }

    Accept.payments.pay(
        amount = 1500,
        currency = "CZK",
        display = PaymentDisplay.CustomerFacing,
    )
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    if (Accept.displays.isDualScreen) {
        const customerPanel = Accept.displays.customerFacing(); // null on single-screen devices
    }

    await Accept.payments.payAsync({
        amount: 1500,
        currency: 'CZK',
        display: { type: 'customerFacing' },
    });
    ```
  </CodeBlockTab>
</CodeBlockTabs>

| `PaymentDisplay`      | Popis                                                                                                          |
| :-------------------- | :------------------------------------------------------------------------------------------------------------- |
| `Default`             | Aktuálny displej hostiteľskej aplikácie (predvolené správanie, na zariadeniach s jednou obrazovkou bez zmeny). |
| `CustomerFacing`      | Sekundárny panel obojstranného zariadenia.                                                                     |
| `Specific(displayId)` | Konkrétny displej podľa `id` uvedeného v `Accept.displays.all()`.                                              |

Cieľ, ktorý nemožno splniť, napríklad `CustomerFacing` na zariadení s jednou obrazovkou, potichu prejde na
`Default`. Predvolené nastavenie pre celé zariadenie nastavte pomocou `Accept.payments.setOptions(display = ...)`, alebo ho prepíšte pre jednotlivé volanie
cez `pay(..., display = ...)`.

<Callout title="Nastavte cieľ pred autentifikáciou" type="info">
  Predbežná príprava terminálu (warm-up) spustená volaním `Accept.auth.authenticate()` sa riadi rovnakým cieľom, preto zavolajte
  `setOptions(display = ...)` pred `authenticate()`, ak chcete, aby ani samotná príprava neprebiehala na
  obrazovke obchodníka.
</Callout>

Ukážková aplikácia `:dualscreen` predvádza kompletné platenie na dvoch paneloch: obchodník pracuje na primárnom paneli, zatiaľ čo
`PaymentDisplay.CustomerFacing` zobrazí platobné UI pluginu na obrazovke zákazníka.

## Výsledok platby [#payment-outcome]

Konečná udalosť `PaymentEvent.Result` nesie `PayResult`. Každý variant nesie `metadata`, ktoré vracajú to, čo ste
odovzdali do `pay()`, ak ich plugin vráti (môže byť `null`):

| `PayResult`                                                 | Význam                                                                                                                                                                                                 |
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Success(paymentToken, metadata, authCode, receiptDetails)` | Autorizované a strhnuté. Prostriedky budú zúčtované obchodníkovi. `authCode` je autorizačný kód spracovateľa hlásený terminálom; má informatívny charakter a v závislosti od spracovateľa môže chýbať. |
| `Declined(metadata, receiptDetails)`                        | Zamietnuté spracovateľom platieb alebo vydavateľom karty.                                                                                                                                              |
| `Canceled(metadata)`                                        | Zrušené zákazníkom alebo obchodníkom pred dokončením.                                                                                                                                                  |
| `Failed(reason, metadata)`                                  | Nedokončené; `reason` je `PayFailureReason`.                                                                                                                                                           |

`PayFailureReason` je jedna z hodnôt `INVALID_INTENT`, `NOT_ACTIVATED`, `TERMINAL_INIT_FAILED`, `PAYMENT_SETUP_FAILED`,
`PLUGIN_UNAVAILABLE` alebo `UNKNOWN`.

### Čítanie údajov účtenky [#reading-receipt-details]

`Success` a `Declined` nesú voliteľné `receiptDetails: ReceiptDetails?` so štruktúrovanými údajmi kartovej účtenky
hlásenými terminálom, určené pre volajúcich, ktorí tlačia vlastnú oficiálnu kartovú účtenku namiesto toho, aby sa spoliehali len na
hostovanú stránku `PaymentStatus.receiptUrl`.

| Pole                   | Typ       | Popis                                           |
| :--------------------- | :-------- | :---------------------------------------------- |
| `merchantId`           | `String?` | ID obchodníka hlásené spracovateľom.            |
| `terminalId`           | `String?` | ID terminálu hlásené spracovateľom.             |
| `cardBrand`            | `String?` | Značka karty, napr. Visa alebo Mastercard.      |
| `appLabel`             | `String?` | Označenie aplikácie EMV.                        |
| `aid`                  | `String?` | Identifikátor aplikácie EMV.                    |
| `maskedPan`            | `String?` | Maskované číslo karty.                          |
| `responseCode`         | `String?` | Kód odpovede spracovateľa.                      |
| `transactionTimestamp` | `String?` | Časová pečiatka transakcie vo formáte ISO-8601. |

Každé pole je voliteľné a bez záruky; volajúci musí počítať s tým, že ktorékoľvek z nich, prípadne aj samotné `receiptDetails`,
bude chýbať.

### Čítanie podrobností platby [#reading-payment-details]

`PayResult.Success` vám poskytne `paymentToken`. Úplný záznam vrátane `receiptUrl`, požadovanej a
zúčtovanej sumy a časových pečiatok načítate pomocou `Accept.payments.status()`:

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    val status: PaymentStatus = Accept.payments.status(paymentToken)
    status.state       // PENDING, SUCCESS, CANCELLED, REFUNDED, FAILED, ACTION_NEEDED
    status.receiptUrl  // for your receipt UI
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    const status = await Accept.payments.status(paymentToken);
    status.state;      // PENDING, SUCCESS, CANCELLED, REFUNDED, FAILED, ACTION_NEEDED
    status.receiptUrl; // for your receipt UI
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Prebiehajúcu platbu zastavíte pomocou `Accept.payments.cancel(paymentToken)`, ktoré vracia výsledný `PaymentState`.

### Vrátenie platby [#issuing-a-refund]

Dokončenú platbu vrátite pomocou `Accept.payments.refund(paymentToken, reason)`, ktoré tiež vracia výsledný
`PaymentState`.

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    val state: PaymentState = Accept.payments.refund(paymentToken, RefundReason.CUSTOMER_REQUEST)
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    const state = await Accept.payments.refund(paymentToken, 'CUSTOMER_REQUEST');
    ```
  </CodeBlockTab>
</CodeBlockTabs>

`RefundReason` je jedna z hodnôt `CUSTOMER_REQUEST`, `DUPLICATE`, `FRAUDULENT` alebo `EXPIRED_CHARGE`.

## Spracovanie chýb [#error-handling]

Platba, ktorá sa dostane do terminálu, ale neuspeje, **nie je** výnimka; príde ako
`PaymentEvent.Result` s `PayResult.Declined`, `Canceled` alebo `Failed(reason)`. Zlyhania, ktoré zabránia
vytvoreniu platby, prichádzajú ako `PaymentEvent.CreationFailed(cause)`, kde `cause` je typovaná
`AcceptException`. Výnimky špecifické pre túto integráciu:

| Výnimka                                            | Kedy nastane                                                                                       | Čo robiť                                                                                       |
| :------------------------------------------------- | :------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------- |
| `PluginUnavailable`                                | Plugin Tapaya Terminal nie je nainštalovaný alebo sa nepodarilo nadviazať spojenie s jeho službou. | Vyzvite na inštaláciu pomocou `Accept.plugin.install()`.                                       |
| `CurrencyNotAvailableForMerchant(code)`            | Obchodník nemá nastavenú požadovanú menu.                                                          | Účtujte v povolenej mene alebo aktualizujte konfiguráciu obchodníka.                           |
| `AmountBelowMinimum(minimum, currency)`            | Suma je pod nastaveným minimom.                                                                    | Účtujte sumu rovnú `minimum` alebo vyššiu.                                                     |
| `TransactionTimeoutOutOfRange(transactionTimeout)` | `transactionTimeout` bol mimo rozsahu 0 až 120 sekúnd.                                             | Odovzdajte hodnotu v povolenom rozsahu alebo ju vynechajte, aby sa použila predvolená hodnota. |
| `LocationPermissionRequired`                       | Vaša aplikácia nemá udelené oprávnenie na polohu.                                                  | Vyžiadajte `ACCESS_FINE_LOCATION` a akciu zopakujte.                                           |
| `LocationUnavailable` / `LocationTimeout`          | Polohu zariadenia sa nepodarilo určiť.                                                             | Požiadajte používateľa, aby zapol polohu, a akciu zopakujte.                                   |
| `SessionExpired`                                   | Relácia prihláseného obchodníka vypršala.                                                          | Znova sa autentifikujte pomocou `Accept.auth.authenticate()`.                                  |
| `NoInternetConnection`                             | Nie je k dispozícii sieťové pripojenie.                                                            | Požiadajte používateľa, aby sa znova pripojil, a akciu zopakujte.                              |
| `PaymentInProgress`                                | Predchádzajúce volanie `pay()` stále čaká na výsledok.                                             | Počkajte, kým sa aktuálna platba dokončí.                                                      |

`Accept.payments.status()`, `cancel()` a `refund()` sú funkcie `suspend` a tieto výnimky **vyhadzujú**
(namiesto vysielania udalostí); obaľte ich do `try-catch`. `status()`/`cancel()`/`refund()` navyše pri neznámom tokene vyhadzujú
`PaymentNotFound`. Úplný zoznam nájdete v časti
[Spracovanie chýb](../integration/sdk_integration#error-handling).

## Časové limity [#timeouts]

Platba kartou sa musí na termináli dokončiť krátko po jej spustení. Ak zákazník kartu nikdy nepriloží alebo sa
plugin Tapaya Terminal počas platby zatvorí, platba skončí ako `PayResult.Canceled` alebo
`PayResult.Failed` a podkladová platba sa na strane servera zruší a nikdy sa nezúčtuje. Predtým, ako platbu budete považovať za dokončenú,
vždy si overte konečný stav pomocou `Accept.payments.status()`.

## Integrácia bez SDK [#integrate-without-the-sdk]

Ak nemôžete Accept SDK vložiť do aplikácie (multiplatformový framework, tenký POS shell, webová pokladnica), môžete
Tapaya Terminal ovládať priamo pomocou deeplinku. Rozdelenie práce sa tým mení: platbu cez
Tapaya API vytvárate **vy** a **vy** sa tiež dopytujete na jej stav, aby ste zistili výsledok; Tapaya Terminal zabezpečuje len prácu s kartou.

Postup:

1. Vytvorte platbu zo svojho backendu cez [Payments API](https://api.tapaya.com/openapi/index.html) a uložte si
   jej `paymentId`.
2. Otvorte Tapaya Terminal s týmto `paymentId` pomocou [platobného deeplinku](#pay-deeplink).
3. Dopytujte sa na stav platby cez [Payments API](https://api.tapaya.com/openapi/index.html), kým neopustí
   stav `PENDING`. Deeplinky nemajú spätný kanál, takže dopytovanie je jediným spoľahlivým zdrojom výsledku.

<Callout title="Poznámka" type="info">
  Tapaya Terminal musí byť nainštalovaný, prihlásený k účtu obchodníka a nastavený pre menu
  platby. V tomto režime neprebieha žiadna aktivácia; obchodníkom zodpovedným za transakciu (merchant of record) je prihlásený účet. V prípade potreby
  obchodníka prihláste pomocou [prihlasovacieho deeplinku](#login-deeplink).
</Callout>

### Schémy deeplinkov [#deeplink-schemes]

Schéma určuje, ktoré zostavenie Tapaya Terminal odkaz obslúži:

| Prostredie | Schéma                  |
| :--------- | :---------------------- |
| Produkcia  | `tapaya-accept`         |
| Sandbox    | `tapaya-accept-sandbox` |

Ak sa odkaz nepodarí otvoriť, dané zostavenie Tapaya Terminal nie je nainštalované.

### Platobný deeplink [#pay-deeplink]

```text
tapaya-accept://pay?paymentId=<paymentId>&amount=<amount>&currency=<ISO4217>
```

| Parameter dopytu | Povinné | Poznámky                                                                                                                                |
| :--------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `paymentId`      | **Áno** | Platba vytvorená v kroku 1.                                                                                                             |
| `amount`         | **Áno** | Suma v **hlavných jednotkách** ako celé číslo: `amount=15` naúčtuje 15,00. Pozor, tým sa líši od SDK, ktoré prijíma najmenšie jednotky. |
| `currency`       | **Áno** | Kód meny ISO 4217, napr. `CZK`.                                                                                                         |

<CodeBlockTabs defaultValue="kotlin" groupId="accept-language">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="kotlin">
      Android (Kotlin)
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="typescript">
      React Native (TypeScript)
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="kotlin">
    ```kotlin
    val uri = Uri.parse("tapaya-accept://pay?paymentId=$paymentId&amount=15&currency=CZK")
    try {
        startActivity(Intent(Intent.ACTION_VIEW, uri).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
    } catch (e: ActivityNotFoundException) {
        // Tapaya Terminal is not installed, send the user to Google Play
    }
    ```
  </CodeBlockTab>

  <CodeBlockTab value="typescript">
    ```typescript
    import { Linking } from 'react-native';

    const url = `tapaya-accept://pay?paymentId=${paymentId}&amount=15&currency=CZK`;
    try {
        await Linking.openURL(url);
    } catch {
        // Tapaya Terminal is not installed, send the user to Google Play
    }
    ```
  </CodeBlockTab>
</CodeBlockTabs>

### Prihlasovací deeplink [#login-deeplink]

Prihláste obchodníka do Tapaya Terminal pomocou jeho `merchantToken`, identifikátora obchodníka, ktorý nastavíte cez `POST /merchant/auth/register`:

```text
tapaya-accept://login?token=<merchantToken>
```

| Parameter dopytu | Povinné | Poznámky                                                                                                                 |
| :--------------- | :------ | :----------------------------------------------------------------------------------------------------------------------- |
| `token`          | **Áno** | `merchantToken` obchodníka. Ide o vaše stabilné ID obchodníka, nie o krátkodobý token SDK z `POST /merchant/auth/login`. |
| `activationCode` | Nie     | Predvyplní aktivačný kód terminálu.                                                                                      |

## Otestovanie integrácie [#test-the-integration]

1. Inicializujte s `isProduction = false` (predvolená hodnota); platby v sandboxe nikdy nepresúvajú skutočné peniaze.
2. Nainštalujte plugin Tapaya Terminal do testovacieho zariadenia, prihláste sa testovacím obchodníkom zo sandboxu a vykonajte aktiváciu.
3. Vykonajte platbu na malú sumu a overte celý tok `Success` od začiatku do konca vrátane vášho UI účtenky.
4. Vyskúšajte cesty zlyhania: zamietnite platbu na termináli, jednu zrušte a jednu spustite v nepodporovanej
   mene; vaše spracovanie `PayResult`/`CreationFailed` by malo pokryť každú z nich.

Keď budete pripravení spustiť ostrú prevádzku, nastavte v `initialize` hodnotu `isProduction = true` a autentifikujte sa s produkčnými
prihlasovacími údajmi. Kód platieb sa nemení.
