# NFC tagy (/sk/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>

Čítajte a zapisujte NFC tagy, napríklad vernostné karty, prístupové karty, nálepky a čipové karty, priamo zo svojej
aplikácie pre Android. SDK komunikuje s vlastnou čítačkou zariadenia: na telefóne je to Android NFC, na termináli Sunmi radu P
jeho bezkontaktná čítačka. Nie je medzi nimi žiadny plugin.

Relácie s tagmi sú súčasťou voliteľných artefaktov `com.tapaya:accept-nfc` a pre terminály Sunmi
`com.tapaya:accept-nfc-sunmi`. Pridajte ich len vtedy, keď ich potrebujete; platby kartou fungujú s nimi aj bez nich rovnako.

## Podporované zariadenia [#supported-devices]

| Zariadenie                             | Čítačka                                                   | Potrebné artefakty                |
| :------------------------------------- | :-------------------------------------------------------- | :-------------------------------- |
| Telefón alebo tablet s Androidom a NFC | Android NFC                                               | `accept-nfc`                      |
| Terminál Sunmi radu P                  | Bezkontaktná čítačka terminálu, cez platobnú službu Sunmi | `accept-nfc` a `accept-nfc-sunmi` |
| Akékoľvek iné zariadenie               | Žiadna                                                    | Nepodporované                     |

Terminály Sunmi radu P nemajú Android NFC; ich anténa sa nachádza za platobným modulom. `accept-nfc-sunmi`
k nej pristupuje cez platobnú službu Sunmi a vždy, keď je táto služba nainštalovaná, nahrádza Android NFC.

Na zariadení bez použiteľnej čítačky vracia `isSupported()` hodnotu `false` a `openSession()` zlyhá s
`NOT_SUPPORTED`.

<Callout title="Telefóny majú niekoľko ďalších obmedzení" type="info">
  Telefóny čítajú tagy len pre aktivitu v popredí, takže relácia skončí, keď táto aktivita popredie opustí.
  MIFARE Classic funguje len na telefónoch s NFC radičom NXP; inde jeho operácie zlyhajú s
  `NOT_SUPPORTED`. Pri tagoch ISO-DEP obsahuje `atsHex` len historické bajty ATS, pretože Android zvyšok
  nesprístupňuje.
</Callout>

## Podporované tagy [#supported-tags]

