# NFC-Tags (/de/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>

Lesen und beschreiben Sie NFC-Tags wie Kundenkarten, Zutrittsausweise, Sticker und Chipkarten direkt in Ihrer
Android-App. Das SDK kommuniziert mit dem geräteeigenen Lesegerät: mit Android NFC auf einem Telefon oder mit dem
kontaktlosen Lesegerät eines Terminals der Sunmi-P-Serie. Dazwischen ist kein Plugin geschaltet.

Tag-Sessions werden in optionalen Artefakten ausgeliefert: `com.tapaya:accept-nfc` und, für Sunmi-Terminals,
`com.tapaya:accept-nfc-sunmi`. Fügen Sie sie nur hinzu, wenn Sie sie benötigen; Kartenzahlungen funktionieren mit und ohne sie gleich.

## Unterstützte Geräte [#supported-devices]

| Gerät                                | Lesegerät                                                                                | Benötigte Artefakte                 |
| :----------------------------------- | :--------------------------------------------------------------------------------------- | :---------------------------------- |
| Android-Telefon oder -Tablet mit NFC | Android NFC                                                                              | `accept-nfc`                        |
| Terminal der Sunmi-P-Serie           | Das kontaktlose Lesegerät des Terminals, über den Zahlungsdienst (Pay Service) von Sunmi | `accept-nfc` und `accept-nfc-sunmi` |
| Jedes andere Gerät                   | Keines                                                                                   | Nicht unterstützt                   |

Terminals der Sunmi-P-Serie haben kein Android NFC; ihre Antenne sitzt hinter dem Zahlungsmodul. `accept-nfc-sunmi`
erreicht sie über den Zahlungsdienst von Sunmi und tritt an die Stelle von Android NFC, wann immer dieser Dienst installiert ist.

Auf einem Gerät ohne nutzbares Lesegerät gibt `isSupported()` den Wert `false` zurück, und `openSession()` schlägt mit
`NOT_SUPPORTED` fehl.

<Callout title="Für Telefone gelten einige zusätzliche Einschränkungen" type="info">
  Telefone lesen Tags nur für die Activity im Vordergrund; eine Session endet daher, sobald diese Activity den Vordergrund verlässt.
  MIFARE Classic funktioniert nur auf Telefonen mit einem NFC-Controller von NXP; auf anderen Telefonen schlagen die zugehörigen Operationen mit
  `NOT_SUPPORTED` fehl. Bei ISO-DEP-Tags enthält `atsHex` nur die historischen Bytes des ATS, weil Android den Rest
  nicht bereitstellt.
</Callout>

## Unterstützte Tags [#supported-tags]

