# App Tapaya Terminal (/it/in-store/integration/card_terminal)



Accetti pagamenti contactless con carta dalla Sua app Android affidando l'interazione con la carta all'app plugin **Tapaya Terminal**.
La vendita resta sotto il controllo della Sua app; il plugin gestisce l'avvicinamento NFC, l'inserimento del PIN e l'elaborazione
della carta, quindi il risultato torna tramite il flusso di eventi di `Accept.payments.pay()`.

Usi questa integrazione se desidera Tap to Pay senza incorporare un processore di pagamento nella Sua app. Se invece la Sua
piattaforma è abilitata a Tap to Pay incorporato, consulti la guida *Embedded Tap to Pay*.

## Opzioni di integrazione [#integration-options]

| Opzione                      | Ideale per                                     | Consegna del risultato                                                                                                             |
| :--------------------------- | :--------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| **Accept SDK** (consigliato) | App Android native                             | `Accept.payments.pay()` emette un `Flow<PaymentEvent>`: creazione, avvio e risultato finale del terminale, gestiti automaticamente |
| **Deeplink**                 | WebView, framework multipiattaforma, codici QR | Lei crea il pagamento tramite la Tapaya API e ne interroga periodicamente lo stato; il plugin si apre tramite un deeplink          |

Il percorso tramite SDK è descritto per primo; per il contratto del deeplink consulti [Integrare senza l'SDK](#integrate-without-the-sdk).

## Come funziona [#how-it-works]

1. La Sua app avvia un pagamento con `Accept.payments.pay(amount, currency)`.
2. L'SDK registra il pagamento nel backend Tapaya e avvia il plugin Tapaya Terminal sullo stesso dispositivo.
3. Il cliente avvicina la carta o il telefono allo schermo di Tapaya Terminal.
4. Tapaya Terminal conclude il pagamento con il backend Tapaya.
5. L'SDK osserva il pagamento finché non raggiunge uno stato finale ed emette `PaymentEvent.Result` con un
   `PayResult`. Se il pagamento non può essere creato affatto, emette invece `PaymentEvent.CreationFailed`, di solito
   entro un secondo.

## Prerequisiti [#prerequisites]

* Il **plugin Tapaya Terminal** installato sul dispositivo e **attivato** per il Suo esercente (consulti
  [Installare e attivare il terminale](#install-and-activate-the-terminal)).
* Un dispositivo con NFC e Android 11 (API 30) o versione successiva.
* L'Accept SDK [installato e inizializzato](../integration/sdk_integration#initialization), con l'esercente
  [che ha effettuato l'accesso](../integration/sdk_integration#step-2-authenticate).
* L'autorizzazione alla posizione (`ACCESS_FINE_LOCATION`) concessa alla Sua app in fase di esecuzione.

## Installare e attivare il terminale [#install-and-activate-the-terminal]

L'attivazione va eseguita una sola volta per dispositivo. Collega il plugin Tapaya Terminal allo stesso account esercente con cui
la Sua app ha effettuato l'accesso.

<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` è uno tra `INVALID_INTENT`, `HANDOVER_FAILED`, `CONFIRM_FAILED`,
`MERCHANT_NOT_ONBOARDED` o `UNKNOWN`.

<Callout title="Nota" type="info">
  `Accept.plugin.isInstalled()` restituisce `false` quando il plugin Tapaya Terminal non è presente. Legga lo stato
  attuale del plugin in qualsiasi momento con `Accept.plugin.status()` e protegga di conseguenza la Sua UI di pagamento.
</Callout>

`activateTerminal()` inoltre **lancia** `NoTidsAvailableForMerchant` quando all'esercente non resta alcun terminale non assegnato
(TID) da attivare, separatamente dagli esiti `ActivateTerminalResult.Failed` indicati sopra. Racchiuda la chiamata in un
`try-catch` se desidera gestire questo caso in modo distinto, ad esempio invitando l'esercente a liberare o
richiedere un altro TID.

### Verificare gli aggiornamenti del plugin [#checking-for-plugin-updates]

`Accept.plugin.updateInfo()` indica se su Google Play è disponibile una build più recente del plugin Tapaya Terminal,
così può invitare l'esercente ad aggiornarlo prima che un pagamento incontri un'incompatibilità di versione.

<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="Nota" type="info">
  L'SDK non interroga mai Google Play direttamente; l'app plugin verifica da sé tramite Play Core e comunica il risultato
  attraverso il proprio canale di stato esistente. Una build del plugin precedente a quella che ha introdotto questa funzionalità restituisce sempre
  `Unknown`.
</Callout>

## Creare un pagamento [#create-a-payment]

Gli importi sono sempre espressi nell'**unità minima della valuta**: haléř per CZK, centesimi per EUR. `1500` con
la valuta `"CZK"` addebita 15,00 Kč. Le valute sono codici ISO 4217.

`pay()` restituisce un `Flow<PaymentEvent>`; lo raccolga per osservare l'avanzamento e il risultato finale. Non lancia mai eccezioni;
gli errori si manifestano come `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>

**Parametri:**

| Parametro            | Tipo                    | Obbligatorio | Descrizione                                                                                                                                                                                                                                                                                                |
| :------------------- | :---------------------- | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `amount`             | `Long`                  | ✓            | Importo da addebitare, nell'unità minore di `currency` (ad es. centesimi per USD, haléř per CZK).                                                                                                                                                                                                          |
| `currency`           | `String`                | ✓            | Codice valuta ISO 4217.                                                                                                                                                                                                                                                                                    |
| `paymentToken`       | `String?`               |              | Riprende una sessione di pagamento esistente invece di avviarne una nuova. Il valore predefinito è `null`.                                                                                                                                                                                                 |
| `receiptConfig`      | `PaymentReceiptConfig?` |              | Sostituisce la schermata della ricevuta mostrata dopo un pagamento **approvato**, solo per questa chiamata. Il valore predefinito è `null`, che applica il valore predefinito impostato con `Accept.payments.setOptions()`.                                                                                |
| `nfcPosition`        | `NfcPositionConfig?`    |              | Sostituisce il punto indicato dall'animazione di avvicinamento, solo per questa chiamata. Il valore predefinito è `null`, che applica il valore predefinito impostato con `Accept.payments.setOptions()`.                                                                                                  |
| `metadata`           | `Map<String, String>`   |              | Dati chiave-valore associati al pagamento. Vengono inviati al plugin Tapaya Terminal nell'intent `PAY` e restituiti nel `PayResult` risultante, se il plugin li restituisce. Il valore predefinito è `emptyMap()`.                                                                                         |
| `tip`                | `Long?`                 |              | Mancia fissa, nell'unità minore di `currency`. Se impostata, il plugin salta la propria schermata interattiva per la mancia e addebita `amount + tip`. Il valore predefinito è `null` (il plugin chiede la mancia al pagatore).                                                                            |
| `transactionTimeout` | `Duration?`             |              | Sostituisce il tempo di attesa del terminale per l'avvicinamento della carta, solo per questa chiamata. Il valore predefinito è `null`, che applica il valore predefinito impostato con `Accept.payments.setOptions()`. Consulti [Impostare un timeout della transazione](#setting-a-transaction-timeout). |
| `display`            | `PaymentDisplay?`       |              | Sostituisce lo schermo su cui viene eseguita la UI di pagamento del plugin, solo per questa chiamata. Il valore predefinito è `null`, che applica il valore predefinito impostato con `Accept.payments.setOptions()`. Consulti [Scegliere il display](#targeting-a-display).                               |

Durante il pagamento il plugin Tapaya Terminal è in primo piano. La Sua app torna automaticamente in primo piano
quando il pagamento termina. Per riprendere un pagamento interrotto, passi di nuovo il relativo `paymentToken` a
`pay(amount, currency, paymentToken)`.

<Callout title="La restituzione di metadata non è garantita" type="info">
  `metadata` viene restituito in `PayResult` solo se il plugin lo restituisce, quindi lo consideri un contesto non garantito
  (ad es. un ID ordine per i Suoi log), non dati che la Sua app ha bisogno di ricevere indietro.
</Callout>

### Scegliere la valuta [#choosing-the-currency]

`pay()` addebita nella valuta `currency` che passa, come codice ISO 4217 in maiuscolo, con `amount` nell'unità minima
di quella valuta. [Valute supportate](/merchants/supported_currencies) elenca tutte le valute supportate da Tapaya, ma un esercente
di solito è abilitato solo per alcune di esse.

Chiami `Accept.merchant.availableCurrencies()` per ottenere i codici ISO 4217 per cui l'esercente è abilitato. Un pagamento in qualsiasi altra
valuta genera l'errore `CurrencyNotAvailableForMerchant`.

Ogni valuta ha inoltre un importo minimo pagabile, applicato a livello di piattaforma. Recuperi i minimi attuali con
`Accept.sdk.minimumAmounts()`, che restituisce una `List<MinimumAmount>` (`currency: String, amount: Long`, nell'unità
minore della valuta). Una chiamata a `pay()` al di sotto di tale minimo genera l'errore
`PaymentEvent.CreationFailed(AmountBelowMinimum(minimum, currency))`. Consulti
[Gestione degli errori](./sdk_integration#error-handling) per l'elenco completo delle eccezioni.

### Configurare la schermata della ricevuta [#configuring-the-receipt-screen]

`receiptConfig` determina cosa mostra il plugin Tapaya Terminal al pagatore dopo un pagamento **approvato**, per quanto
tempo resta sullo schermo e se viene stampato. Non ha alcun effetto sugli esiti rifiutati, stornati o in attesa di
storno; questi mostrano sempre la propria schermata di esito, indipendentemente da questa impostazione.

| `PaymentReceiptConfig`                                    | Descrizione                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| :-------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `DismissImmediately`                                      | Al pagatore non viene mostrato nulla.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `Show(showQrCodeReceipt, dismissBehavior, printBehavior)` | Mostra la schermata della ricevuta. `showQrCodeReceipt: Boolean = true` attiva o disattiva un codice QR con il link alla ricevuta. `dismissBehavior: DismissBehavior = DismissBehavior.Manual` determina come si chiude: `Manual` (resta finché il pagatore non la chiude) o `Delayed(delay: Duration)` (si chiude automaticamente dopo `delay` se il pagatore non l'ha chiusa prima). `printBehavior: ReceiptPrintBehavior = ReceiptPrintBehavior.OnDemand` controlla lo scontrino fisico: `Automatic` (viene stampato non appena compare la schermata), `OnDemand` (solo quando lo richiede il cassiere) o `Disabled` (non viene mai stampato). |

Imposti un valore predefinito per l'intero dispositivo una sola volta con `Accept.payments.setOptions()` e lo sostituisca per un singolo pagamento tramite
`pay(..., receiptConfig = ...)`. Finché non viene chiamato `setOptions()`, il valore predefinito è `Show` con ricevuta QR, chiusura
manuale e stampa su richiesta, in linea con il comportamento di riserva del plugin stesso.

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

### Posizionare l'animazione di avvicinamento NFC [#positioning-the-nfc-tap-animation]

`nfcPosition` è un'indicazione che comunica al plugin Tapaya Terminal dove si trova l'antenna NFC di questo dispositivo, così da poter
posizionare correttamente l'animazione “avvicini qui”. Il plugin determina prima autonomamente la posizione dell'antenna (rilevamento in tempo reale della piattaforma,
poi un database di dispositivi incluso, poi qualsiasi impostazione configurata dall'esercente direttamente nel plugin); fornisca questo valore solo
per sostituire tale determinazione, ad esempio su un dispositivo che il plugin non riesce a rilevare o che ha registrato in modo errato nel database.

| `NfcPositionConfig`                       | Descrizione                                                                                                                                                                    |
| :---------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Unspecified`                             | Nessuna sostituzione (predefinito). Il plugin determina la posizione autonomamente e mantiene qualsiasi posizione già configurata lì dall'esercente.                           |
| `OnDevice(surface, xFraction, yFraction)` | L'antenna si trova sul dispositivo stesso; l'animazione punta a `(xFraction, yFraction)` su `surface`, che può essere `OnDeviceSurface.BACK` o `OnDeviceSurface.FRONT_SCREEN`. |
| `ExternalAbove`                           | Un modulo lettore separato si trova sopra il telefono (supporto smart-POS); non c'è alcun punto sul dispositivo da indicare.                                                   |

Imposti un valore predefinito per l'intero dispositivo con `Accept.payments.setOptions(nfcPosition = ...)`, oppure lo sostituisca per singola chiamata tramite
`pay(..., nfcPosition = ...)`. `setOptions()` modifica solo le opzioni che passa, quindi `setOptions(nfcPosition = ...)`
da solo non altera un valore predefinito di `receiptConfig` impostato in precedenza, e viceversa.

### Impostare un timeout della transazione [#setting-a-transaction-timeout]

`transactionTimeout` determina per quanto tempo il plugin Tapaya Terminal attende che il cliente avvicini la carta prima
di rinunciare. Accetta qualsiasi `Duration` da zero fino a 120 secondi; qualsiasi valore al di fuori di questo intervallo si manifesta come
`TransactionTimeoutOutOfRange(transactionTimeout)` tramite `PaymentEvent.CreationFailed`, poiché `pay()` non lancia mai eccezioni
in modo sincrono.

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

Lasci il valore predefinito `null` per usare il timeout di riserva dell'app companion, di circa 60 secondi, oppure imposti un valore
predefinito per l'intero dispositivo con `Accept.payments.setOptions(transactionTimeout = ...)`.

### Scegliere il display [#targeting-a-display]

Su un dispositivo a doppio schermo (“double-sided”), un pannello è rivolto verso l'esercente e un secondo pannello, lato cliente, è rivolto verso
il pagatore. `Accept.displays` indica di quali display dispone il dispositivo in questo momento, e l'opzione `display` indica al plugin
su quale pannello eseguire la propria UI di pagamento.

<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`      | Descrizione                                                                                                 |
| :-------------------- | :---------------------------------------------------------------------------------------------------------- |
| `Default`             | Il display corrente dell'app host (comportamento predefinito, invariato sui dispositivi a schermo singolo). |
| `CustomerFacing`      | Il pannello secondario di un dispositivo a doppio schermo.                                                  |
| `Specific(displayId)` | Un display specifico, indicato dall'`id` riportato in `Accept.displays.all()`.                              |

Una destinazione che non può essere rispettata, ad esempio `CustomerFacing` su un dispositivo a schermo singolo, ricade silenziosamente su
`Default`. Imposti un valore predefinito per l'intero dispositivo con `Accept.payments.setOptions(display = ...)`, oppure lo sostituisca per singola chiamata tramite
`pay(..., display = ...)`.

<Callout title="Imposti la destinazione prima dell'autenticazione" type="info">
  Il riscaldamento (warm-up) del terminale avviato da `Accept.auth.authenticate()` segue la stessa destinazione, quindi chiami
  `setOptions(display = ...)` prima di `authenticate()` se desidera che anche il warm-up non compaia sullo schermo
  dell'esercente.
</Callout>

L'app di esempio `:dualscreen` mostra un checkout completo su due pannelli: l'esercente lavora sul pannello principale mentre
`PaymentDisplay.CustomerFacing` porta la UI di pagamento del plugin sullo schermo del cliente.

## Esito del pagamento [#payment-outcome]

L'evento finale `PaymentEvent.Result` contiene un `PayResult`. Ogni variante contiene `metadata`, che riporta quanto Lei ha
passato a `pay()` se il plugin lo restituisce (può essere `null`):

| `PayResult`                                                 | Significato                                                                                                                                                                                                        |
| :---------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Success(paymentToken, metadata, authCode, receiptDetails)` | Autorizzato e acquisito. I fondi verranno regolati a favore dell'esercente. `authCode` è il codice di autorizzazione del processore riportato dal terminale; è informativo e può mancare a seconda del processore. |
| `Declined(metadata, receiptDetails)`                        | Rifiutato dal processore della carta o dalla banca emittente.                                                                                                                                                      |
| `Canceled(metadata)`                                        | Annullato dal cliente o dall'esercente prima del completamento.                                                                                                                                                    |
| `Failed(reason, metadata)`                                  | Non completato; `reason` è un `PayFailureReason`.                                                                                                                                                                  |

`PayFailureReason` è uno tra `INVALID_INTENT`, `NOT_ACTIVATED`, `TERMINAL_INIT_FAILED`, `PAYMENT_SETUP_FAILED`,
`PLUGIN_UNAVAILABLE` o `UNKNOWN`.

### Leggere i dettagli della ricevuta [#reading-receipt-details]

`Success` e `Declined` contengono un `receiptDetails: ReceiptDetails?` facoltativo con i dati strutturati della ricevuta della carta
riportati dal terminale, pensato per chi stampa una propria ricevuta ufficiale della carta invece di affidarsi soltanto
alla pagina ospitata di `PaymentStatus.receiptUrl`.

| Campo                  | Tipo      | Descrizione                                      |
| :--------------------- | :-------- | :----------------------------------------------- |
| `merchantId`           | `String?` | ID esercente riportato dal processore.           |
| `terminalId`           | `String?` | ID terminale riportato dal processore.           |
| `cardBrand`            | `String?` | Circuito della carta, ad es. Visa o Mastercard.  |
| `appLabel`             | `String?` | Etichetta dell'applicazione EMV.                 |
| `aid`                  | `String?` | Identificatore dell'applicazione EMV.            |
| `maskedPan`            | `String?` | Numero di carta mascherato.                      |
| `responseCode`         | `String?` | Codice di risposta del processore.               |
| `transactionTimestamp` | `String?` | Timestamp della transazione in formato ISO-8601. |

Ogni campo è facoltativo e non garantito; il chiamante deve essere pronto a gestire l'assenza di uno qualsiasi di essi, o dello stesso `receiptDetails`.

### Leggere i dettagli del pagamento [#reading-payment-details]

`PayResult.Success` fornisce il `paymentToken`. Recuperi il record completo, inclusi `receiptUrl`, gli importi richiesti e
regolati e i timestamp, con `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>

Interrompa un pagamento in corso con `Accept.payments.cancel(paymentToken)`, che restituisce il `PaymentState` risultante.

### Emettere un rimborso [#issuing-a-refund]

Rimborsi un pagamento concluso con `Accept.payments.refund(paymentToken, reason)`, che restituisce anch'esso il
`PaymentState` risultante.

<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` è uno tra `CUSTOMER_REQUEST`, `DUPLICATE`, `FRAUDULENT` o `EXPIRED_CHARGE`.

## Gestione degli errori [#error-handling]

Un pagamento che raggiunge il terminale ma non va a buon fine **non** è un'eccezione; arriva come
`PaymentEvent.Result` con `PayResult.Declined`, `Canceled` o `Failed(reason)`. Gli errori che impediscono
la creazione del pagamento arrivano come `PaymentEvent.CreationFailed(cause)`, dove `cause` è un'`AcceptException`
tipizzata. Quelle specifiche di questa integrazione:

| Eccezione                                          | Quando si verifica                                                                             | Cosa fare                                                                                   |
| :------------------------------------------------- | :--------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ |
| `PluginUnavailable`                                | Il plugin Tapaya Terminal non è installato o non è stato possibile collegarsi al suo servizio. | Inviti all'installazione con `Accept.plugin.install()`.                                     |
| `CurrencyNotAvailableForMerchant(code)`            | L'esercente non è configurato per la valuta richiesta.                                         | Addebiti in una valuta abilitata, oppure aggiorni la configurazione dell'esercente.         |
| `AmountBelowMinimum(minimum, currency)`            | L'importo è inferiore al minimo configurato.                                                   | Addebiti un importo pari o superiore a `minimum`.                                           |
| `TransactionTimeoutOutOfRange(transactionTimeout)` | `transactionTimeout` era al di fuori dell'intervallo da 0 a 120 secondi.                       | Passi un valore compreso nell'intervallo, oppure lo ometta per usare il valore predefinito. |
| `LocationPermissionRequired`                       | Alla Sua app non è stata concessa l'autorizzazione alla posizione.                             | Richieda `ACCESS_FINE_LOCATION`, quindi riprovi.                                            |
| `LocationUnavailable` / `LocationTimeout`          | Non è stato possibile determinare la posizione del dispositivo.                                | Chieda all'utente di attivare la posizione e riprovi.                                       |
| `SessionExpired`                                   | La sessione autenticata è scaduta.                                                             | Ripeta l'autenticazione con `Accept.auth.authenticate()`.                                   |
| `NoInternetConnection`                             | Nessuna connessione di rete.                                                                   | Chieda all'utente di riconnettersi e riprovi.                                               |
| `PaymentInProgress`                                | Una chiamata `pay()` precedente è ancora in attesa di un risultato.                            | Attenda che il pagamento in corso termini.                                                  |

`Accept.payments.status()`, `cancel()` e `refund()` sono funzioni `suspend` e **lanciano** queste eccezioni
(invece di emettere eventi); le racchiuda in un `try-catch`. `status()`/`cancel()`/`refund()` lanciano inoltre
`PaymentNotFound` per un token sconosciuto. Consulti l'elenco completo in
[Gestione degli errori](../integration/sdk_integration#error-handling).

## Timeout [#timeouts]

Un pagamento con carta deve essere completato sul terminale poco dopo il suo avvio. Se il cliente non avvicina mai la carta, o se il
plugin Tapaya Terminal viene chiuso durante il pagamento, il pagamento termina come `PayResult.Canceled` o
`PayResult.Failed`, e il pagamento sottostante viene annullato lato server e non viene mai regolato. Verifichi sempre lo
stato finale con `Accept.payments.status()` prima di considerare completato un pagamento.

## Integrare senza l'SDK [#integrate-without-the-sdk]

Se non può incorporare l'Accept SDK (un framework multipiattaforma, una shell POS leggera, una cassa basata sul web), può controllare
Tapaya Terminal direttamente con un deeplink. La suddivisione del lavoro cambia: è **Lei** a creare il pagamento tramite
la Tapaya API ed è **Lei** a interrogarne periodicamente lo stato per conoscere il risultato; Tapaya Terminal gestisce solo l'interazione con la carta.

Il flusso:

1. Crei un pagamento dal Suo backend tramite la [Payments API](https://api.tapaya.com/openapi/index.html) e conservi
   il relativo `paymentId`.
2. Apra Tapaya Terminal con quel `paymentId` usando il [deeplink di pagamento](#pay-deeplink).
3. Interroghi periodicamente lo stato del pagamento tramite la [Payments API](https://api.tapaya.com/openapi/index.html) finché non esce
   da `PENDING`. I deeplink non hanno un canale di ritorno, quindi l'interrogazione periodica è l'unica fonte attendibile.

<Callout title="Nota" type="info">
  Tapaya Terminal deve essere installato, con accesso effettuato all'account esercente e configurato per la valuta del
  pagamento. In questa modalità non c'è alcun passaggio di attivazione; il merchant of record è l'account che ha effettuato l'accesso. Se necessario, faccia
  accedere un esercente con il [deeplink di accesso](#login-deeplink).
</Callout>

### Schemi dei deeplink [#deeplink-schemes]

Lo schema determina quale build di Tapaya Terminal gestisce il link:

| Ambiente   | Schema                  |
| :--------- | :---------------------- |
| Produzione | `tapaya-accept`         |
| Sandbox    | `tapaya-accept-sandbox` |

Se un link non viene risolto, quella build di Tapaya Terminal non è installata.

### Deeplink di pagamento [#pay-deeplink]

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

| Parametro di query | Obbligatorio | Note                                                                                                                                              |
| :----------------- | :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| `paymentId`        | **Sì**       | Il pagamento creato nel passaggio 1.                                                                                                              |
| `amount`           | **Sì**       | Importo in **unità principali** come numero intero: `amount=15` addebita 15,00. Attenzione: questo differisce dall'SDK, che accetta unità minori. |
| `currency`         | **Sì**       | Codice valuta ISO 4217, ad es. `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>

### Deeplink di accesso [#login-deeplink]

Faccia accedere un esercente a Tapaya Terminal con il suo `merchantToken`, l'identificatore dell'esercente impostato con `POST /merchant/auth/register`:

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

| Parametro di query | Obbligatorio | Note                                                                                                                               |
| :----------------- | :----------- | :--------------------------------------------------------------------------------------------------------------------------------- |
| `token`            | **Sì**       | Il `merchantToken` dell'esercente. È il Suo ID esercente stabile, non il token SDK di breve durata di `POST /merchant/auth/login`. |
| `activationCode`   | No           | Precompila il codice di attivazione del terminale.                                                                                 |

## Testare l'integrazione [#test-the-integration]

1. Inizializzi con `isProduction = false` (il valore predefinito); i pagamenti in sandbox non muovono mai denaro reale.
2. Installi il plugin Tapaya Terminal sul dispositivo di test, acceda con un esercente della sandbox ed esegua l'attivazione.
3. Esegua un pagamento di importo ridotto e verifichi il flusso `Success` dall'inizio alla fine, inclusa la Sua UI della ricevuta.
4. Provi i percorsi di errore: rifiuti un pagamento sul terminale, ne annulli uno e ne avvii uno con una valuta
   non supportata; la Sua gestione di `PayResult`/`CreationFailed` dovrebbe coprire ciascun caso.

Quando è pronto per andare live, imposti `isProduction = true` in `initialize` ed esegua l'autenticazione con le credenziali di
produzione. Il codice dei pagamenti non cambia.
