# Tag NFC (/it/in-store/integration/nfc_tags)





<div className="bg-background relative h-auto w-full overflow-hidden rounded-lg border mt-8">
  <FlickeringGrid className="absolute inset-0 z-0 size-full" squareSize="4" gridGap="6" maxOpacity="0.1" flickerChance="0.1" height="600" width="2000" />

  <NfcTagSessionDiagram />
</div>

Legga e scriva tag NFC, come carte fedeltà, badge di accesso, adesivi e smart card, direttamente dalla Sua
app Android. L'SDK comunica con il lettore del dispositivo stesso: l'NFC di Android su un telefono, oppure il lettore
contactless di un terminale Sunmi della serie P. Non c'è alcun plugin di mezzo.

Le sessioni con i tag sono distribuite in artefatti facoltativi, `com.tapaya:accept-nfc` e, per i terminali Sunmi,
`com.tapaya:accept-nfc-sunmi`. Li aggiunga solo se Le servono; i pagamenti con carta funzionano allo stesso modo con o senza di essi.

## Dispositivi supportati [#supported-devices]

| Dispositivo                       | Lettore                                                                         | Artefatti necessari               |
| :-------------------------------- | :------------------------------------------------------------------------------ | :-------------------------------- |
| Telefono o tablet Android con NFC | NFC di Android                                                                  | `accept-nfc`                      |
| Terminale Sunmi della serie P     | Il lettore contactless del terminale, tramite il servizio di pagamento di Sunmi | `accept-nfc` e `accept-nfc-sunmi` |
| Qualsiasi altro dispositivo       | Nessuno                                                                         | Non supportato                    |

I terminali Sunmi della serie P non dispongono dell'NFC di Android; la loro antenna si trova dietro il modulo di pagamento. `accept-nfc-sunmi`
la raggiunge tramite il servizio di pagamento di Sunmi e subentra all'NFC di Android ogni volta che tale servizio è installato.

Su un dispositivo senza un lettore utilizzabile, `isSupported()` restituisce `false` e `openSession()` genera l'errore
`NOT_SUPPORTED`.

<Callout title="I telefoni hanno alcuni limiti aggiuntivi" type="info">
  I telefoni leggono i tag solo per l'activity in primo piano, quindi una sessione si chiude quando quell'activity esce dal primo piano.
  MIFARE Classic funziona solo sui telefoni con un controller NFC NXP; altrove le sue operazioni generano l'errore
  `NOT_SUPPORTED`. Per i tag ISO-DEP, `atsHex` contiene solo i byte storici (historical bytes) dell'ATS, perché Android non espone
  il resto.
</Callout>

## Tag supportati [#supported-tags]

