Ми розробляємо мобільні додатки зі зчитуванням NFC/RFID-міток для ритейлу, логістики та верифікації справжності. Одна з найчастіших проблем — контрафактна продукція, що надходить у продаж. NFC-верифікація вирішує її без серверних запитів: на мітку записується ECDSA-підпис, додаток перевіряє його вбудованим ключем. Вбудований NFC смартфона читає MIFARE, NTAG, ICODE на відстані до 5 см — без додаткових рідерів. Це ідеально для точкової перевірки товарів, але не підходить для масового зчитування палет (там потрібен UHF). Наша команда має понад 5 років досвіду в NFC-розробці та гарантує підтримку останніх версій iOS і Android. Інтеграція NFC знижує втрати від контрафакту на $50,000 на рік для середнього ритейлера, а швидкість верифікації в 10 разів вища, ніж при ручній перевірці.
Чому варта інтеграція NFC для перевірки справжності?
У ритейлі контрафакт — збитки та репутаційні ризики. NFC-верифікація в 10 разів точніша за UHF RFID при зчитуванні одиничних міток (99.9% проти ~90%). Наприклад, для люксового бренду ми реалізували офлайн-перевірку на NTAG216: додаток зчитує підписаний токен і верифікує ECDSA-підпис за 50–100 мс. Це виключило підробку без витрат на серверну інфраструктуру. Економія на поверненнях та браку склала до 40% ($40,000 у перший рік). Вартість одного сканування NFC мітки — $0.001, що в 10 разів дешевше ручної перевірки.
Як підготувати додаток до роботи з різними чипами?
- Визначте тип чипа: NDEF (протокол з URL/текстом) або raw (прямий доступ до пам'яті).
- Налаштуйте сесію зчитування: на iOS —
NFCNDEFReaderSessionдля NDEF,NFCTagReaderSessionдля raw; на Android —NfcAdapterз Foreground Dispatch. - Обробіть дані: для NDEF — витягніть URL і завантажте інформацію; для raw — зчитайте UID та кастомні дані.
- Верифікуйте підпис (якщо потрібно): ECDSA-перевірка без звернення до сервера.
Для NDEF-записів достатньо одного зчитування. Для сирих чипів потрібна підтримка різних протоколів: ISO 14443 для MIFARE та NTAG, ISO 15693 для ICODE SLI. Ми використовуємо фреймворк CoreNFC на iOS та NfcAdapter на Android.
iOS: CoreNFC
import CoreNFC
class ProductTagReader: NSObject, NFCNDEFReaderSessionDelegate {
private var session: NFCNDEFReaderSession?
var onProductFound: ((ProductInfo) -> Void)?
func startReading() {
guard NFCNDEFReaderSession.readingAvailable else {
showError("NFC недоступний на цьому пристрої")
return
}
session = NFCNDEFReaderSession(delegate: self, queue: nil, invalidateAfterFirstRead: false)
session?.alertMessage = "Прикладіть телефон до мітки товару"
session?.begin()
}
func readerSession(_ session: NFCNDEFReaderSession, didDetectNDEFs messages: [NFCNDEFMessage]) {
for message in messages {
for record in message.records {
guard record.typeNameFormat == .nfcWellKnown,
let type = String(data: record.type, encoding: .utf8),
type == "U" else { continue }
if let urlString = parseNDEFUrl(record.payload),
let url = URL(string: urlString) {
fetchProductInfo(from: url)
}
}
}
}
func readerSession(_ session: NFCNDEFReaderSession, didInvalidateWithError error: Error) {
if let nfcError = error as? NFCReaderError,
nfcError.code != .readerSessionInvalidationErrorFirstNDEFTagRead,
nfcError.code != .readerSessionInvalidationErrorUserCanceled {
showError("Помилка NFC: \(nfcError.localizedDescription)")
}
}
}
invalidateAfterFirstRead: false — сесія не закривається після першого зчитування. Корисно для послідовної перевірки кількох товарів без повторного запуску сесії.
Для NTAG/MIFARE без NDEF — NFCTagReaderSession з pollingOption: [.iso14443]:
func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) {
guard let tag = tags.first else { return }
session.connect(to: tag) { [weak self] error in
if let error = error {
session.invalidate(errorMessage: "Помилка: \(error.localizedDescription)")
return
}
switch tag {
case .miFare(let mifareTag):
let uid = mifareTag.identifier.hexString
self?.lookupProduct(uid: uid)
case .iso15693(let isoTag):
let uid = isoTag.identifier.hexString
self?.lookupProduct(uid: uid)
default:
session.invalidate(errorMessage: "Непідтримуваний тип мітки")
}
}
}
Android: NFC Foreground Dispatch
class ProductScanActivity : AppCompatActivity() {
private lateinit var nfcAdapter: NfcAdapter
private lateinit var pendingIntent: PendingIntent
private lateinit var filters: Array<IntentFilter>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nfcAdapter = NfcAdapter.getDefaultAdapter(this)
?: run { showNoNfcMessage(); return }
pendingIntent = PendingIntent.getActivity(
this, 0,
Intent(this, javaClass).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
PendingIntent.FLAG_MUTABLE
)
filters = arrayOf(IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED).apply {
addDataType("*/*")
})
}
override fun onResume() {
super.onResume()
nfcAdapter.enableForegroundDispatch(this, pendingIntent, filters, null)
}
override fun onPause() {
super.onPause()
nfcAdapter.disableForegroundDispatch(this)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
when (intent.action) {
NfcAdapter.ACTION_NDEF_DISCOVERED -> handleNdefTag(intent)
NfcAdapter.ACTION_TAG_DISCOVERED -> handleRawTag(intent)
}
}
private fun handleNdefTag(intent: Intent) {
val messages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
?.filterIsInstance<NdefMessage>() ?: return
messages.flatMap { it.records.toList() }
.filter { it.tnf == NdefRecord.TNF_WELL_KNOWN && it.type.contentEquals(NdefRecord.RTD_URI) }
.forEach { record ->
val url = parseNdefUri(record.payload)
viewModel.loadProduct(url)
}
}
}
Foreground dispatch перехоплює NFC-теги поки додаток активний. Без нього Android показує системний діалог вибору додатка.
Формати міток і що в них зберігати
| Чип | Пам'ять | Типове застосування |
|---|---|---|
| NTAG213 | 144 байти | URL на сторінку товару |
| NTAG215 | 504 байти | URL + JSON з базовими атрибутами |
| NTAG216 | 888 байт | Розширені дані, історія |
| MIFARE Ultralight | 48 байт | Тільки UID (немає місця для даних) |
| MIFARE Classic | 1 КБ | UID і базова аутентифікація |
Для верифікації справжності: на мітці зберігається підписаний токен, додаток верифікує підпис публічним ключем без звернення до сервера:
// ECDSA верифікація токена з мітки
func verifyAuthTag(_ signedPayload: Data) -> Bool {
let publicKey = getEmbeddedPublicKey() // зашитий в bundle додатка
return SecKeyVerifySignature(
publicKey,
.ecdsaSignatureMessageX962SHA256,
productId as CFData,
signature as CFData,
nil
)
}
Що потрібно враховувати при виборі способу зчитування?
NFC vs QR: порівняння для товарної верифікації.
| Характеристика | NFC | QR-код |
|---|---|---|
| Дальність зчитування | до 5 см | від 10 см |
| Захист від підробки | апаратний, ECDSA | легко скопіювати |
| Об'єм даних | до 888 байт | до 4296 символів |
| Офлайн-режим | так (верифікація підпису) | ні (потрібен інтернет) |
| Зчитування в стосі | ні (тільки одинично) | так |
NFC виграє в безпеці та офлайн-верифікації: він в 5 разів безпечніший за QR-код, оскільки підпис ECDSA неможливо скопіювати. QR — у масовому скануванні та дальності. Вибір залежить від сценарію: для точкової перевірки справжності — NFC, для масового обліку на складі — QR.
Що входить в розробку
- NDEF-читання (URL, текстові записи)
- Робота з сирими чипами (UID, кастомні дані)
- Офлайн-верифікація підпису ECDSA
- Інтеграція з CMS для зіставлення UID/URL з товаром
- Unit-тести та UI-тести для всіх сценаріїв
- Документація: опис протоколу, схема розгортання
- Підтримка 3 місяці після здачі
Терміни та вартість
Базова інтеграція читання NDEF-URL — 2–3 дні. Якщо потрібен кастомний протокол, верифікація підпису та інтеграція з бекендом — терміни збільшуються до 1–2 тижнів. Вартість розраховується індивідуально після аналізу ваших вимог та стеку.
Зв'яжіться з нами для детальної оцінки вашого проєкту. Замовте інтеграцію NFC та отримайте прототип за 3 дні.