| Typ tagu                                     | Príklady                                              |
| :------------------------------------------- | :---------------------------------------------------- |
| [NFC Type 2](#nfc-type-2)                    | NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C |
| [MIFARE Classic](#mifare-classic)            | MIFARE Classic Mini, 1K, 4K                           |
| [Čipové karty ISO-DEP](#iso-dep-smart-cards) | MIFARE DESFire                                        |

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

1. Vaša aplikácia zavolá `Accept.nfc.openSession(activity)`.
2. SDK zapne čítačku zariadenia a čaká na priloženie.
3. Zákazník priloží tag a `openSession()` vráti `TagSession` s UID a rodinou tagu.
4. Vaša aplikácia číta a zapisuje prostredníctvom relácie, vždy po jednom príkaze.
5. Vaša aplikácia zavolá `close()`, čím uvoľní čítačku pre platby a ďalšie relácie s tagmi.

<Callout title="Platby majú vždy prednosť" type="warn">
  Zariadenie má jednu bezkontaktnú čítačku, ktorú relácia s tagom a platba kartou nemôžu zdieľať. Spustenie platby
  pomocou `Accept.payments.pay()` vašu reláciu ukončí a `openSession()` zlyháva s `READER_BUSY`, kým platba
  neskončí. Počítajte s tým, že relácia môže skončiť kedykoľvek; pozrite [Ukončenie relácie](#end-a-session).
</Callout>

## Predpoklady [#prerequisites]

* [Podporované zariadenie](#supported-devices).
* Accept SDK [nainštalované a inicializované](../integration/sdk_integration#initialization). Volanie `isSupported()` alebo
  `openSession()` pred `Accept.initialize` vyhodí `NotInitialized`.

Relácie s tagmi nevyžadujú prihláseného obchodníka, plugin Tapaya Terminal ani oprávnenie na polohu. Artefakty
si do vášho manifestu pridajú, čo potrebujú:

* `accept-nfc` pridá oprávnenie `NFC` a označí `android.hardware.nfc` ako **nepovinné**, takže Play ponechá vašu aplikáciu
  dostupnú aj na termináloch Sunmi, ktoré Android NFC nemajú.
* `accept-nfc-sunmi` pridá oprávnenie `com.sunmi.perm.CONTACTLESS_CARD` a záznam `<queries>` pre platobnú službu
  Sunmi. Obsahuje knižnicu Sunmi PayLib.

## Integrácia [#integrate]

<div className="fd-steps [&_h3]:fd-step">
  ### Inštalácia [#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")
  }
  ```

  Ak deklarujete aj `com.tapaya:accept`, udržiavajte všetky artefakty v rovnakej verzii.

  `Accept.nfc` je extension property; pred použitím ju naimportujte:

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

  ### Overenie podpory [#check-support]

  Zavolajte `isSupported()` a podľa výsledku rozhodnite, či funkciu pre tagy vôbec zobraziť:

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

  `isSupported()` nevyžaduje priloženie a čítačku nijako nevyužíva, takže ju môžete bezpečne volať kedykoľvek. Udáva, či
  má zariadenie čítačku, ktorú SDK dokáže použiť. Na telefóne s vypnutým NFC vracia `true`; `openSession()` potom zlyhá
  s `NFC_DISABLED`. Ak `Accept.initialize` ešte neprebehlo, vyhodí `NotInitialized`.

  ### Otvorenie relácie [#open-a-session]

  Odovzdajte aktivitu, ktorá je v popredí a obnovená (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()` sa pozastaví, kým nie je priložený tag alebo kým nevyprší `tapWait` (predvolene 30 sekúnd). Počas čakania
  zobrazte výzvu „Priložte kartu“. Ak `activity` nie je obnovená (resumed), vyhodí `IllegalStateException`.

  Na telefóne na aktivite záleží: Android číta tagy len pre ňu a keď aktivita opustí popredie, čakanie sa zastaví alebo
  relácia skončí s `ACTIVITY_PAUSED`. Terminály Sunmi čítajú tagy bez ohľadu na to, čo je v popredí, aktivitu však
  odovzdávate aj tak.

  Ak chcete čakanie ukončiť, napríklad keď zákazník ťukne na **Cancel**, zrušte korutinu, ktorá zavolala `openSession()`.
  Čítačka prestane čakať a môžete hneď otvoriť novú reláciu.

  #### Čítanie informácií o relácii [#reading-session-info]

  `session.info` popisuje priložený tag:

  | Pole             | Popis                                                                         |
  | :--------------- | :---------------------------------------------------------------------------- |
  | `uidHex`         | UID tagu ako hexadecimálny reťazec veľkými písmenami, napr. `04A1B2C3D4E5F6`. |
  | `family`         | Hodnota `TagFamily`, z ktorej zistíte, aké operácie možno použiť.             |
  | `sak`, `atqaHex` | Nízkoúrovňová identifikácia vrátená pri detekcii tagu.                        |
  | `atsHex`         | ATS pre tagy `ISO_DEP`; inak `null`.                                          |
  | `memoryBytes`    | Veľkosť používateľskej pamäte, ak ju čítačka dokáže zistiť; inak `null`.      |
  | `versionHex`     | Odpoveď na `GET_VERSION` pri tagoch, ktoré ho podporujú; inak `null`.         |

  ### Čítanie a zápis tagu [#read-and-write-a-tag]

  Vetvite podľa `session.info.family` a potom použite operácie pre daný typ tagu:

  ```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.")
  }
  ```

  Príkaz, ktorý nezodpovedá priloženému tagu, zlyhá s `NOT_SUPPORTED` a relácia zostane otvorená.

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

  NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C a ďalšie tagy Type 2 (`TYPE2_OTHER`).

  | Operácia                 | Čo robí                                                                                                                                       |
  | :----------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- |
  | `readPages(startPage)`   | Vráti 16 bajtov: štyri 4-bajtové stránky počnúc stránkou `startPage`.                                                                         |
  | `writePage(page, data)`  | Zapíše jednu 4-bajtovú stránku.                                                                                                               |
  | `passwordAuth(password)` | Len NTAG a Ultralight EV1. Odomkne chránené stránky 4-bajtovým heslom a vráti 2-bajtový PACK, pomocou ktorého môžete overiť, že je tag pravý. |
  | `ultralightCAuth(key)`   | Len Ultralight C. Vykoná autentifikáciu 16-bajtovým kľúčom 3DES.                                                                              |
  | `transceiveType2(frame)` | Odošle surový rámec Type 2 pre príkazy bez vlastnej metódy a vráti odpoveď tagu.                                                              |

  ```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 a 4K. Predtým, ako budete čítať alebo zapisovať bloky sektora, sektor autentifikujte.

  | Operácia                                   | Čo robí                                                                   |
  | :----------------------------------------- | :------------------------------------------------------------------------ |
  | `authenticateClassic(block, keyType, key)` | Autentifikuje 6-bajtovým kľúčom sektor, ktorý obsahuje `block`.           |
  | `readBlock(block)`                         | Vráti 16 bajtov bloku.                                                    |
  | `writeBlock(block, data)`                  | Zapíše do bloku 16 bajtov.                                                |
  | `increment`, `decrement`, `restore`        | Uložia výsledok operácie s hodnotovým blokom do prenosového buffera tagu. |
  | `transfer(block)`                          | Zapíše prenosový buffer do bloku.                                         |

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

  Autentifikácia nesprávnym kľúčom zlyhá s `AUTH_FAILED` a SDK za vás tag znova vyberie (re-select), takže môžete hneď skúsiť ďalší
  kľúč:

  ```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")
  ```

  #### Čipové karty ISO-DEP [#iso-dep-smart-cards]

  MIFARE DESFire a ďalšie karty ISO 14443-4.

  | Operácia             | Čo robí                                                                 |
  | :------------------- | :---------------------------------------------------------------------- |
  | `transmitApdu(apdu)` | Odošle jedno príkazové APDU a vráti odpoveď: údaje nasledované 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
  ```

  ### Ukončenie relácie [#end-a-session]

  Hneď ako skončíte, zavolajte `close()`, ideálne z bloku `finally`. Kým to neurobíte, žiadna platba nemôže čítačku
  použiť. `close()` je bezpečné volať opakovane a pri relácii, ktorá už skončila, nikdy nevyhadzuje výnimku.

  Relácia môže skončiť aj sama. `session.ended` je `StateFlow`, ktorý zostáva `null`, kým je relácia aktívna,
  a potom vyšle dôvod jej ukončenia:

  ```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` | Kedy nastáva                                                                                                  |
  | :-------------------- | :------------------------------------------------------------------------------------------------------------ |
  | `CLOSED`              | Vaša aplikácia zavolala `close()`.                                                                            |
  | `CARD_REMOVED`        | Tag opustil pole čítačky a nevrátil sa doň v rámci časového limitu `reselect`.                                |
  | `IDLE_TIMEOUT`        | Počas časového limitu `idle` (predvolene 10 sekúnd) neprišiel žiadny príkaz.                                  |
  | `PAYMENT`             | Spustila sa platba cez `Accept.payments.pay()`. Platby majú vždy prednosť.                                    |
  | `ACTIVITY_PAUSED`     | Len telefóny. Aktivita, s ktorou ste reláciu otvorili, opustila popredie a Android pre ňu prestal čítať tagy. |
  | `READER_LOST`         | Len terminály Sunmi. Spojenie s platobnou službou Sunmi sa prerušilo.                                         |

  Keď relácia skončí, každý ďalší príkaz zlyhá s `SESSION_ENDED`. Pre nové priloženie zavolajte znova
  `openSession()`.
</div>

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

Zlyhania pri práci s tagom vyhadzujú `TagSessionException`, ktorej `reason` vám povie, čo robiť ďalej. Volanie API pred
nastavením SDK vyhodí `NotInitialized`. `TagSessionException` **nie je** `AcceptException`, preto tieto dve výnimky
zachytávajte oddelene:

```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="Pri nastavenom authLost sa znova autentifikujte" type="warn">
  Pri akomkoľvek dôvode môže byť nastavený príznak výnimky `authLost`. Ak je nastavený, znova sa autentifikujte, skôr než pošlete
  ďalší príkaz do chránenej pamäte; pri DESFire navyše znova vyberte aplikáciu.
</Callout>

Ak má argument kľúča, bloku alebo stránky nesprávnu dĺžku alebo znamienko, prípadne je rámec alebo APDU prázdne, vyhodí sa
`IllegalArgumentException` skôr, než sa čokoľvek dostane k tagu.

### Otváranie relácie [#opening-a-session]

| `reason`        | Kedy nastáva                                                                                         | Čo robiť                                                                                                                              |
| :-------------- | :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| `TIMEOUT`       | Počas `tapWait` nedošlo k priloženiu.                                                                | Znova vyzvite na priloženie alebo pokus ukončite.                                                                                     |
| `READER_BUSY`   | Čítačku používa platba alebo iná relácia.                                                            | Skúste to neskôr; žiadny rad neexistuje.                                                                                              |
| `SESSION_ENDED` | Počas čakania na priloženie sa spustila platba alebo aktivita opustila popredie.                     | Otvorte novú reláciu, keď platba skončí alebo keď bude aplikácia opäť v popredí.                                                      |
| `NFC_DISABLED`  | Len telefóny. NFC je vypnuté v systémových nastaveniach.                                             | Požiadajte používateľa, aby NFC zapol.                                                                                                |
| `VENDOR_ERROR`  | Terminály Sunmi: platobná služba nie je dostupná. `vendorCode` obsahuje jej kód, ak nejaký poskytla. | Skúste to znova. Ak problém pretrváva, nahláste ho [podpore pre vývojárov](/support/contacts#developer-support) spolu s `vendorCode`. |
| `NOT_SUPPORTED` | Zariadenie nemá čítačku, ktorú SDK dokáže použiť.                                                    | Skryte funkciu, keď `isSupported()` vráti `false`.                                                                                    |

### Odoslanie príkazu [#sending-a-command]

| `reason`        | Kedy nastáva                                                                                         | Čo robiť                                                                                                                   |
| :-------------- | :--------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
| `TIMEOUT`       | Tag neodpovedal včas; bol znova vybraný.                                                             | Skúste to znova. Ak je nastavený `authLost`, najprv sa autentifikujte.                                                     |
| `NO_RESPONSE`   | Tag neodpovedal.                                                                                     | Skúste to znova, ak je tag stále v poli čítačky.                                                                           |
| `AUTH_FAILED`   | Nesprávny kľúč alebo heslo.                                                                          | Skúste ďalší kľúč; tag je už znova vybraný.                                                                                |
| `NAK`           | Tag príkaz odmietol: stránka mimo rozsahu, chránená pamäť alebo nepodporovaný príkaz.                | Opravte príkaz. Relácia pokračuje.                                                                                         |
| `NOT_SUPPORTED` | Príkaz nezodpovedá tejto rodine tagov alebo NFC radiču tohto telefónu (MIFARE Classic vyžaduje NXP). | Skontrolujte `session.info.family`. Relácia pokračuje.                                                                     |
| `VENDOR_ERROR`  | Vlastné SDK čítačky ohlásilo poruchu; `vendorCode` obsahuje jej kód.                                 | Skúste to znova, potom poruchu nahláste [podpore pre vývojárov](/support/contacts#developer-support) spolu s `vendorCode`. |

### Relácia skončila [#session-over]

| `reason`        | Kedy nastáva                             | Čo robiť                                               |
| :-------------- | :--------------------------------------- | :----------------------------------------------------- |
| `CARD_REMOVED`  | Tag opustil pole čítačky počas príkazu.  | Vyzvite na opätovné priloženie a otvorte novú reláciu. |
| `SESSION_ENDED` | Príkaz bol odoslaný po skončení relácie. | Otvorte novú reláciu.                                  |

## Časové limity [#timeouts]

Ak chcete zmeniť predvolené hodnoty, odovzdajte `openSession()` objekt `TagTimeouts`:

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

| Časový limit | Predvolená hodnota | Maximum | Platí pre                                    |
| :----------- | :----------------- | :------ | :------------------------------------------- |
| `tapWait`    | 30 s               | 60 s    | Čakanie na priloženie v `openSession()`      |
| `operation`  | 1 s                | 5 s     | Každý príkaz na tagu                         |
| `reselect`   | 1 s                | 5 s     | Opätovný výber tagu po neúspešnom príkaze    |
| `idle`       | 10 s               | 60 s    | Čas medzi príkazmi, po ktorom relácia skončí |

Každá hodnota musí byť väčšia ako nula. Vytvorenie `TagTimeouts` s hodnotou mimo týchto rozsahov vyhodí
`IllegalArgumentException`.

## Dobré vedieť [#good-to-know]

* **Jedna čítačka.** Zariadenie má jednu bezkontaktnú čítačku, takže naraz môže prebiehať len jedna relácia s tagom
  alebo jedno čakanie na priloženie. Druhé volanie `openSession()` zlyhá s `READER_BUSY`. Platby spustené cez
  `Accept.payments.pay()` reláciu najprv ukončia; platby spustené mimo SDK sa koordinovať nedajú, preto udržiavajte
  relácie krátke.
* **Zrušenie príkazu ho nevráti späť.** V okamihu, keď korutinu zrušíte, príkaz už dorazil k tagu
  a stále sa vykonáva; zahodí sa len jeho výsledok. Zrušený `writeBlock` teda môže tag aj tak zmeniť.
* **Príkazy sa vykonávajú v poradí.** Metódy relácie možno volať z ľubovoľnej korutiny; k tagu sa dostávajú jedna po druhej,
  v poradí, v akom ich voláte.
* **Údaje tagov sa nedostanú do logov.** SDK nikdy neloguje kľúče, heslá ani údaje tagov a UID maskuje (napr.
  `04A1…E5F6`). Ako vaša aplikácia údaje tagov ukladá a zobrazuje, je na vás.

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

Na telefóne a, ak ich podporujete, aj na termináli Sunmi radu P:

1. Overte, že `isSupported()` vracia `true` na oboch a `false` na zariadení bez NFC.
2. Priložte tag NTAG alebo Ultralight a overte, že relácia hlási správne UID a `readPages(0)` vracia 16
   bajtov. Potom vykonajte `writePage` a stránku znova prečítajte.
3. Na karte MIFARE Classic vykonajte autentifikáciu nesprávnym kľúčom a potvrďte `AUTH_FAILED`, potom správnym kľúčom.
4. Zrušte `openSession()` počas čakania na priloženie a hneď otvorte novú reláciu.
5. Počas relácie spustite platbu: relácia by mala skončiť ako `PAYMENT` a platba by mala prejsť. Počas inej relácie
   tag oddiaľte: relácia by mala skončiť ako `CARD_REMOVED`.
6. Na telefóne vypnite NFC a potvrďte `NFC_DISABLED`. Počas relácie pošlite aplikáciu na pozadie a potvrďte,
   že relácia skončí ako `ACTIVITY_PAUSED`.
