# Tag NFC (/vi/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>

Đọc và ghi tag NFC, chẳng hạn thẻ khách hàng thân thiết, thẻ ra vào, nhãn dán và thẻ thông minh, trực tiếp từ ứng dụng
Android của bạn. SDK giao tiếp với chính đầu đọc của thiết bị: Android NFC trên điện thoại, hoặc đầu đọc không tiếp xúc của
máy thanh toán Sunmi dòng P. Không có plugin nào đứng giữa.

Phiên đọc tag được cung cấp trong các artifact tùy chọn, `com.tapaya:accept-nfc` và, cho máy Sunmi,
`com.tapaya:accept-nfc-sunmi`. Chỉ thêm các artifact này nếu bạn cần; thanh toán bằng thẻ vẫn hoạt động như nhau dù có hay không có chúng.

## Thiết bị được hỗ trợ [#supported-devices]

| Thiết bị                                     | Đầu đọc                                                                              | Artifact bạn cần                   |
| :------------------------------------------- | :----------------------------------------------------------------------------------- | :--------------------------------- |
| Điện thoại hoặc máy tính bảng Android có NFC | Android NFC                                                                          | `accept-nfc`                       |
| Máy thanh toán Sunmi dòng P                  | Đầu đọc không tiếp xúc của máy, thông qua dịch vụ thanh toán (pay service) của Sunmi | `accept-nfc` và `accept-nfc-sunmi` |
| Mọi thiết bị khác                            | Không có                                                                             | Không được hỗ trợ                  |

Máy thanh toán Sunmi dòng P không có Android NFC; ăng-ten của chúng nằm phía sau mô-đun thanh toán. `accept-nfc-sunmi`
truy cập ăng-ten này thông qua dịch vụ thanh toán của Sunmi, và được dùng thay cho Android NFC bất cứ khi nào dịch vụ đó được cài đặt.

Trên thiết bị không có đầu đọc dùng được, `isSupported()` trả về `false` và `openSession()` thất bại với
`NOT_SUPPORTED`.

<Callout title="Điện thoại có thêm một vài giới hạn" type="info">
  Điện thoại chỉ đọc tag cho activity đang ở foreground, nên phiên sẽ kết thúc khi activity đó rời khỏi foreground.
  MIFARE Classic chỉ hoạt động trên điện thoại có bộ điều khiển NFC của NXP; trên các điện thoại khác, các thao tác của nó thất bại với
  `NOT_SUPPORTED`. Với tag ISO-DEP, `atsHex` chỉ chứa các byte lịch sử (historical bytes) của ATS, vì Android không cung cấp
  phần còn lại.
</Callout>

## Tag được hỗ trợ [#supported-tags]

