NFC tagy
AndroidBetaČítajte a zapisujte NFC tagy zo svojej aplikácie pre Android pomocou NFC telefónu alebo čítačky terminálu Sunmi radu P.
Čí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
| 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.
Telefóny majú niekoľko ďalších obmedzení
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.
Podporované tagy
| Typ tagu | Príklady |
|---|---|
| NFC Type 2 | NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C |
| MIFARE Classic | MIFARE Classic Mini, 1K, 4K |
| Čipové karty ISO-DEP | MIFARE DESFire |
Ako to funguje
- Vaša aplikácia zavolá
Accept.nfc.openSession(activity). - SDK zapne čítačku zariadenia a čaká na priloženie.
- Zákazník priloží tag a
openSession()vrátiTagSessions UID a rodinou tagu. - Vaša aplikácia číta a zapisuje prostredníctvom relácie, vždy po jednom príkaze.
- Vaša aplikácia zavolá
close(), čím uvoľní čítačku pre platby a ďalšie relácie s tagmi.
Platby majú vždy prednosť
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.
Predpoklady
- Podporované zariadenie.
- Accept SDK nainštalované a inicializované. Volanie
isSupported()aleboopenSession()predAccept.initializevyhodí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-nfcpridá oprávnenieNFCa označíandroid.hardware.nfcako nepovinné, takže Play ponechá vašu aplikáciu dostupnú aj na termináloch Sunmi, ktoré Android NFC nemajú.accept-nfc-sunmipridá oprávneniecom.sunmi.perm.CONTACTLESS_CARDa záznam<queries>pre platobnú službu Sunmi. Obsahuje knižnicu Sunmi PayLib.
Integrácia
Inštalácia
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:
import com.tapaya.accept.nfc.nfcOverenie podpory
Zavolajte isSupported() a podľa výsledku rozhodnite, či funkciu pre tagy vôbec zobraziť:
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
Odovzdajte aktivitu, ktorá je v popredí a obnovená (resumed):
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
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
Vetvite podľa session.info.family a potom použite operácie pre daný typ tagu:
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
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. |
// 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 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. |
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ľúč:
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
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. |
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 successUkončenie relácie
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:
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().
Spracovanie chýb
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:
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
}Pri nastavenom authLost sa znova autentifikujte
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.
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
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 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
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 spolu s vendorCode. |
Relácia skončila
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
Ak chcete zmeniť predvolené hodnoty, odovzdajte openSession() objekt TagTimeouts:
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ť
- 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á sREADER_BUSY. Platby spustené cezAccept.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ý
writeBlockteda 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
Na telefóne a, ak ich podporujete, aj na termináli Sunmi radu P:
- Overte, že
isSupported()vraciatruena oboch afalsena zariadení bez NFC. - Priložte tag NTAG alebo Ultralight a overte, že relácia hlási správne UID a
readPages(0)vracia 16 bajtov. Potom vykonajtewritePagea stránku znova prečítajte. - Na karte MIFARE Classic vykonajte autentifikáciu nesprávnym kľúčom a potvrďte
AUTH_FAILED, potom správnym kľúčom. - Zrušte
openSession()počas čakania na priloženie a hneď otvorte novú reláciu. - Počas relácie spustite platbu: relácia by mala skončiť ako
PAYMENTa platba by mala prejsť. Počas inej relácie tag oddiaľte: relácia by mala skončiť akoCARD_REMOVED. - 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čí akoACTIVITY_PAUSED.