| Tipo di tag                                | Esempi                                                |
| :----------------------------------------- | :---------------------------------------------------- |
| [NFC Type 2](#nfc-type-2)                  | NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C |
| [MIFARE Classic](#mifare-classic)          | MIFARE Classic Mini, 1K, 4K                           |
| [Smart card ISO-DEP](#iso-dep-smart-cards) | MIFARE DESFire                                        |

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

1. La Sua app chiama `Accept.nfc.openSession(activity)`.
2. L'SDK attiva il lettore del dispositivo e attende un avvicinamento.
3. Il cliente avvicina un tag e `openSession()` restituisce una `TagSession` con l'UID e la famiglia del tag.
4. La Sua app legge e scrive tramite la sessione, un comando alla volta.
5. La Sua app chiama `close()`, che libera il lettore per i pagamenti e per altre sessioni con i tag.

<Callout title="I pagamenti hanno sempre la priorità" type="warn">
  Un dispositivo ha un solo lettore contactless, che una sessione con il tag e un pagamento con carta non possono condividere. L'avvio di un pagamento
  con `Accept.payments.pay()` chiude la Sua sessione, e `openSession()` genera l'errore `READER_BUSY` finché il pagamento non è
  terminato. Sia pronto alla chiusura di una sessione in qualsiasi momento; si veda [Chiudere una sessione](#end-a-session).
</Callout>

## Prerequisiti [#prerequisites]

* Un [dispositivo supportato](#supported-devices).
* L'Accept SDK [installato e inizializzato](../integration/sdk_integration#initialization). Chiamare `isSupported()` o
  `openSession()` prima di `Accept.initialize` lancia `NotInitialized`.

Le sessioni con i tag non richiedono che un esercente abbia effettuato l'accesso, né il plugin Tapaya Terminal, né l'autorizzazione alla posizione. Gli
artefatti aggiungono al manifest ciò di cui hanno bisogno:

* `accept-nfc` aggiunge l'autorizzazione `NFC` e contrassegna `android.hardware.nfc` come **non obbligatorio**, così Play mantiene la Sua app
  disponibile sui terminali Sunmi, che non dispongono dell'NFC di Android.
* `accept-nfc-sunmi` aggiunge l'autorizzazione `com.sunmi.perm.CONTACTLESS_CARD` e una voce `<queries>` per il servizio di pagamento
  di Sunmi. Incorpora Sunmi PayLib.

## Integrazione [#integrate]

<div className="fd-steps [&_h3]:fd-step">
  ### Installazione [#install]

  ```kotlin title="build.gradle.kts"
  dependencies {
      implementation("com.tapaya:accept-nfc:1.20.0")

      // Only if you run on Sunmi P-series terminals
      implementation("com.tapaya:accept-nfc-sunmi:1.20.0")
  }
  ```

  Se dichiara anche `com.tapaya:accept`, mantenga tutti gli artefatti alla stessa versione.

  `Accept.nfc` è una proprietà di estensione (extension property); la importi prima di usarla:

  ```kotlin
  import com.tapaya.accept.nfc.nfc
  ```

  ### Verificare il supporto [#check-support]

  Chiami `isSupported()` per decidere se mostrare o meno la funzionalità dei tag:

  ```kotlin
  if (Accept.nfc.isSupported()) {
      showScanTagButton()
  }
  ```

  `isSupported()` non richiede alcun avvicinamento e non interagisce con il lettore, quindi può essere chiamato in sicurezza in qualsiasi momento. Indica se il dispositivo
  dispone di un lettore che l'SDK può usare. Restituisce `true` su un telefono con l'NFC disattivato; in tal caso `openSession()` genera
  l'errore `NFC_DISABLED`. Lancia `NotInitialized` se `Accept.initialize` non è ancora stato eseguito.

  ### Aprire una sessione [#open-a-session]

  Passi l'activity che si trova in primo piano ed è nello stato resumed:

  ```kotlin
  val session = try {
      Accept.nfc.openSession(activity)
  } catch (e: TagSessionException) {
      when (e.reason) {
          TagErrorReason.TIMEOUT -> showMessage("No tag detected. Try again.")
          TagErrorReason.READER_BUSY -> showMessage("The reader is busy. Try again soon.")
          TagErrorReason.NFC_DISABLED -> promptToEnableNfc()
          else -> showMessage("Can't read tags on this device.")
      }
      return
  }

  try {
      showTag(session.info.uidHex, session.info.family)
      // read and write...
  } finally {
      session.close()
  }
  ```

  `openSession()` resta sospeso finché non viene avvicinato un tag o finché non scade `tapWait` (30 secondi per impostazione predefinita). Durante l'attesa
  mostri un invito come “Avvicini la carta”. Lancia `IllegalStateException` se `activity` non è nello stato resumed.

  Su un telefono l'activity è importante: Android legge i tag solo per essa, e l'uscita dal primo piano interrompe l'attesa o chiude
  la sessione con `ACTIVITY_PAUSED`. I terminali Sunmi leggono i tag indipendentemente da ciò che è in primo piano, ma l'activity va
  passata comunque.

  Per interrompere l'attesa, ad esempio quando il cliente tocca **Cancel**, annulli la coroutine che ha chiamato `openSession()`.
  Il lettore smette di attendere e Lei può aprire subito una nuova sessione.

  #### Leggere le informazioni della sessione [#reading-session-info]

  `session.info` descrive il tag avvicinato:

  | Campo            | Descrizione                                                                                   |
  | :--------------- | :-------------------------------------------------------------------------------------------- |
  | `uidHex`         | L'UID del tag in esadecimale maiuscolo, ad es. `04A1B2C3D4E5F6`.                              |
  | `family`         | La `TagFamily`, che indica quali operazioni sono applicabili.                                 |
  | `sak`, `atqaHex` | Identificazione di basso livello restituita al rilevamento del tag.                           |
  | `atsHex`         | ATS per i tag `ISO_DEP`; altrimenti `null`.                                                   |
  | `memoryBytes`    | Dimensione della memoria utente, se il lettore è in grado di determinarla; altrimenti `null`. |
  | `versionHex`     | Risposta a `GET_VERSION`, per i tag che lo supportano; altrimenti `null`.                     |

  ### Leggere e scrivere un tag [#read-and-write-a-tag]

  Scelga il ramo in base a `session.info.family`, quindi usi le operazioni previste per quel tipo di tag:

  ```kotlin
  when (session.info.family) {
      TagFamily.NTAG, TagFamily.ULTRALIGHT, TagFamily.ULTRALIGHT_EV1,
      TagFamily.ULTRALIGHT_C, TagFamily.TYPE2_OTHER -> readType2Tag(session)
      TagFamily.MIFARE_CLASSIC_MINI, TagFamily.MIFARE_CLASSIC_1K,
      TagFamily.MIFARE_CLASSIC_4K -> readClassicCard(session)
      TagFamily.ISO_DEP -> readSmartCard(session)
      else -> showMessage("This tag isn't supported.")
  }
  ```

  Un comando non adatto al tag avvicinato genera l'errore `NOT_SUPPORTED`, e la sessione resta aperta.

  #### NFC Type 2 [#nfc-type-2]

  NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C e altri tag Type 2 (`TYPE2_OTHER`).

  | Operazione               | Cosa fa                                                                                                                                                                   |
  | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
  | `readPages(startPage)`   | Restituisce 16 byte: quattro pagine da 4 byte a partire da `startPage`.                                                                                                   |
  | `writePage(page, data)`  | Scrive una pagina da 4 byte.                                                                                                                                              |
  | `passwordAuth(password)` | Solo NTAG e Ultralight EV1. Sblocca le pagine protette con una password di 4 byte e restituisce il PACK di 2 byte, che può usare per verificare che il tag sia autentico. |
  | `ultralightCAuth(key)`   | Solo Ultralight C. Esegue l'autenticazione con una chiave 3DES di 16 byte.                                                                                                |
  | `transceiveType2(frame)` | Invia un frame Type 2 grezzo, per i comandi senza un metodo dedicato, e restituisce la risposta del tag.                                                                  |

  ```kotlin
  // Protected NTAG and Ultralight EV1 tags only
  session.passwordAuth(password = byteArrayOf(0x12, 0x34, 0x56, 0x78))

  val pages = session.readPages(startPage = 4)  // pages 4 to 7
  session.writePage(page = 4, data = byteArrayOf(0x01, 0x02, 0x03, 0x04))

  // Raw GET_VERSION command
  val version = session.transceiveType2(byteArrayOf(0x60))
  ```

  #### MIFARE Classic [#mifare-classic]

  MIFARE Classic Mini, 1K e 4K. Autentichi un settore prima di leggerne o scriverne i blocchi.

  | Operazione                                 | Cosa fa                                                                                  |
  | :----------------------------------------- | :--------------------------------------------------------------------------------------- |
  | `authenticateClassic(block, keyType, key)` | Autentica il settore che contiene `block` con una chiave di 6 byte.                      |
  | `readBlock(block)`                         | Restituisce i 16 byte del blocco.                                                        |
  | `writeBlock(block, data)`                  | Scrive 16 byte nel blocco.                                                               |
  | `increment`, `decrement`, `restore`        | Inseriscono il risultato di un'operazione su un value block nel transfer buffer del tag. |
  | `transfer(block)`                          | Scrive il transfer buffer in un blocco.                                                  |

  ```kotlin
  session.authenticateClassic(block = 4, keyType = MifareKeyType.A, key = sectorKey)
  val block = session.readBlock(4)
  session.writeBlock(4, newContent)

  // Value blocks: change the value, then write it back
  session.decrement(block = 5, value = 1)
  session.transfer(block = 5)
  ```

  Una chiave errata genera l'errore `AUTH_FAILED` e l'SDK riseleziona il tag automaticamente, così può provare subito la chiave
  successiva:

  ```kotlin
  val key = candidateKeys.firstOrNull { key ->
      try {
          session.authenticateClassic(block = 4, keyType = MifareKeyType.A, key = key)
          true
      } catch (e: TagSessionException) {
          if (e.reason != TagErrorReason.AUTH_FAILED) throw e
          false
      }
  } ?: error("no key for sector 1")
  ```

  #### Smart card ISO-DEP [#iso-dep-smart-cards]

  MIFARE DESFire e altre carte ISO 14443-4.

  | Operazione           | Cosa fa                                                                        |
  | :------------------- | :----------------------------------------------------------------------------- |
  | `transmitApdu(apdu)` | Invia un'APDU di comando e restituisce la risposta: i dati seguiti da SW1 SW2. |

  ```kotlin
  val select = byteArrayOf(0x00, 0xA4.toByte(), 0x04, 0x00, 0x07,
      0xA0.toByte(), 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x00)
  val response = session.transmitApdu(select)
  val sw = response.takeLast(2)   // [0x90, 0x00] on success
  ```

  ### Chiudere una sessione [#end-a-session]

  Chiami `close()` non appena ha terminato, idealmente da un blocco `finally`. Finché non lo fa, nessun pagamento può usare il
  lettore. `close()` può essere chiamato in sicurezza più di una volta e non lancia mai eccezioni per una sessione già chiusa.

  Una sessione può anche chiudersi da sola. `session.ended` è uno `StateFlow` che resta `null` finché la sessione è attiva,
  poi emette il motivo della chiusura:

  ```kotlin
  session.ended.filterNotNull().first().let { reason ->
      when (reason) {
          TagSessionEndReason.CARD_REMOVED -> showMessage("Card removed")
          TagSessionEndReason.PAYMENT -> showMessage("A payment took over the reader")
          TagSessionEndReason.ACTIVITY_PAUSED -> showMessage("Keep the app in the foreground")
          else -> Unit
      }
  }
  ```

  | `TagSessionEndReason` | Quando si verifica                                                                                                              |
  | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------ |
  | `CLOSED`              | La Sua app ha chiamato `close()`.                                                                                               |
  | `CARD_REMOVED`        | Il tag è uscito dal campo e non è rientrato entro il timeout `reselect`.                                                        |
  | `IDLE_TIMEOUT`        | Nessun comando è arrivato entro il timeout `idle` (10 secondi per impostazione predefinita).                                    |
  | `PAYMENT`             | È iniziato un pagamento tramite `Accept.payments.pay()`. I pagamenti hanno sempre la priorità.                                  |
  | `ACTIVITY_PAUSED`     | Solo telefoni. L'activity con cui ha aperto la sessione è uscita dal primo piano e Android ha smesso di leggere i tag per essa. |
  | `READER_LOST`         | Solo terminali Sunmi. La connessione al servizio di pagamento di Sunmi si è interrotta.                                         |

  Una volta chiusa una sessione, ogni comando successivo genera l'errore `SESSION_ENDED`. Chiami di nuovo `openSession()` per un nuovo
  avvicinamento.
</div>

## Gestione degli errori [#error-handling]

Gli errori relativi ai tag lanciano `TagSessionException`, il cui `reason` indica cosa fare dopo. Chiamare l'API prima che
l'SDK sia configurato lancia `NotInitialized`. `TagSessionException` **non** è un'`AcceptException`, quindi intercetti le due
separatamente:

```kotlin
try {
    val session = Accept.nfc.openSession(activity)
    // ...
} catch (e: TagSessionException) {
    // reason-specific handling, see the tables below
} catch (e: NotInitialized) {
    // Accept.initialize hasn't been called
}
```

<Callout title="Ripeta l'autenticazione quando authLost è impostato" type="warn">
  Con qualsiasi motivo, il flag `authLost` dell'eccezione può essere impostato. In tal caso, ripeta l'autenticazione prima di inviare
  un altro comando alla memoria protetta; su DESFire, riselezioni anche l'applicazione.
</Callout>

Un argomento chiave, blocco o pagina di lunghezza o segno errati, oppure un frame o un'APDU vuoti, lancia
`IllegalArgumentException` prima che qualcosa raggiunga il tag.

### Apertura di una sessione [#opening-a-session]

| `reason`        | Quando si verifica                                                                                                                          | Cosa fare                                                                                                                                   |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
| `TIMEOUT`       | Nessun avvicinamento entro `tapWait`.                                                                                                       | Ripeta l'invito, oppure rinunci.                                                                                                            |
| `READER_BUSY`   | Un pagamento o un'altra sessione sta usando il lettore.                                                                                     | Riprovi più tardi; non esiste una coda.                                                                                                     |
| `SESSION_ENDED` | Durante l'attesa dell'avvicinamento è iniziato un pagamento, oppure l'activity è uscita dal primo piano.                                    | Apra una nuova sessione quando il pagamento è terminato o l'app è tornata in primo piano.                                                   |
| `NFC_DISABLED`  | Solo telefoni. L'NFC è disattivato nelle impostazioni di sistema.                                                                           | Chieda all'utente di attivare l'NFC.                                                                                                        |
| `VENDOR_ERROR`  | Terminali Sunmi: il servizio di pagamento non è disponibile. `vendorCode` contiene il codice restituito dal servizio, se ne ha fornito uno. | Riprovi. Se il problema persiste, lo segnali all'[assistenza per sviluppatori](/support/contacts#developer-support) indicando `vendorCode`. |
| `NOT_SUPPORTED` | Il dispositivo non dispone di un lettore che l'SDK possa usare.                                                                             | Nasconda la funzionalità quando `isSupported()` restituisce `false`.                                                                        |

### Invio di un comando [#sending-a-command]

| `reason`        | Quando si verifica                                                                                                      | Cosa fare                                                                                                              |
| :-------------- | :---------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- |
| `TIMEOUT`       | Il tag non ha risposto in tempo; è stato riselezionato.                                                                 | Riprovi. Se `authLost` è impostato, ripeta prima l'autenticazione.                                                     |
| `NO_RESPONSE`   | Il tag non ha risposto.                                                                                                 | Riprovi se il tag è ancora nel campo.                                                                                  |
| `AUTH_FAILED`   | Chiave o password errata.                                                                                               | Provi la chiave successiva; il tag è già stato riselezionato.                                                          |
| `NAK`           | Il tag ha rifiutato il comando: pagina fuori intervallo, memoria protetta o comando non supportato.                     | Corregga il comando. La sessione continua.                                                                             |
| `NOT_SUPPORTED` | Il comando non è adatto a questa famiglia di tag, o al controller NFC di questo telefono (MIFARE Classic richiede NXP). | Verifichi `session.info.family`. La sessione continua.                                                                 |
| `VENDOR_ERROR`  | L'SDK del lettore stesso ha segnalato un guasto; `vendorCode` ne contiene il codice.                                    | Riprovi, poi lo segnali all'[assistenza per sviluppatori](/support/contacts#developer-support) indicando `vendorCode`. |

### Sessione terminata [#session-over]

| `reason`        | Quando si verifica                                          | Cosa fare                                                       |
| :-------------- | :---------------------------------------------------------- | :-------------------------------------------------------------- |
| `CARD_REMOVED`  | Il tag è uscito dal campo durante il comando.               | Chieda di nuovo di avvicinare il tag e apra una nuova sessione. |
| `SESSION_ENDED` | È stato inviato un comando dopo la chiusura della sessione. | Apra una nuova sessione.                                        |

## Timeout [#timeouts]

Per modificare i valori predefiniti, passi `TagTimeouts` a `openSession()`:

```kotlin
Accept.nfc.openSession(activity, TagTimeouts(tapWait = 15.seconds, idle = 30.seconds))
```

| Timeout     | Predefinito | Massimo | Si applica a                                                   |
| :---------- | :---------- | :------ | :------------------------------------------------------------- |
| `tapWait`   | 30 s        | 60 s    | Attesa dell'avvicinamento in `openSession()`                   |
| `operation` | 1 s         | 5 s     | Ogni comando sul tag                                           |
| `reselect`  | 1 s         | 5 s     | Riselezione del tag dopo un comando non riuscito               |
| `idle`      | 10 s        | 60 s    | Tempo tra un comando e l'altro prima che la sessione si chiuda |

Ogni valore deve essere maggiore di zero. Creare `TagTimeouts` con un valore al di fuori di questi intervalli lancia
`IllegalArgumentException`.

## Da sapere [#good-to-know]

* **Un solo lettore.** Il dispositivo ha un solo lettore contactless, quindi è possibile una sola sessione con il tag o una sola attesa di avvicinamento alla volta. Un secondo
  `openSession()` genera l'errore `READER_BUSY`. I pagamenti avviati con `Accept.payments.pay()` chiudono prima la sessione;
  i pagamenti avviati al di fuori dell'SDK non possono essere coordinati, quindi mantenga le sessioni brevi.
* **Annullare un comando non lo annulla sul tag.** Nel momento in cui annulla la coroutine, il comando ha già raggiunto il tag
  e viene comunque eseguito; viene scartato solo il suo risultato. Un `writeBlock` annullato può comunque modificare il tag.
* **I comandi vengono eseguiti in ordine.** I metodi della sessione possono essere chiamati da qualsiasi coroutine; raggiungono il tag uno alla volta,
  nell'ordine in cui li chiama.
* **I dati dei tag restano fuori dai log.** L'SDK non registra mai nei log chiavi, password o dati dei tag e maschera gli UID (ad es.
  `04A1…E5F6`). Il modo in cui la Sua app salva e mostra i dati dei tag dipende da Lei.

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

Su un telefono e, se supporta i terminali Sunmi della serie P, anche su uno di questi:

1. Verifichi che `isSupported()` restituisca `true` su entrambi e `false` su un dispositivo senza NFC.
2. Avvicini un tag NTAG o Ultralight e verifichi che la sessione riporti l'UID corretto e che `readPages(0)` restituisca 16
   byte. Poi esegua `writePage` e rilegga la pagina.
3. Su MIFARE Classic, esegua l'autenticazione con una chiave errata e verifichi `AUTH_FAILED`, poi con quella corretta.
4. Annulli `openSession()` durante l'attesa dell'avvicinamento e apra subito una nuova sessione.
5. Avvii un pagamento durante una sessione: la sessione dovrebbe chiudersi come `PAYMENT` e il pagamento andare a buon fine. Allontani il
   tag durante un'altra sessione: la sessione dovrebbe chiudersi come `CARD_REMOVED`.
6. Su un telefono, disattivi l'NFC e verifichi `NFC_DISABLED`. Durante una sessione, porti l'app in background e
   verifichi che la sessione si chiuda come `ACTIVITY_PAUSED`.