| Loại tag                                       | Ví dụ                                                 |
| :--------------------------------------------- | :---------------------------------------------------- |
| [NFC Type 2](#nfc-type-2)                      | NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C |
| [MIFARE Classic](#mifare-classic)              | MIFARE Classic Mini, 1K, 4K                           |
| [Thẻ thông minh ISO-DEP](#iso-dep-smart-cards) | MIFARE DESFire                                        |

## Cách hoạt động [#how-it-works]

1. Ứng dụng của bạn gọi `Accept.nfc.openSession(activity)`.
2. SDK bật đầu đọc của thiết bị và chờ chạm tag.
3. Khách hàng chạm một tag, và `openSession()` trả về một `TagSession` mang UID và họ (family) của tag.
4. Ứng dụng của bạn đọc và ghi thông qua phiên, mỗi lần một lệnh.
5. Ứng dụng của bạn gọi `close()`, thao tác này giải phóng đầu đọc cho thanh toán và các phiên đọc tag khác.

<Callout title="Thanh toán luôn được ưu tiên" type="warn">
  Mỗi thiết bị chỉ có một đầu đọc không tiếp xúc, và phiên đọc tag với thanh toán bằng thẻ không thể dùng chung đầu đọc đó. Việc bắt đầu
  một khoản thanh toán bằng `Accept.payments.pay()` sẽ kết thúc phiên của bạn, và `openSession()` thất bại với `READER_BUSY` cho tới khi
  khoản thanh toán kết thúc. Hãy sẵn sàng cho việc phiên có thể kết thúc bất cứ lúc nào; xem [Kết thúc phiên](#end-a-session).
</Callout>

## Điều kiện tiên quyết [#prerequisites]

* Một [thiết bị được hỗ trợ](#supported-devices).
* Accept SDK đã được [cài đặt và khởi tạo](../integration/sdk_integration#initialization). Gọi `isSupported()` hoặc
  `openSession()` trước `Accept.initialize` sẽ ném ra `NotInitialized`.

Phiên đọc tag không cần merchant đã đăng nhập, plugin Tapaya Terminal hay quyền truy cập vị trí. Các
artifact tự thêm những gì chúng cần vào manifest của bạn:

* `accept-nfc` thêm quyền `NFC` và đánh dấu `android.hardware.nfc` là **không bắt buộc**, để Play vẫn cung cấp ứng dụng của bạn
  trên máy Sunmi, vốn không có Android NFC.
* `accept-nfc-sunmi` thêm quyền `com.sunmi.perm.CONTACTLESS_CARD` và một mục `<queries>` cho dịch vụ thanh toán
  của Sunmi. Artifact này nhúng sẵn Sunmi PayLib.

## Tích hợp [#integrate]

<div className="fd-steps [&_h3]:fd-step">
  ### Cài đặt [#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")
  }
  ```

  Nếu bạn cũng khai báo `com.tapaya:accept`, hãy giữ tất cả các artifact ở cùng một phiên bản.

  `Accept.nfc` là một extension property; hãy import trước khi sử dụng:

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

  ### Kiểm tra khả năng hỗ trợ [#check-support]

  Gọi `isSupported()` để quyết định có hiển thị tính năng tag hay không:

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

  `isSupported()` không cần chạm và không động tới đầu đọc, nên có thể gọi an toàn bất cứ lúc nào. Hàm cho bạn biết thiết bị
  có đầu đọc mà SDK dùng được. Hàm trả về `true` trên điện thoại đã tắt NFC; khi đó `openSession()` thất bại
  với `NFC_DISABLED`. Hàm ném ra `NotInitialized` nếu `Accept.initialize` chưa chạy.

  ### Mở phiên [#open-a-session]

  Truyền vào activity đang ở foreground và ở trạng thái 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()` tạm dừng (suspend) cho tới khi một tag được chạm hoặc hết `tapWait` (mặc định 30 giây). Hãy hiển thị lời nhắc “Chạm
  thẻ của bạn” trong lúc chờ. Hàm ném ra `IllegalStateException` nếu `activity` không ở trạng thái resumed.

  Trên điện thoại, activity đóng vai trò quan trọng: Android chỉ đọc tag cho activity đó, và khi activity rời khỏi foreground, việc chờ
  sẽ dừng hoặc phiên kết thúc với `ACTIVITY_PAUSED`. Máy Sunmi đọc tag bất kể thứ gì đang ở foreground, nhưng bạn vẫn truyền vào một
  activity.

  Để dừng chờ, chẳng hạn khi khách hàng nhấn **Cancel**, hãy hủy coroutine đã gọi `openSession()`.
  Đầu đọc sẽ ngừng chờ, và bạn có thể mở một phiên mới ngay lập tức.

  #### Đọc thông tin phiên [#reading-session-info]

  `session.info` mô tả tag đã được chạm:

  | Trường           | Mô tả                                                                             |
  | :--------------- | :-------------------------------------------------------------------------------- |
  | `uidHex`         | UID của tag dưới dạng hex viết hoa, ví dụ `04A1B2C3D4E5F6`.                       |
  | `family`         | `TagFamily`, cho bạn biết những thao tác nào áp dụng được.                        |
  | `sak`, `atqaHex` | Thông tin nhận dạng cấp thấp được trả về khi phát hiện tag.                       |
  | `atsHex`         | ATS cho tag `ISO_DEP`; nếu không thì là `null`.                                   |
  | `memoryBytes`    | Dung lượng bộ nhớ người dùng, khi đầu đọc xác định được; nếu không thì là `null`. |
  | `versionHex`     | Phản hồi `GET_VERSION`, với các tag hỗ trợ lệnh này; nếu không thì là `null`.     |

  ### Đọc và ghi tag [#read-and-write-a-tag]

  Rẽ nhánh theo `session.info.family`, sau đó dùng các thao tác dành cho loại 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.")
  }
  ```

  Một lệnh không phù hợp với tag đã chạm sẽ thất bại với `NOT_SUPPORTED`, và phiên vẫn mở.

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

  NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C và các tag Type 2 khác (`TYPE2_OTHER`).

  | Thao tác                 | Chức năng                                                                                                                                                                                |
  | :----------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `readPages(startPage)`   | Trả về 16 byte: bốn trang 4 byte bắt đầu từ `startPage`.                                                                                                                                 |
  | `writePage(page, data)`  | Ghi một trang 4 byte.                                                                                                                                                                    |
  | `passwordAuth(password)` | Chỉ dành cho NTAG và Ultralight EV1. Mở khóa các trang được bảo vệ bằng mật khẩu 4 byte và trả về PACK 2 byte, bạn có thể dùng PACK để kiểm tra tag có phải là tag chính hãng hay không. |
  | `ultralightCAuth(key)`   | Chỉ dành cho Ultralight C. Xác thực bằng khóa 3DES 16 byte.                                                                                                                              |
  | `transceiveType2(frame)` | Gửi một frame Type 2 thô, cho các lệnh không có phương thức riêng, và trả về phản hồi của 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 và 4K. Hãy xác thực một sector trước khi đọc hoặc ghi các block của nó.

  | Thao tác                                   | Chức năng                                                                              |
  | :----------------------------------------- | :------------------------------------------------------------------------------------- |
  | `authenticateClassic(block, keyType, key)` | Xác thực sector chứa `block` bằng một khóa 6 byte.                                     |
  | `readBlock(block)`                         | Trả về 16 byte của block.                                                              |
  | `writeBlock(block, data)`                  | Ghi 16 byte vào block.                                                                 |
  | `increment`, `decrement`, `restore`        | Đưa kết quả của thao tác trên value block vào bộ đệm truyền (transfer buffer) của tag. |
  | `transfer(block)`                          | Ghi bộ đệm truyền vào một 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)
  ```

  Khóa sai sẽ thất bại với `AUTH_FAILED`, và SDK tự chọn lại (re-select) tag cho bạn, nên bạn có thể thử khóa tiếp theo
  ngay lập tức:

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

  #### Thẻ thông minh ISO-DEP [#iso-dep-smart-cards]

  MIFARE DESFire và các thẻ ISO 14443-4 khác.

  | Thao tác             | Chức năng                                                          |
  | :------------------- | :----------------------------------------------------------------- |
  | `transmitApdu(apdu)` | Gửi một APDU lệnh và trả về phản hồi: dữ liệu theo sau là 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
  ```

  ### Kết thúc phiên [#end-a-session]

  Gọi `close()` ngay khi bạn xong việc, tốt nhất là từ một khối `finally`. Cho tới khi bạn làm vậy, không khoản thanh toán nào có thể dùng
  đầu đọc. Có thể gọi `close()` an toàn nhiều lần, và hàm không bao giờ ném ngoại lệ với một phiên đã kết thúc.

  Phiên cũng có thể tự kết thúc. `session.ended` là một `StateFlow` giữ giá trị `null` trong khi phiên còn hoạt động,
  sau đó phát ra lý do phiên kết thúc:

  ```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` | Khi nào xảy ra                                                                                                          |
  | :-------------------- | :---------------------------------------------------------------------------------------------------------------------- |
  | `CLOSED`              | Ứng dụng của bạn đã gọi `close()`.                                                                                      |
  | `CARD_REMOVED`        | Tag đã rời khỏi vùng đọc và không quay lại trong thời gian chờ `reselect`.                                              |
  | `IDLE_TIMEOUT`        | Không có lệnh nào đến trong thời gian chờ `idle` (mặc định 10 giây).                                                    |
  | `PAYMENT`             | Một khoản thanh toán đã bắt đầu qua `Accept.payments.pay()`. Thanh toán luôn được ưu tiên.                              |
  | `ACTIVITY_PAUSED`     | Chỉ trên điện thoại. Activity mà bạn dùng để mở phiên đã rời khỏi foreground, và Android ngừng đọc tag cho activity đó. |
  | `READER_LOST`         | Chỉ trên máy Sunmi. Kết nối tới dịch vụ thanh toán của Sunmi bị ngắt.                                                   |

  Sau khi phiên đã kết thúc, mọi lệnh tiếp theo đều thất bại với `SESSION_ENDED`. Hãy gọi lại `openSession()` cho một lần
  chạm mới.
</div>

## Xử lý lỗi [#error-handling]

Lỗi liên quan tới tag sẽ ném ra `TagSessionException`, với `reason` cho bạn biết cần làm gì tiếp theo. Gọi API trước khi
SDK được thiết lập sẽ ném ra `NotInitialized`. `TagSessionException` **không phải** là một `AcceptException`, vì vậy hãy bắt riêng
hai loại này:

```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="Xác thực lại khi authLost được đặt" type="warn">
  Với bất kỳ lý do nào, cờ `authLost` của ngoại lệ đều có thể được đặt. Khi cờ này được đặt, hãy xác thực lại trước khi gửi
  lệnh tiếp theo tới vùng nhớ được bảo vệ; với DESFire, hãy chọn lại cả ứng dụng (application).
</Callout>

Đối số khóa, block hoặc trang có độ dài hoặc dấu không đúng, hoặc frame hay APDU rỗng, sẽ ném ra
`IllegalArgumentException` trước khi bất cứ thứ gì đến được tag.

### Mở phiên [#opening-a-session]

| `reason`        | Khi nào xảy ra                                                                              | Cần làm gì                                                                                                                              |
| :-------------- | :------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------- |
| `TIMEOUT`       | Không có lần chạm nào trong `tapWait`.                                                      | Nhắc lại, hoặc dừng.                                                                                                                    |
| `READER_BUSY`   | Một khoản thanh toán hoặc phiên khác đang dùng đầu đọc.                                     | Thử lại sau; không có hàng đợi.                                                                                                         |
| `SESSION_ENDED` | Một khoản thanh toán đã bắt đầu, hoặc activity đã rời khỏi foreground, trong lúc chờ chạm.  | Mở một phiên mới khi khoản thanh toán đã kết thúc hoặc ứng dụng đã quay lại foreground.                                                 |
| `NFC_DISABLED`  | Chỉ trên điện thoại. NFC bị tắt trong cài đặt hệ thống.                                     | Yêu cầu người dùng bật NFC.                                                                                                             |
| `VENDOR_ERROR`  | Máy Sunmi: dịch vụ thanh toán không khả dụng. `vendorCode` chứa mã lỗi của dịch vụ, nếu có. | Thử lại. Nếu lỗi vẫn tiếp diễn, hãy báo cho [bộ phận hỗ trợ nhà phát triển](/support/contacts#developer-support) kèm theo `vendorCode`. |
| `NOT_SUPPORTED` | Thiết bị không có đầu đọc mà SDK dùng được.                                                 | Ẩn tính năng khi `isSupported()` trả về `false`.                                                                                        |

### Gửi lệnh [#sending-a-command]

| `reason`        | Khi nào xảy ra                                                                                             | Cần làm gì                                                                                                          |
| :-------------- | :--------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
| `TIMEOUT`       | Tag không phản hồi kịp thời; tag đã được chọn lại.                                                         | Thử lại. Nếu `authLost` được đặt, hãy xác thực trước.                                                               |
| `NO_RESPONSE`   | Tag không phản hồi.                                                                                        | Thử lại nếu tag vẫn còn trong vùng đọc.                                                                             |
| `AUTH_FAILED`   | Sai khóa hoặc mật khẩu.                                                                                    | Thử khóa tiếp theo; tag đã được chọn lại.                                                                           |
| `NAK`           | Tag từ chối lệnh: trang nằm ngoài phạm vi, vùng nhớ được bảo vệ hoặc lệnh không được hỗ trợ.               | Sửa lệnh. Phiên vẫn tiếp tục.                                                                                       |
| `NOT_SUPPORTED` | Lệnh không phù hợp với họ tag này, hoặc với bộ điều khiển NFC của điện thoại này (MIFARE Classic cần NXP). | Kiểm tra `session.info.family`. Phiên vẫn tiếp tục.                                                                 |
| `VENDOR_ERROR`  | SDK riêng của đầu đọc báo lỗi; `vendorCode` chứa mã lỗi.                                                   | Thử lại, sau đó báo cho [bộ phận hỗ trợ nhà phát triển](/support/contacts#developer-support) kèm theo `vendorCode`. |

### Phiên đã kết thúc [#session-over]

| `reason`        | Khi nào xảy ra                                     | Cần làm gì                            |
| :-------------- | :------------------------------------------------- | :------------------------------------ |
| `CARD_REMOVED`  | Tag đã rời khỏi vùng đọc trong lúc thực hiện lệnh. | Yêu cầu chạm lại và mở một phiên mới. |
| `SESSION_ENDED` | Một lệnh được gửi sau khi phiên đã kết thúc.       | Mở một phiên mới.                     |

## Thời gian chờ [#timeouts]

Truyền `TagTimeouts` vào `openSession()` để thay đổi giá trị mặc định:

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

| Thời gian chờ | Mặc định | Tối đa | Áp dụng cho                                             |
| :------------ | :------- | :----- | :------------------------------------------------------ |
| `tapWait`     | 30 s     | 60 s   | Chờ chạm trong `openSession()`                          |
| `operation`   | 1 s      | 5 s    | Mỗi lệnh trên tag                                       |
| `reselect`    | 1 s      | 5 s    | Chọn lại tag sau một lệnh thất bại                      |
| `idle`        | 10 s     | 60 s   | Khoảng thời gian giữa các lệnh trước khi phiên kết thúc |

Mọi giá trị đều phải lớn hơn 0. Tạo `TagTimeouts` với giá trị nằm ngoài các khoảng này sẽ ném ra
`IllegalArgumentException`.

## Cần lưu ý [#good-to-know]

* **Một đầu đọc.** Thiết bị chỉ có một đầu đọc không tiếp xúc, nên mỗi lúc chỉ có một phiên đọc tag hoặc một lượt chờ chạm. Lệnh gọi
  `openSession()` thứ hai sẽ thất bại với `READER_BUSY`. Khoản thanh toán bắt đầu bằng `Accept.payments.pay()` sẽ kết thúc phiên trước;
  khoản thanh toán bắt đầu bên ngoài SDK thì không thể điều phối, vì vậy hãy giữ phiên ngắn gọn.
* **Hủy một lệnh không hoàn tác lệnh đó.** Vào lúc bạn hủy coroutine, lệnh đã tới tag
  và vẫn được thực thi; chỉ kết quả của nó bị bỏ đi. Một `writeBlock` đã bị hủy vẫn có thể thay đổi tag.
* **Các lệnh chạy theo thứ tự.** Có thể gọi các phương thức của phiên từ bất kỳ coroutine nào; chúng tới tag lần lượt từng lệnh một,
  theo đúng thứ tự bạn gọi.
* **Dữ liệu tag không xuất hiện trong log.** SDK không bao giờ ghi log khóa, mật khẩu hay dữ liệu tag, và che UID (ví dụ
  `04A1…E5F6`). Việc ứng dụng của bạn lưu trữ và hiển thị dữ liệu tag như thế nào là do bạn quyết định.

## Kiểm thử tích hợp [#test-the-integration]

Trên điện thoại và, nếu bạn hỗ trợ, trên máy thanh toán Sunmi dòng P:

1. Kiểm tra rằng `isSupported()` trả về `true` trên cả hai, và `false` trên thiết bị không có NFC.
2. Chạm một tag NTAG hoặc Ultralight và xác minh rằng phiên báo đúng UID và `readPages(0)` trả về 16
   byte. Sau đó thực hiện `writePage` và đọc lại trang đó.
3. Trên MIFARE Classic, xác thực bằng một khóa sai và xác nhận nhận được `AUTH_FAILED`, sau đó xác thực bằng khóa đúng.
4. Hủy `openSession()` trong lúc chờ chạm và mở ngay một phiên mới.
5. Bắt đầu một khoản thanh toán trong khi phiên đang mở: phiên sẽ kết thúc với `PAYMENT` và khoản thanh toán được thực hiện thành công. Lấy
   tag ra trong một phiên khác: phiên sẽ kết thúc với `CARD_REMOVED`.
6. Trên điện thoại, tắt NFC và xác nhận nhận được `NFC_DISABLED`. Trong khi phiên đang mở, đưa ứng dụng xuống chạy nền và
   xác nhận phiên kết thúc với `ACTIVITY_PAUSED`.
