# Aplikace Tapaya Terminal (/cs/in-store/integration/card_terminal)



Přijímejte bezkontaktní platby kartou ze své aplikace pro Android tak, že práci s kartou předáte pluginové aplikaci **Tapaya Terminal**.
Prodej zůstává pod kontrolou vaší aplikace; plugin obstará přiložení karty přes NFC, zadání PIN a zpracování
karty a výsledek se pak vrátí přes proud událostí `Accept.payments.pay()`.

Tuto integraci použijte, pokud chcete Tap to Pay, aniž byste do vlastní aplikace vkládali zpracovatele plateb. Pokud má vaše
platforma místo toho povolené vestavěné Tap to Pay, přečtěte si průvodce *Embedded Tap to Pay*.

## Možnosti integrace [#integration-options]

| Možnost                     | Vhodné pro                                   | Doručení výsledku                                                                                                          |
| :-------------------------- | :------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| **Accept SDK** (doporučeno) | Nativní aplikace pro Android                 | `Accept.payments.pay()` vysílá `Flow<PaymentEvent>`: vytvoření, spuštění i konečný výsledek z terminálu se zpracují za vás |
| **Deeplink**                | WebView, multiplatformní frameworky, QR kódy | Platbu vytvoříte přes Tapaya API a průběžně se dotazujete na její stav; plugin se otevře přes deeplink                     |