| Tag-Typ                                    | Beispiele                                             |
| :----------------------------------------- | :---------------------------------------------------- |
| [NFC Type 2](#nfc-type-2)                  | NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C |
| [MIFARE Classic](#mifare-classic)          | MIFARE Classic Mini, 1K, 4K                           |
| [ISO-DEP-Chipkarten](#iso-dep-smart-cards) | MIFARE DESFire                                        |

## So funktioniert es [#how-it-works]

1. Ihre App ruft `Accept.nfc.openSession(activity)` auf.
2. Das SDK schaltet das Lesegerät des Geräts ein und wartet darauf, dass ein Tag vorgehalten wird.
3. Der Kunde hält einen Tag vor, und `openSession()` gibt eine `TagSession` mit der UID und der Familie des Tags zurück.
4. Ihre App liest und schreibt über die Session, jeweils einen Befehl nach dem anderen.
5. Ihre App ruft `close()` auf und gibt damit das Lesegerät für Zahlungen und andere Tag-Sessions frei.

<Callout title="Zahlungen haben immer Vorrang" type="warn">
  Ein Gerät hat ein einziges kontaktloses Lesegerät, das sich eine Tag-Session und eine Kartenzahlung nicht teilen können. Der Start einer Zahlung
  mit `Accept.payments.pay()` beendet Ihre Session, und `openSession()` schlägt mit `READER_BUSY` fehl, bis die Zahlung
  abgeschlossen ist. Rechnen Sie damit, dass eine Session jederzeit enden kann; siehe [Session beenden](#end-a-session).
</Callout>

## Voraussetzungen [#prerequisites]

* Ein [unterstütztes Gerät](#supported-devices).
* Das Accept SDK ist [installiert und initialisiert](../integration/sdk_integration#initialization). Wenn Sie `isSupported()` oder
  `openSession()` vor `Accept.initialize` aufrufen, wird `NotInitialized` geworfen.

Tag-Sessions benötigen keinen angemeldeten Händler, kein Tapaya-Terminal-Plugin und keine Standortberechtigung. Die
Artefakte ergänzen Ihr Manifest um alles, was sie benötigen:

* `accept-nfc` fügt die Berechtigung `NFC` hinzu und kennzeichnet `android.hardware.nfc` als **nicht erforderlich**, sodass Google Play Ihre App
  auch auf Sunmi-Terminals verfügbar hält, die kein Android NFC haben.
* `accept-nfc-sunmi` fügt die Berechtigung `com.sunmi.perm.CONTACTLESS_CARD` und einen `<queries>`-Eintrag für den Zahlungsdienst
  von Sunmi hinzu. Es bettet Sunmi PayLib ein.

## Integration [#integrate]

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

  Wenn Sie zusätzlich `com.tapaya:accept` deklarieren, halten Sie alle Artefakte auf derselben Version.

  `Accept.nfc` ist eine Extension Property; importieren Sie sie vor der Verwendung:

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

  ### Unterstützung prüfen [#check-support]

  Rufen Sie `isSupported()` auf, um zu entscheiden, ob Sie die Tag-Funktion überhaupt anzeigen:

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

  `isSupported()` erfordert kein Vorhalten und greift nicht auf das Lesegerät zu. Sie können die Methode daher jederzeit gefahrlos aufrufen. Sie sagt Ihnen, ob das Gerät
  ein Lesegerät hat, das das SDK nutzen kann. Auf einem Telefon mit ausgeschaltetem NFC gibt sie `true` zurück; `openSession()` schlägt dann
  mit `NFC_DISABLED` fehl. Wenn `Accept.initialize` noch nicht ausgeführt wurde, wirft sie `NotInitialized`.

  ### Session öffnen [#open-a-session]

  Übergeben Sie die Activity, die sich im Vordergrund und im Zustand „resumed“ befindet:

  ```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()` suspendiert, bis ein Tag vorgehalten wird oder `tapWait` abläuft (standardmäßig 30 Sekunden). Zeigen Sie während des Wartens
  die Aufforderung „Karte vorhalten“ an. Befindet sich `activity` nicht im Zustand „resumed“, wird eine `IllegalStateException` geworfen.

  Auf einem Telefon spielt die Activity eine Rolle: Android liest Tags nur für sie, und verlässt sie den Vordergrund, wird das Warten abgebrochen
  oder die Session mit `ACTIVITY_PAUSED` beendet. Sunmi-Terminals lesen Tags unabhängig davon, was sich im Vordergrund befindet, Sie übergeben aber
  trotzdem eine Activity.

  Um das Warten abzubrechen, zum Beispiel wenn der Kunde auf **Cancel** tippt, brechen Sie die Coroutine ab, die `openSession()` aufgerufen hat.
  Das Lesegerät hört auf zu warten, und Sie können sofort eine neue Session öffnen.

  #### Session-Informationen auslesen [#reading-session-info]

  `session.info` beschreibt den vorgehaltenen Tag:

  | Feld             | Beschreibung                                                                        |
  | :--------------- | :---------------------------------------------------------------------------------- |
  | `uidHex`         | Die UID des Tags als Hexadezimalwert in Großbuchstaben, z. B. `04A1B2C3D4E5F6`.     |
  | `family`         | Die `TagFamily`, an der Sie erkennen, welche Operationen anwendbar sind.            |
  | `sak`, `atqaHex` | Low-Level-Identifikation, die beim Erkennen des Tags zurückgegeben wird.            |
  | `atsHex`         | ATS für Tags vom Typ `ISO_DEP`; sonst `null`.                                       |
  | `memoryBytes`    | Größe des Benutzerspeichers, sofern das Lesegerät sie ermitteln kann; sonst `null`. |
  | `versionHex`     | Antwort auf `GET_VERSION` bei Tags, die diesen Befehl unterstützen; sonst `null`.   |

  ### Tag lesen und beschreiben [#read-and-write-a-tag]

  Verzweigen Sie anhand von `session.info.family` und verwenden Sie dann die Operationen für diesen Tag-Typ:

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

  Ein Befehl, der nicht zum vorgehaltenen Tag passt, schlägt mit `NOT_SUPPORTED` fehl, und die Session bleibt geöffnet.

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

  NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C und andere Type-2-Tags (`TYPE2_OTHER`).

  | Operation                | Funktion                                                                                                                                                            |
  | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
  | `readPages(startPage)`   | Gibt 16 Byte zurück: vier 4-Byte-Seiten ab `startPage`.                                                                                                             |
  | `writePage(page, data)`  | Schreibt eine 4-Byte-Seite.                                                                                                                                         |
  | `passwordAuth(password)` | Nur NTAG und Ultralight EV1. Entsperrt geschützte Seiten mit einem 4-Byte-Passwort und gibt das 2-Byte-PACK zurück, mit dem Sie prüfen können, ob der Tag echt ist. |
  | `ultralightCAuth(key)`   | Nur Ultralight C. Authentifiziert mit einem 16-Byte-3DES-Schlüssel.                                                                                                 |
  | `transceiveType2(frame)` | Sendet einen rohen Type-2-Frame für Befehle ohne eigene Methode und gibt die Antwort des Tags zurück.                                                               |

  ```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 und 4K. Authentifizieren Sie einen Sektor, bevor Sie seine Blöcke lesen oder beschreiben.

  | Operation                                  | Funktion                                                                      |
  | :----------------------------------------- | :---------------------------------------------------------------------------- |
  | `authenticateClassic(block, keyType, key)` | Authentifiziert den Sektor, der `block` enthält, mit einem 6-Byte-Schlüssel.  |
  | `readBlock(block)`                         | Gibt die 16 Byte des Blocks zurück.                                           |
  | `writeBlock(block, data)`                  | Schreibt 16 Byte in den Block.                                                |
  | `increment`, `decrement`, `restore`        | Legen das Ergebnis einer Value-Block-Operation im Transferpuffer des Tags ab. |
  | `transfer(block)`                          | Schreibt den Transferpuffer in einen Block.                                   |

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

  Ein falscher Schlüssel schlägt mit `AUTH_FAILED` fehl, und das SDK wählt den Tag für Sie erneut aus (Re-Select), sodass Sie sofort den nächsten Schlüssel
  versuchen können:

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

  #### ISO-DEP-Chipkarten [#iso-dep-smart-cards]

  MIFARE DESFire und andere Karten nach ISO 14443-4.

  | Operation            | Funktion                                                                              |
  | :------------------- | :------------------------------------------------------------------------------------ |
  | `transmitApdu(apdu)` | Sendet eine Befehls-APDU und gibt die Antwort zurück: die Daten, gefolgt von 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
  ```

  ### Session beenden [#end-a-session]

  Rufen Sie `close()` auf, sobald Sie fertig sind, idealerweise aus einem `finally`-Block. Bis dahin kann keine Zahlung das
  Lesegerät nutzen. `close()` kann gefahrlos mehrfach aufgerufen werden und wirft bei einer bereits beendeten Session nie eine Exception.

  Eine Session kann auch von selbst enden. `session.ended` ist ein `StateFlow`, der `null` bleibt, solange die Session aktiv ist,
  und dann den Grund ausgibt, aus dem sie geendet hat:

  ```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` | Wann es eintritt                                                                                                                                   |
  | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `CLOSED`              | Ihre App hat `close()` aufgerufen.                                                                                                                 |
  | `CARD_REMOVED`        | Der Tag hat das Feld verlassen und ist nicht innerhalb des Timeouts `reselect` zurückgekehrt.                                                      |
  | `IDLE_TIMEOUT`        | Innerhalb des Timeouts `idle` (standardmäßig 10 Sekunden) kam kein Befehl.                                                                         |
  | `PAYMENT`             | Eine Zahlung wurde über `Accept.payments.pay()` gestartet. Zahlungen haben immer Vorrang.                                                          |
  | `ACTIVITY_PAUSED`     | Nur auf Telefonen. Die Activity, mit der Sie die Session geöffnet haben, hat den Vordergrund verlassen, und Android liest keine Tags mehr für sie. |
  | `READER_LOST`         | Nur auf Sunmi-Terminals. Die Verbindung zum Zahlungsdienst von Sunmi ist abgebrochen.                                                              |

  Sobald eine Session beendet ist, schlägt jeder weitere Befehl mit `SESSION_ENDED` fehl. Rufen Sie für ein neues Vorhalten
  erneut `openSession()` auf.
</div>

## Fehlerbehandlung [#error-handling]

Bei Fehlern am Tag wird eine `TagSessionException` geworfen, deren `reason` Ihnen sagt, was als Nächstes zu tun ist. Wird die API aufgerufen, bevor das
SDK eingerichtet ist, wird `NotInitialized` geworfen. `TagSessionException` ist **keine** `AcceptException`, fangen Sie die beiden daher getrennt ab:

```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="Bei gesetztem authLost erneut authentifizieren" type="warn">
  Bei jedem Grund kann das Flag `authLost` der Exception gesetzt sein. Ist es gesetzt, authentifizieren Sie sich erneut, bevor Sie
  einen weiteren Befehl an geschützten Speicher senden; bei DESFire wählen Sie zusätzlich die Anwendung erneut aus.
</Callout>

Ein Schlüssel-, Block- oder Seitenargument mit falscher Länge oder falschem Vorzeichen oder ein leerer Frame bzw. eine leere APDU wirft
eine `IllegalArgumentException`, bevor irgendetwas den Tag erreicht.

### Beim Öffnen einer Session [#opening-a-session]

| `reason`        | Wann es eintritt                                                                                                          | Was zu tun ist                                                                                                                                     |
| :-------------- | :------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TIMEOUT`       | Innerhalb von `tapWait` wurde kein Tag vorgehalten.                                                                       | Fordern Sie erneut zum Vorhalten auf oder brechen Sie ab.                                                                                          |
| `READER_BUSY`   | Eine Zahlung oder eine andere Session nutzt das Lesegerät.                                                                | Versuchen Sie es später erneut; es gibt keine Warteschlange.                                                                                       |
| `SESSION_ENDED` | Während des Wartens auf das Vorhalten wurde eine Zahlung gestartet, oder die Activity hat den Vordergrund verlassen.      | Öffnen Sie eine neue Session, sobald die Zahlung abgeschlossen ist oder die App wieder im Vordergrund ist.                                         |
| `NFC_DISABLED`  | Nur auf Telefonen. NFC ist in den Systemeinstellungen ausgeschaltet.                                                      | Bitten Sie den Nutzer, NFC einzuschalten.                                                                                                          |
| `VENDOR_ERROR`  | Sunmi-Terminals: Der Zahlungsdienst ist nicht verfügbar. `vendorCode` enthält dessen Code, sofern er einen geliefert hat. | Versuchen Sie es erneut. Besteht der Fehler weiter, melden Sie ihn mit `vendorCode` dem [Entwickler-Support](/support/contacts#developer-support). |
| `NOT_SUPPORTED` | Das Gerät hat kein Lesegerät, das das SDK nutzen kann.                                                                    | Blenden Sie die Funktion aus, wenn `isSupported()` den Wert `false` zurückgibt.                                                                    |

### Beim Senden eines Befehls [#sending-a-command]

| `reason`        | Wann es eintritt                                                                                                      | Was zu tun ist                                                                                                                  |
| :-------------- | :-------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ |
| `TIMEOUT`       | Der Tag hat nicht rechtzeitig geantwortet; er wurde erneut ausgewählt.                                                | Versuchen Sie es erneut. Ist `authLost` gesetzt, authentifizieren Sie sich zuerst.                                              |
| `NO_RESPONSE`   | Der Tag hat nicht geantwortet.                                                                                        | Versuchen Sie es erneut, wenn sich der Tag noch im Feld befindet.                                                               |
| `AUTH_FAILED`   | Falscher Schlüssel oder falsches Passwort.                                                                            | Versuchen Sie den nächsten Schlüssel; der Tag ist bereits erneut ausgewählt.                                                    |
| `NAK`           | Der Tag hat den Befehl abgelehnt: Seite außerhalb des Bereichs, geschützter Speicher oder nicht unterstützter Befehl. | Korrigieren Sie den Befehl. Die Session läuft weiter.                                                                           |
| `NOT_SUPPORTED` | Der Befehl passt nicht zu dieser Tag-Familie oder zum NFC-Controller dieses Telefons (MIFARE Classic erfordert NXP).  | Prüfen Sie `session.info.family`. Die Session läuft weiter.                                                                     |
| `VENDOR_ERROR`  | Das SDK des Lesegeräts selbst hat eine Störung gemeldet; `vendorCode` enthält deren Code.                             | Versuchen Sie es erneut und melden Sie sie dann mit `vendorCode` dem [Entwickler-Support](/support/contacts#developer-support). |

### Session beendet [#session-over]

| `reason`        | Wann es eintritt                                     | Was zu tun ist                                                         |
| :-------------- | :--------------------------------------------------- | :--------------------------------------------------------------------- |
| `CARD_REMOVED`  | Der Tag hat das Feld während des Befehls verlassen.  | Fordern Sie erneut zum Vorhalten auf und öffnen Sie eine neue Session. |
| `SESSION_ENDED` | Ein Befehl wurde nach dem Ende der Session gesendet. | Öffnen Sie eine neue Session.                                          |

## Timeouts [#timeouts]

Übergeben Sie `TagTimeouts` an `openSession()`, um die Standardwerte zu ändern:

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

| Timeout     | Standard | Maximum | Gilt für                                                    |
| :---------- | :------- | :------ | :---------------------------------------------------------- |
| `tapWait`   | 30 s     | 60 s    | Warten auf das Vorhalten in `openSession()`                 |
| `operation` | 1 s      | 5 s     | Jeden Befehl auf dem Tag                                    |
| `reselect`  | 1 s      | 5 s     | Erneute Auswahl des Tags nach einem fehlgeschlagenen Befehl |
| `idle`      | 10 s     | 60 s    | Zeit zwischen Befehlen, nach der die Session endet          |

Jeder Wert muss größer als null sein. Wird `TagTimeouts` mit einem Wert außerhalb dieser Bereiche erstellt, wird
eine `IllegalArgumentException` geworfen.

## Gut zu wissen [#good-to-know]

* **Ein einziges Lesegerät.** Das Gerät hat ein einziges kontaktloses Lesegerät, daher ist jeweils nur eine Tag-Session oder ein ausstehendes Warten auf das Vorhalten möglich. Ein zweiter
  Aufruf von `openSession()` schlägt mit `READER_BUSY` fehl. Zahlungen, die mit `Accept.payments.pay()` gestartet werden, beenden zuerst eine Session;
  Zahlungen, die außerhalb des SDK gestartet werden, lassen sich nicht koordinieren, halten Sie Sessions daher kurz.
* **Das Abbrechen eines Befehls macht ihn nicht rückgängig.** Wenn Sie die Coroutine abbrechen, hat der Befehl den Tag bereits erreicht
  und wird weiter ausgeführt; nur sein Ergebnis wird verworfen. Ein abgebrochenes `writeBlock` kann den Tag also trotzdem verändern.
* **Befehle werden der Reihe nach ausgeführt.** Session-Methoden können aus jeder Coroutine aufgerufen werden; sie erreichen den Tag einzeln,
  in der Reihenfolge Ihrer Aufrufe.
* **Tag-Daten gelangen nicht in Logs.** Das SDK protokolliert nie Schlüssel, Passwörter oder Tag-Daten und maskiert UIDs (z. B.
  `04A1…E5F6`). Wie Ihre App Tag-Daten speichert und anzeigt, liegt bei Ihnen.

## Integration testen [#test-the-integration]

Auf einem Telefon und, falls Sie diese unterstützen, auf einem Terminal der Sunmi-P-Serie:

1. Prüfen Sie, dass `isSupported()` auf beiden `true` und auf einem Gerät ohne NFC `false` zurückgibt.
2. Halten Sie einen NTAG- oder Ultralight-Tag vor und prüfen Sie, dass die Session die richtige UID meldet und `readPages(0)` 16
   Byte zurückgibt. Führen Sie dann `writePage` aus und lesen Sie die Seite erneut.
3. Authentifizieren Sie sich bei MIFARE Classic mit einem falschen Schlüssel und bestätigen Sie `AUTH_FAILED`, dann mit dem richtigen.
4. Brechen Sie `openSession()` während des Wartens auf das Vorhalten ab und öffnen Sie sofort eine neue Session.
5. Starten Sie während einer Session eine Zahlung: Die Session sollte als `PAYMENT` enden und die Zahlung durchgehen. Entfernen Sie
   während einer anderen Session den Tag: Sie sollte als `CARD_REMOVED` enden.
6. Schalten Sie auf einem Telefon NFC aus und bestätigen Sie `NFC_DISABLED`. Schicken Sie die App während einer Session in den Hintergrund und
   bestätigen Sie, dass die Session als `ACTIVITY_PAUSED` endet.
