Tapaya
Prijímanie platieb v predajniSprievodca integráciouNFC tagyBeta

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čkaPotrebné artefakty
Telefón alebo tablet s Androidom a NFCAndroid NFCaccept-nfc
Terminál Sunmi radu PBezkontaktná čítačka terminálu, cez platobnú službu Sunmiaccept-nfc a accept-nfc-sunmi
Akékoľvek iné zariadenieŽiadnaNepodporované

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 taguPríklady
NFC Type 2NTAG, MIFARE Ultralight, Ultralight EV1, Ultralight C
MIFARE ClassicMIFARE Classic Mini, 1K, 4K
Čipové karty ISO-DEPMIFARE DESFire

Ako to funguje

  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.

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

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

Inštalácia

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:

import com.tapaya.accept.nfc.nfc

Overenie 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:

PolePopis
uidHexUID tagu ako hexadecimálny reťazec veľkými písmenami, napr. 04A1B2C3D4E5F6.
familyHodnota TagFamily, z ktorej zistíte, aké operácie možno použiť.
sak, atqaHexNízkoúrovňová identifikácia vrátená pri detekcii tagu.
atsHexATS pre tagy ISO_DEP; inak null.
memoryBytesVeľkosť používateľskej pamäte, ak ju čítačka dokáže zistiť; inak null.
versionHexOdpoveď 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, restoreUlož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 success

Ukonč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
    }
}
TagSessionEndReasonKedy nastáva
CLOSEDVaša aplikácia zavolala close().
CARD_REMOVEDTag opustil pole čítačky a nevrátil sa doň v rámci časového limitu reselect.
IDLE_TIMEOUTPočas časového limitu idle (predvolene 10 sekúnd) neprišiel žiadny príkaz.
PAYMENTSpustila sa platba cez Accept.payments.pay(). Platby majú vždy prednosť.
ACTIVITY_PAUSEDLen telefóny. Aktivita, s ktorou ste reláciu otvorili, opustila popredie a Android pre ňu prestal čítať tagy.
READER_LOSTLen 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

reasonKedy nastávaČo robiť
TIMEOUTPoč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_ENDEDPoč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_DISABLEDLen telefóny. NFC je vypnuté v systémových nastaveniach.Požiadajte používateľa, aby NFC zapol.
VENDOR_ERRORTerminá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_SUPPORTEDZariadenie nemá čítačku, ktorú SDK dokáže použiť.Skryte funkciu, keď isSupported() vráti false.

Odoslanie príkazu

reasonKedy nastávaČo robiť
TIMEOUTTag neodpovedal včas; bol znova vybraný.Skúste to znova. Ak je nastavený authLost, najprv sa autentifikujte.
NO_RESPONSETag neodpovedal.Skúste to znova, ak je tag stále v poli čítačky.
AUTH_FAILEDNesprávny kľúč alebo heslo.Skúste ďalší kľúč; tag je už znova vybraný.
NAKTag príkaz odmietol: stránka mimo rozsahu, chránená pamäť alebo nepodporovaný príkaz.Opravte príkaz. Relácia pokračuje.
NOT_SUPPORTEDPrí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_ERRORVlastné 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

reasonKedy nastávaČo robiť
CARD_REMOVEDTag opustil pole čítačky počas príkazu.Vyzvite na opätovné priloženie a otvorte novú reláciu.
SESSION_ENDEDPrí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ý limitPredvolená hodnotaMaximumPlatí pre
tapWait30 s60 sČakanie na priloženie v openSession()
operation1 s5 sKaždý príkaz na tagu
reselect1 s5 sOpätovný výber tagu po neúspešnom príkaze
idle10 s60 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á 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

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.
Chýba vám niečo na tejto stránke?Navrhnite funkciu. Pripojíme túto stránku, aby tím videl súvislosti.