Nejprve je popsána cesta přes SDK; kontrakt deeplinku najdete v části [Integrace bez SDK](#integrate-without-the-sdk).

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

1. Vaše aplikace zahájí platbu voláním `Accept.payments.pay(amount, currency)`.
2. SDK zaregistruje platbu v backendu Tapaya a spustí plugin Tapaya Terminal na stejném zařízení.
3. Zákazník přiloží kartu nebo telefon k obrazovce Tapaya Terminal.
4. Tapaya Terminal dokončí zpracování platby s backendem Tapaya.
5. SDK sleduje platbu, dokud nedosáhne konečného stavu, a vyšle `PaymentEvent.Result` s
   `PayResult`. Pokud platbu vůbec nelze vytvořit, vyšle místo toho `PaymentEvent.CreationFailed`, obvykle
   do jedné sekundy.

## Předpoklady [#prerequisites]

* **Plugin Tapaya Terminal** nainstalovaný v zařízení a **aktivovaný** pro vašeho obchodníka (viz
  [Instalace a aktivace terminálu](#install-and-activate-the-terminal)).
* Zařízení s NFC se systémem Android 11 (API 30) nebo novějším.
* Accept SDK [nainstalované a inicializované](../integration/sdk_integration#initialization), s
  [autentizovaným](../integration/sdk_integration#step-2-authenticate) obchodníkem.
* Oprávnění k poloze (`ACCESS_FINE_LOCATION`) udělené vaší aplikaci za běhu.

## Instalace a aktivace terminálu [#install-and-activate-the-terminal]

Aktivaci proveďte jednou pro každé zařízení. Aktivace propojí plugin Tapaya Terminal se stejným účtem obchodníka, pod
kterým je autentizovaná vaše aplikace.

<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 hodnot `INVALID_INTENT`, `HANDOVER_FAILED`, `CONFIRM_FAILED`,
`MERCHANT_NOT_ONBOARDED` nebo `UNKNOWN`.

<Callout title="Poznámka" type="info">
  `Accept.plugin.isInstalled()` vrací `false`, když plugin Tapaya Terminal chybí. Aktuální stav pluginu
  kdykoli zjistíte voláním `Accept.plugin.status()`; podle něj ošetřete své platební UI.
</Callout>

`activateTerminal()` navíc **vyhazuje** `NoTidsAvailableForMerchant`, když obchodníkovi nezbývá žádný nepřiřazený terminál
(TID) k aktivaci, a to odděleně od výše uvedených výsledků `ActivateTerminalResult.Failed`. Pokud chcete tento případ
zobrazit zvlášť, například vyzvat obchodníka, aby uvolnil nebo si vyžádal další TID, obalte volání do
`try-catch`.

### Kontrola aktualizací pluginu [#checking-for-plugin-updates]

`Accept.plugin.updateInfo()` hlásí, zda je na Google Play k dispozici novější sestavení pluginu Tapaya Terminal,
takže můžete obchodníka vyzvat k aktualizaci dřív, než platba narazí na rozdíl verzí.

<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 se nikdy nedotazuje Google Play přímo; pluginová aplikace si dostupnost aktualizace ověřuje sama přes Play Core a výsledek hlásí
  svým stávajícím stavovým kanálem. Sestavení pluginu starší než to, se kterým byla tato funkce vydána, vždy hlásí
  `Unknown`.
</Callout>

## Vytvoření platby [#create-a-payment]

Částky jsou vždy v **nejmenší jednotce měny**: haléře pro CZK, centy pro EUR. `1500` s
měnou `"CZK"` naúčtuje 15,00 Kč. Měny se zadávají jako kódy ISO 4217.

`pay()` vrací `Flow<PaymentEvent>`; sbírejte jej, abyste sledovali průběh a konečný výsledek. Nikdy nevyhazuje výjimku;
selhání se projeví jako `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>

**Parametry:**

| Parametr             | Typ                     | Povinné | Popis                                                                                                                                                                                                                                                     |
| :------------------- | :---------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`             | `Long`                  | ✓       | Částka k naúčtování v nejmenší jednotce měny `currency` (např. centy pro USD, haléře pro CZK).                                                                                                                                                            |
| `currency`           | `String`                | ✓       | Kód měny ISO 4217.                                                                                                                                                                                                                                        |
| `paymentToken`       | `String?`               |         | Obnoví existující platební relaci místo zahájení nové. Výchozí hodnota je `null`.                                                                                                                                                                         |
| `receiptConfig`      | `PaymentReceiptConfig?` |         | Přepíše obrazovku účtenky zobrazenou po **schválené** platbě, jen pro toto volání. Výchozí hodnota je `null`, při které se použije výchozí nastavení z `Accept.payments.setOptions()`.                                                                    |
| `nfcPosition`        | `NfcPositionConfig?`    |         | Přepíše, kam míří animace přiložení karty, jen pro toto volání. Výchozí hodnota je `null`, při které se použije výchozí nastavení z `Accept.payments.setOptions()`.                                                                                       |
| `metadata`           | `Map<String, String>`   |         | Data ve tvaru klíč-hodnota připojená k platbě. Odesílají se pluginu Tapaya Terminal v intentu `PAY` a vracejí se zpět ve výsledném `PayResult`, pokud je plugin vrátí. Výchozí hodnota je `emptyMap()`.                                                   |
| `tip`                | `Long?`                 |         | Pevné spropitné v nejmenší jednotce měny `currency`. Je-li nastaveno, plugin přeskočí vlastní interaktivní obrazovku spropitného a naúčtuje `amount + tip`. Výchozí hodnota je `null` (plugin se plátce na spropitné zeptá).                              |
| `transactionTimeout` | `Duration?`             |         | Přepíše, jak dlouho terminál čeká na přiložení karty, jen pro toto volání. Výchozí hodnota je `null`, při které se použije výchozí nastavení z `Accept.payments.setOptions()`. Viz [Nastavení časového limitu transakce](#setting-a-transaction-timeout). |
| `display`            | `PaymentDisplay?`       |         | Přepíše, na které obrazovce běží platební UI pluginu, jen pro toto volání. Výchozí hodnota je `null`, při které se použije výchozí nastavení z `Accept.payments.setOptions()`. Viz [Volba displeje](#targeting-a-display).                                |

Během platby je plugin Tapaya Terminal v popředí. Po dokončení platby se vaše aplikace automaticky
vrátí do popředí. Chcete-li obnovit přerušenou platbu, předejte její `paymentToken` zpět do
`pay(amount, currency, paymentToken)`.

<Callout title="Vrácení metadata není zaručeno" type="info">
  `metadata` se v `PayResult` vracejí jen tehdy, když je plugin vrátí, proto je berte jako doplňkový kontext bez záruky
  (např. ID objednávky pro vlastní logování), ne jako data, na jejichž vrácení vaše aplikace závisí.
</Callout>

### Volba měny [#choosing-the-currency]

`pay()` účtuje v měně `currency`, kterou předáte jako kód ISO 4217 velkými písmeny, s částkou `amount` v nejmenší
jednotce této měny. Stránka [Podporované měny](/merchants/supported_currencies) uvádí všechny měny, které Tapaya podporuje, ale obchodník
má obvykle povolené jen některé z nich.

Kódy ISO 4217, které má obchodník povolené, zjistíte voláním `Accept.merchant.availableCurrencies()`. Platba v jakékoli jiné
měně selže s `CurrencyNotAvailableForMerchant`.

Každá měna má také minimální částku k zaplacení, vynucovanou v celé platformě. Aktuální minima načtete pomocí
`Accept.sdk.minimumAmounts()`, které vrací `List<MinimumAmount>` (`currency: String, amount: Long`, v nejmenší
jednotce měny). Volání `pay()` pod tímto minimem selže s
`PaymentEvent.CreationFailed(AmountBelowMinimum(minimum, currency))`. Úplný seznam výjimek najdete v části
[Zpracování chyb](./sdk_integration#error-handling).

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

`receiptConfig` určuje, co plugin Tapaya Terminal zobrazí plátci po **schválené** platbě, jak dlouho
to zůstane na obrazovce a zda se účtenka vytiskne. Nemá žádný vliv na zamítnuté a stornované platby ani na platby čekající
na storno; ty vždy zobrazí vlastní obrazovku výsledku bez ohledu na toto nastavení.

| `PaymentReceiptConfig`                                    | Popis                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DismissImmediately`                                      | Plátci se nic nezobrazí.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `Show(showQrCodeReceipt, dismissBehavior, printBehavior)` | Zobrazí obrazovku účtenky. `showQrCodeReceipt: Boolean = true` zapíná nebo vypíná QR kód s odkazem na účtenku. `dismissBehavior: DismissBehavior = DismissBehavior.Manual` určuje, jak se obrazovka zavře: `Manual` (zůstane, dokud ji plátce nezavře) nebo `Delayed(delay: Duration)` (automaticky se zavře po uplynutí `delay`, pokud ji plátce nezavřel dříve). `printBehavior: ReceiptPrintBehavior = ReceiptPrintBehavior.OnDemand` řídí fyzickou účtenku: `Automatic` (vytiskne se hned, jak se obrazovka zobrazí), `OnDemand` (jen když tisk spustí pokladní) nebo `Disabled` (nikdy se netiskne). |

Výchozí nastavení pro celé zařízení nastavte jednou pomocí `Accept.payments.setOptions()` a pro jednotlivou platbu jej přepište
přes `pay(..., receiptConfig = ...)`. Dokud nezavoláte `setOptions()`, výchozí hodnota je `Show` s QR účtenkou, ručním
zavřením a tiskem na vyžádání, což odpovídá vlastnímu záložnímu nastavení 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>

### Umístění animace přiložení přes NFC [#positioning-the-nfc-tap-animation]

`nfcPosition` je nápověda, která pluginu Tapaya Terminal sděluje, kde má toto zařízení anténu NFC, aby mohl
správně umístit animaci „přiložte zde“. Plugin nejprve určí polohu antény sám (živou detekcí platformy,
pak z přibalené databáze zařízení a nakonec z čehokoli, co obchodník nastavil přímo v pluginu); tuto hodnotu zadávejte jen
tehdy, když chcete toto určení přepsat, například u zařízení, které plugin nedokáže rozpoznat nebo ho má ve své databázi chybně.

| `NfcPositionConfig`                       | Popis                                                                                                                                                           |
| :---------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Unspecified`                             | Bez přepsání (výchozí). Plugin určí polohu sám a zachová polohu, kterou v něm obchodník již nastavil.                                                           |
| `OnDevice(surface, xFraction, yFraction)` | Anténa je přímo v zařízení; animace míří na bod `(xFraction, yFraction)` na ploše `surface`, což je `OnDeviceSurface.BACK` nebo `OnDeviceSurface.FRONT_SCREEN`. |
| `ExternalAbove`                           | Samostatný modul čtečky je umístěn nad telefonem (držák smart-POS); na zařízení tedy není žádný bod, na který by animace mířila.                                |

Výchozí nastavení pro celé zařízení nastavte pomocí `Accept.payments.setOptions(nfcPosition = ...)`, nebo jej přepište pro jednotlivé volání
přes `pay(..., nfcPosition = ...)`. `setOptions()` mění jen ty volby, které předáte, takže samotné `setOptions(nfcPosition = ...)`
nenaruší dříve nastavenou výchozí hodnotu `receiptConfig` a naopak.

### Nastavení časového limitu transakce [#setting-a-transaction-timeout]

`transactionTimeout` určuje, jak dlouho plugin Tapaya Terminal čeká na přiložení karty zákazníkem, než to
vzdá. Přijímá jakoukoli hodnotu `Duration` od nuly do 120 sekund; cokoli mimo tento rozsah se projeví jako
`TransactionTimeoutOutOfRange(transactionTimeout)` přes `PaymentEvent.CreationFailed`, protože `pay()` nikdy nevyhazuje výjimku
synchronně.

<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>

Ponechte výchozí hodnotu `null`, chcete-li použít vlastní záložní limit doprovodné aplikace (přibližně 60 sekund), nebo nastavte výchozí
hodnotu pro celé zařízení pomocí `Accept.payments.setOptions(transactionTimeout = ...)`.

### Volba displeje [#targeting-a-display]

U oboustranného zařízení („double-sided“) je jeden panel otočený k obchodníkovi a druhý, zákaznický panel k
plátci. `Accept.displays` hlásí, jaké displeje zařízení právě má, a volba `display` říká pluginu,
na kterém panelu má spustit své platební 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ální displej hostitelské aplikace (výchozí chování, na zařízeních s jednou obrazovkou beze změny). |
| `CustomerFacing`      | Sekundární panel oboustranného zařízení.                                                               |
| `Specific(displayId)` | Konkrétní displej podle `id` uvedeného v `Accept.displays.all()`.                                      |

Cíl, který nelze splnit, například `CustomerFacing` na zařízení s jednou obrazovkou, tiše přejde na
`Default`. Výchozí nastavení pro celé zařízení nastavte pomocí `Accept.payments.setOptions(display = ...)`, nebo jej přepište pro jednotlivé volání
přes `pay(..., display = ...)`.

<Callout title="Nastavte cíl před autentizací" type="info">
  Předběžná příprava terminálu (warm-up) spuštěná voláním `Accept.auth.authenticate()` se řídí stejným cílem, proto zavolejte
  `setOptions(display = ...)` před `authenticate()`, pokud chcete, aby ani samotná příprava neprobíhala na
  obrazovce obchodníka.
</Callout>

Ukázková aplikace `:dualscreen` předvádí kompletní placení na dvou panelech: obchodník pracuje na primárním panelu, zatímco
`PaymentDisplay.CustomerFacing` zobrazí platební UI pluginu na obrazovce zákazníka.

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

Konečná událost `PaymentEvent.Result` nese `PayResult`. Každá varianta nese `metadata`, která vracejí to, co jste
předali do `pay()`, pokud je plugin vrátí (může být `null`):

| `PayResult`                                                 | Význam                                                                                                                                                                                                |
| :---------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Success(paymentToken, metadata, authCode, receiptDetails)` | Autorizováno a strženo. Prostředky budou zúčtovány obchodníkovi. `authCode` je autorizační kód zpracovatele hlášený terminálem; má informativní charakter a v závislosti na zpracovateli může chybět. |
| `Declined(metadata, receiptDetails)`                        | Zamítnuto zpracovatelem plateb nebo vydavatelem karty.                                                                                                                                                |
| `Canceled(metadata)`                                        | Zrušeno zákazníkem nebo obchodníkem před dokončením.                                                                                                                                                  |
| `Failed(reason, metadata)`                                  | Nedokončeno; `reason` je `PayFailureReason`.                                                                                                                                                          |

`PayFailureReason` je jedna z hodnot `INVALID_INTENT`, `NOT_ACTIVATED`, `TERMINAL_INIT_FAILED`, `PAYMENT_SETUP_FAILED`,
`PLUGIN_UNAVAILABLE` nebo `UNKNOWN`.

### Čtení údajů účtenky [#reading-receipt-details]

`Success` a `Declined` nesou volitelné `receiptDetails: ReceiptDetails?` se strukturovanými údaji karetní účtenky
hlášenými terminálem, určené pro volající, kteří tisknou vlastní oficiální karetní účtenku místo toho, aby se spoléhali jen na
hostovanou stránku `PaymentStatus.receiptUrl`.

| Pole                   | Typ       | Popis                                         |
| :--------------------- | :-------- | :-------------------------------------------- |
| `merchantId`           | `String?` | ID obchodníka hlášené zpracovatelem.          |
| `terminalId`           | `String?` | ID terminálu hlášené zpracovatelem.           |
| `cardBrand`            | `String?` | Značka karty, např. Visa nebo Mastercard.     |
| `appLabel`             | `String?` | Označení aplikace EMV.                        |
| `aid`                  | `String?` | Identifikátor aplikace EMV.                   |
| `maskedPan`            | `String?` | Maskované číslo karty.                        |
| `responseCode`         | `String?` | Kód odpovědi zpracovatele.                    |
| `transactionTimestamp` | `String?` | Časové razítko transakce ve formátu ISO-8601. |

Každé pole je volitelné a bez záruky; volající musí počítat s tím, že kterékoli z nich, případně i samotné `receiptDetails`,
bude chybět.

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

`PayResult.Success` vám poskytne `paymentToken`. Úplný záznam, včetně `receiptUrl`, požadované a
zúčtované částky a časových razítek, načtete pomocí `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>

Probíhající platbu zastavíte pomocí `Accept.payments.cancel(paymentToken)`, které vrací výsledný `PaymentState`.

### Vrácení platby [#issuing-a-refund]

Dokončenou platbu vrátíte pomocí `Accept.payments.refund(paymentToken, reason)`, které rovněž vrací 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 hodnot `CUSTOMER_REQUEST`, `DUPLICATE`, `FRAUDULENT` nebo `EXPIRED_CHARGE`.

## Zpracování chyb [#error-handling]

Platba, která dorazí do terminálu, ale neuspěje, **není** výjimka; přijde jako
`PaymentEvent.Result` s `PayResult.Declined`, `Canceled` nebo `Failed(reason)`. Selhání, která zabrání
vytvoření platby, přicházejí jako `PaymentEvent.CreationFailed(cause)`, kde `cause` je typovaná
`AcceptException`. Výjimky specifické pro tuto integraci:

| Výjimka                                            | Kdy nastane                                                                                  | Co dělat                                                                                |
| :------------------------------------------------- | :------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
| `PluginUnavailable`                                | Plugin Tapaya Terminal není nainstalovaný nebo se nepodařilo navázat spojení s jeho službou. | Vyzvěte k instalaci pomocí `Accept.plugin.install()`.                                   |
| `CurrencyNotAvailableForMerchant(code)`            | Obchodník nemá nastavenou požadovanou měnu.                                                  | Účtujte v povolené měně, nebo aktualizujte konfiguraci obchodníka.                      |
| `AmountBelowMinimum(minimum, currency)`            | Částka je pod nastaveným minimem.                                                            | Účtujte částku rovnou `minimum` nebo vyšší.                                             |
| `TransactionTimeoutOutOfRange(transactionTimeout)` | `transactionTimeout` byl mimo rozsah 0 až 120 sekund.                                        | Předejte hodnotu v povoleném rozsahu, nebo ji vynechte, aby se použila výchozí hodnota. |
| `LocationPermissionRequired`                       | Vaše aplikace nemá udělené oprávnění k poloze.                                               | Vyžádejte `ACCESS_FINE_LOCATION` a akci opakujte.                                       |
| `LocationUnavailable` / `LocationTimeout`          | Polohu zařízení se nepodařilo určit.                                                         | Požádejte uživatele, aby zapnul polohu, a akci opakujte.                                |
| `SessionExpired`                                   | Autentizovaná relace vypršela.                                                               | Znovu se autentizujte pomocí `Accept.auth.authenticate()`.                              |
| `NoInternetConnection`                             | Není k dispozici síťové připojení.                                                           | Požádejte uživatele, aby se znovu připojil, a akci opakujte.                            |
| `PaymentInProgress`                                | Předchozí volání `pay()` stále čeká na výsledek.                                             | Počkejte, až se aktuální platba dokončí.                                                |

`Accept.payments.status()`, `cancel()` a `refund()` jsou funkce `suspend` a tyto výjimky **vyhazují**
(místo vysílání událostí); obalte je do `try-catch`. `status()`/`cancel()`/`refund()` navíc pro neznámý token vyhazují
`PaymentNotFound`. Úplný seznam najdete v části
[Zpracování chyb](../integration/sdk_integration#error-handling).

## Časové limity [#timeouts]

Platbu kartou je nutné na terminálu dokončit krátce po jejím zahájení. Pokud zákazník kartu nikdy nepřiloží nebo se
plugin Tapaya Terminal během platby zavře, platba skončí jako `PayResult.Canceled` nebo
`PayResult.Failed` a podkladová platba se na straně serveru zruší a nikdy se nezúčtuje. Než platbu považujete za dokončenou,
vždy si ověřte konečný stav pomocí `Accept.payments.status()`.

## Integrace bez SDK [#integrate-without-the-sdk]

Pokud nemůžete Accept SDK vložit do aplikace (multiplatformní framework, tenký POS shell, webová pokladna), můžete
Tapaya Terminal ovládat přímo pomocí deeplinku. Rozdělení práce se tím mění: platbu přes
Tapaya API vytváříte **vy** a **vy** se také dotazujete na její stav, abyste zjistili výsledek; Tapaya Terminal obstarává jen práci s kartou.

Postup:

1. Vytvořte platbu ze svého backendu přes [Payments API](https://api.tapaya.com/openapi/index.html) a uložte si
   její `paymentId`.
2. Otevřete Tapaya Terminal s tímto `paymentId` pomocí [platebního deeplinku](#pay-deeplink).
3. Dotazujte se na stav platby přes [Payments API](https://api.tapaya.com/openapi/index.html), dokud neopustí
   stav `PENDING`. Deeplinky nemají zpětný kanál, takže dotazování je jediným spolehlivým zdrojem výsledku.

<Callout title="Poznámka" type="info">
  Tapaya Terminal musí být nainstalovaný, přihlášený k účtu obchodníka a nastavený pro měnu
  platby. V tomto režimu neprobíhá žádná aktivace; obchodníkem odpovědným za transakci (merchant of record) je přihlášený účet. V případě potřeby
  obchodníka přihlaste pomocí [přihlašovacího deeplinku](#login-deeplink).
</Callout>

### Schémata deeplinků [#deeplink-schemes]

Schéma určuje, které sestavení Tapaya Terminal odkaz obslouží:

| Prostředí | Schéma                  |
| :-------- | :---------------------- |
| Produkce  | `tapaya-accept`         |
| Sandbox   | `tapaya-accept-sandbox` |

Pokud se odkaz nepodaří otevřít, dané sestavení Tapaya Terminal není nainstalované.

### Platební deeplink [#pay-deeplink]

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

| Parametr dotazu | Povinné | Poznámky                                                                                                                                  |
| :-------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------- |
| `paymentId`     | **Ano** | Platba vytvořená v kroku 1.                                                                                                               |
| `amount`        | **Ano** | Částka v **hlavních jednotkách** jako celé číslo: `amount=15` naúčtuje 15,00. Pozor, tím se liší od SDK, které přijímá nejmenší jednotky. |
| `currency`      | **Ano** | Kód měny ISO 4217, např. `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>

### Přihlašovací deeplink [#login-deeplink]

Přihlaste obchodníka do Tapaya Terminal pomocí jeho `merchantToken`, identifikátoru obchodníka, který nastavíte přes `POST /merchant/auth/register`:

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

| Parametr dotazu  | Povinné | Poznámky                                                                                                                    |
| :--------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------- |
| `token`          | **Ano** | `merchantToken` obchodníka. Jde o vaše stabilní ID obchodníka, nikoli o krátkodobý token SDK z `POST /merchant/auth/login`. |
| `activationCode` | Ne      | Předvyplní aktivační kód terminálu.                                                                                         |

## Otestování integrace [#test-the-integration]

1. Inicializujte s `isProduction = false` (výchozí hodnota); platby v sandboxu nikdy nepřesouvají skutečné peníze.
2. Nainstalujte plugin Tapaya Terminal do testovacího zařízení, přihlaste se testovacím obchodníkem ze sandboxu a proveďte aktivaci.
3. Proveďte platbu na malou částku a ověřte celý tok `Success` od začátku do konce, včetně vašeho UI účtenky.
4. Vyzkoušejte cesty selhání: zamítněte platbu na terminálu, jednu zrušte a jednu zahajte v nepodporované
   měně; vaše zpracování `PayResult`/`CreationFailed` by mělo pokrýt každou z nich.

Až budete připraveni spustit ostrý provoz, nastavte v `initialize` hodnotu `isProduction = true` a autentizujte se produkčními
přihlašovacími údaji. Kód plateb se nemění.
