Tapaya
Přijímání plateb v prodejněPrůvodce integracíAplikace Tapaya Terminal

Aplikace Tapaya Terminal

Přijímejte bezkontaktní platby kartou tak, že práci s kartou předáte pluginové aplikaci Tapaya Terminal.

Přijímejte bezkontaktní platby kartou ze své aplikace pro Android tak, že práci s kartou předáte pluginové aplikaci Tapaya Terminal. Prodej zůstává pod kontrolou vaší aplikace; plugin obstará přiložení karty přes NFC, zadání PIN a zpracování karty a výsledek se pak vrátí přes proud událostí Accept.payments.pay().

Tuto integraci použijte, pokud chcete Tap to Pay, aniž byste do vlastní aplikace vkládali zpracovatele plateb. Pokud má vaše platforma místo toho povolené vestavěné Tap to Pay, přečtěte si průvodce Embedded Tap to Pay.

Možnosti integrace

MožnostVhodné proDoručení výsledku
Accept SDK (doporučeno)Nativní aplikace pro AndroidAccept.payments.pay() vysílá Flow<PaymentEvent>: vytvoření, spuštění i konečný výsledek z terminálu se zpracují za vás
DeeplinkWebView, multiplatformní frameworky, QR kódyPlatbu vytvoříte přes Tapaya API a průběžně se dotazujete na její stav; plugin se otevře přes deeplink

Nejprve je popsána cesta přes SDK; kontrakt deeplinku najdete v části Integrace bez SDK.

Jak to funguje

  1. Vaše aplikace zahájí platbu voláním Accept.payments.pay(amount, currency).
  2. SDK zaregistruje platbu v backendu Tapaya a spustí plugin Tapaya Terminal na stejném zařízení.
  3. Zákazník přiloží kartu nebo telefon k obrazovce Tapaya Terminal.
  4. Tapaya Terminal dokončí zpracování platby s backendem Tapaya.
  5. SDK sleduje platbu, dokud nedosáhne konečného stavu, a vyšle PaymentEvent.Result s PayResult. Pokud platbu vůbec nelze vytvořit, vyšle místo toho PaymentEvent.CreationFailed, obvykle do jedné sekundy.

Předpoklady

Instalace a aktivace terminálu

Aktivaci proveďte jednou pro každé zařízení. Aktivace propojí plugin Tapaya Terminal se stejným účtem obchodníka, pod kterým je autentizovaná vaše aplikace.

if (!Accept.plugin.isInstalled()) {
    // Opens the store listing so the user can install Tapaya Terminal
    Accept.plugin.install()
    return
}

// suspend; launches the plugin's activation flow and waits for the outcome
when (val result = Accept.plugin.activateTerminal()) {
    ActivateTerminalResult.Success -> { /* device linked, ready to take payments */ }
    ActivateTerminalResult.Canceled -> { /* user backed out */ }
    is ActivateTerminalResult.Failed -> result.reason // ActivateTerminalFailureReason
}

ActivateTerminalFailureReason je jedna z hodnot INVALID_INTENT, HANDOVER_FAILED, CONFIRM_FAILED, MERCHANT_NOT_ONBOARDED nebo UNKNOWN.

Poznámka

Accept.plugin.isInstalled() vrací false, když plugin Tapaya Terminal chybí. Aktuální stav pluginu kdykoli zjistíte voláním Accept.plugin.status(); podle něj ošetřete své platební UI.

activateTerminal() navíc vyhazuje NoTidsAvailableForMerchant, když obchodníkovi nezbývá žádný nepřiřazený terminál (TID) k aktivaci, a to odděleně od výše uvedených výsledků ActivateTerminalResult.Failed. Pokud chcete tento případ zobrazit zvlášť, například vyzvat obchodníka, aby uvolnil nebo si vyžádal další TID, obalte volání do try-catch.

Kontrola aktualizací pluginu

Accept.plugin.updateInfo() hlásí, zda je na Google Play k dispozici novější sestavení pluginu Tapaya Terminal, takže můžete obchodníka vyzvat k aktualizaci dřív, než platba narazí na rozdíl verzí.

when (val info = Accept.plugin.updateInfo()) {
    PluginUpdateInfo.UpToDate -> { /* nothing to do */ }
    is PluginUpdateInfo.UpdateAvailable -> promptUpdate(info.priority) // Int, higher is more urgent
    PluginUpdateInfo.Unknown -> { /* not installed, or an older plugin build that predates this check */ }
}

Poznámka

SDK se nikdy nedotazuje Google Play přímo; pluginová aplikace si dostupnost aktualizace ověřuje sama přes Play Core a výsledek hlásí svým stávajícím stavovým kanálem. Sestavení pluginu starší než to, se kterým byla tato funkce vydána, vždy hlásí Unknown.

Vytvoření platby

Částky jsou vždy v nejmenší jednotce měny: haléře pro CZK, centy pro EUR. 1500 s měnou "CZK" naúčtuje 15,00 Kč. Měny se zadávají jako kódy ISO 4217.

pay() vrací Flow<PaymentEvent>; sbírejte jej, abyste sledovali průběh a konečný výsledek. Nikdy nevyhazuje výjimku; selhání se projeví jako PaymentEvent.CreationFailed.

Accept.payments.pay(amount = 1500, currency = "CZK") // 15.00 Kč
    .collect { event ->
        when (event) {
            PaymentEvent.Creating -> { /* registering the payment */ }
            is PaymentEvent.Created -> { /* backend payment created: event.paymentToken */ }
            PaymentEvent.Launched -> { /* Tapaya Terminal is in the foreground */ }
            is PaymentEvent.Result -> when (val r = event.payResult) {
                is PayResult.Success -> showReceipt(r.paymentToken, r.authCode)
                is PayResult.Declined -> showDeclined(r.metadata)
                is PayResult.Canceled -> showCancelled(r.metadata)
                is PayResult.Failed -> showUnexpected(r.reason) // PayFailureReason
            }
            is PaymentEvent.CreationFailed -> handleError(event.cause) // AcceptException
        }
    }

Parametry:

ParametrTypPovinnéPopis
amountLong✓Částka k naúčtování v nejmenší jednotce měny currency (např. centy pro USD, haléře pro CZK).
currencyString✓Kód měny ISO 4217.
paymentTokenString?Obnoví existující platební relaci místo zahájení nové. Výchozí hodnota je null.
receiptConfigPaymentReceiptConfig?Přepíše obrazovku účtenky zobrazenou po schválené platbě, jen pro toto volání. Výchozí hodnota je null, při které se použije výchozí nastavení z Accept.payments.setOptions().
nfcPositionNfcPositionConfig?Přepíše, kam míří animace přiložení karty, jen pro toto volání. Výchozí hodnota je null, při které se použije výchozí nastavení z Accept.payments.setOptions().
metadataMap<String, String>Data ve tvaru klíč-hodnota připojená k platbě. Odesílají se pluginu Tapaya Terminal v intentu PAY a vracejí se zpět ve výsledném PayResult, pokud je plugin vrátí. Výchozí hodnota je emptyMap().
tipLong?Pevné spropitné v nejmenší jednotce měny currency. Je-li nastaveno, plugin přeskočí vlastní interaktivní obrazovku spropitného a naúčtuje amount + tip. Výchozí hodnota je null (plugin se plátce na spropitné zeptá).
transactionTimeoutDuration?Přepíše, jak dlouho terminál čeká na přiložení karty, jen pro toto volání. Výchozí hodnota je null, při které se použije výchozí nastavení z Accept.payments.setOptions(). Viz Nastavení časového limitu transakce.
displayPaymentDisplay?Přepíše, na které obrazovce běží platební UI pluginu, jen pro toto volání. Výchozí hodnota je null, při které se použije výchozí nastavení z Accept.payments.setOptions(). Viz Volba displeje.

Během platby je plugin Tapaya Terminal v popředí. Po dokončení platby se vaše aplikace automaticky vrátí do popředí. Chcete-li obnovit přerušenou platbu, předejte její paymentToken zpět do pay(amount, currency, paymentToken).

Vrácení metadata není zaručeno

metadata se v PayResult vracejí jen tehdy, když je plugin vrátí, proto je berte jako doplňkový kontext bez záruky (např. ID objednávky pro vlastní logování), ne jako data, na jejichž vrácení vaše aplikace závisí.

Volba měny

pay() účtuje v měně currency, kterou předáte jako kód ISO 4217 velkými písmeny, s částkou amount v nejmenší jednotce této měny. Stránka Podporované měny uvádí všechny měny, které Tapaya podporuje, ale obchodník má obvykle povolené jen některé z nich.

Kódy ISO 4217, které má obchodník povolené, zjistíte voláním Accept.merchant.availableCurrencies(). Platba v jakékoli jiné měně selže s CurrencyNotAvailableForMerchant.

Každá měna má také minimální částku k zaplacení, vynucovanou v celé platformě. Aktuální minima načtete pomocí Accept.sdk.minimumAmounts(), které vrací List<MinimumAmount> (currency: String, amount: Long, v nejmenší jednotce měny). Volání pay() pod tímto minimem selže s PaymentEvent.CreationFailed(AmountBelowMinimum(minimum, currency)). Úplný seznam výjimek najdete v části Zpracování chyb.

Nastavení obrazovky účtenky

receiptConfig určuje, co plugin Tapaya Terminal zobrazí plátci po schválené platbě, jak dlouho to zůstane na obrazovce a zda se účtenka vytiskne. Nemá žádný vliv na zamítnuté a stornované platby ani na platby čekající na storno; ty vždy zobrazí vlastní obrazovku výsledku bez ohledu na toto nastavení.

PaymentReceiptConfigPopis
DismissImmediatelyPlátci se nic nezobrazí.
Show(showQrCodeReceipt, dismissBehavior, printBehavior)Zobrazí obrazovku účtenky. showQrCodeReceipt: Boolean = true zapíná nebo vypíná QR kód s odkazem na účtenku. dismissBehavior: DismissBehavior = DismissBehavior.Manual určuje, jak se obrazovka zavře: Manual (zůstane, dokud ji plátce nezavře) nebo Delayed(delay: Duration) (automaticky se zavře po uplynutí delay, pokud ji plátce nezavřel dříve). printBehavior: ReceiptPrintBehavior = ReceiptPrintBehavior.OnDemand řídí fyzickou účtenku: Automatic (vytiskne se hned, jak se obrazovka zobrazí), OnDemand (jen když tisk spustí pokladní) nebo Disabled (nikdy se netiskne).

Výchozí nastavení pro celé zařízení nastavte jednou pomocí Accept.payments.setOptions() a pro jednotlivou platbu jej přepište přes pay(..., receiptConfig = ...). Dokud nezavoláte setOptions(), výchozí hodnota je Show s QR účtenkou, ručním zavřením a tiskem na vyžádání, což odpovídá vlastnímu záložnímu nastavení pluginu.

// Set a default, e.g. once at app startup
Accept.payments.setOptions(
    receiptConfig = PaymentReceiptConfig.Show(
        showQrCodeReceipt = true,
        dismissBehavior = DismissBehavior.Delayed(30.seconds),
        printBehavior = ReceiptPrintBehavior.Automatic,
    ),
)

// Override it for a single payment
Accept.payments.pay(
    amount = 1500,
    currency = "CZK",
    receiptConfig = PaymentReceiptConfig.DismissImmediately,
)

Umístění animace přiložení přes NFC

nfcPosition je nápověda, která pluginu Tapaya Terminal sděluje, kde má toto zařízení anténu NFC, aby mohl správně umístit animaci „přiložte zde“. Plugin nejprve určí polohu antény sám (živou detekcí platformy, pak z přibalené databáze zařízení a nakonec z čehokoli, co obchodník nastavil přímo v pluginu); tuto hodnotu zadávejte jen tehdy, když chcete toto určení přepsat, například u zařízení, které plugin nedokáže rozpoznat nebo ho má ve své databázi chybně.

NfcPositionConfigPopis
UnspecifiedBez přepsání (výchozí). Plugin určí polohu sám a zachová polohu, kterou v něm obchodník již nastavil.
OnDevice(surface, xFraction, yFraction)Anténa je přímo v zařízení; animace míří na bod (xFraction, yFraction) na ploše surface, což je OnDeviceSurface.BACK nebo OnDeviceSurface.FRONT_SCREEN.
ExternalAboveSamostatný modul čtečky je umístěn nad telefonem (držák smart-POS); na zařízení tedy není žádný bod, na který by animace mířila.

Výchozí nastavení pro celé zařízení nastavte pomocí Accept.payments.setOptions(nfcPosition = ...), nebo jej přepište pro jednotlivé volání přes pay(..., nfcPosition = ...). setOptions() mění jen ty volby, které předáte, takže samotné setOptions(nfcPosition = ...) nenaruší dříve nastavenou výchozí hodnotu receiptConfig a naopak.

Nastavení časového limitu transakce

transactionTimeout určuje, jak dlouho plugin Tapaya Terminal čeká na přiložení karty zákazníkem, než to vzdá. Přijímá jakoukoli hodnotu Duration od nuly do 120 sekund; cokoli mimo tento rozsah se projeví jako TransactionTimeoutOutOfRange(transactionTimeout) přes PaymentEvent.CreationFailed, protože pay() nikdy nevyhazuje výjimku synchronně.

Accept.payments.pay(
    amount = 1500,
    currency = "CZK",
    transactionTimeout = 90.seconds,
)

Ponechte výchozí hodnotu null, chcete-li použít vlastní záložní limit doprovodné aplikace (přibližně 60 sekund), nebo nastavte výchozí hodnotu pro celé zařízení pomocí Accept.payments.setOptions(transactionTimeout = ...).

Volba displeje

U oboustranného zařízení („double-sided“) je jeden panel otočený k obchodníkovi a druhý, zákaznický panel k plátci. Accept.displays hlásí, jaké displeje zařízení právě má, a volba display říká pluginu, na kterém panelu má spustit své platební UI.

if (Accept.displays.isDualScreen) {
    val customerPanel = Accept.displays.customerFacing() // AcceptDisplay?, null on single-screen devices
}

Accept.payments.pay(
    amount = 1500,
    currency = "CZK",
    display = PaymentDisplay.CustomerFacing,
)
PaymentDisplayPopis
DefaultAktuální displej hostitelské aplikace (výchozí chování, na zařízeních s jednou obrazovkou beze změny).
CustomerFacingSekundární panel oboustranného zařízení.
Specific(displayId)Konkrétní displej podle id uvedeného v Accept.displays.all().

Cíl, který nelze splnit, například CustomerFacing na zařízení s jednou obrazovkou, tiše přejde na Default. Výchozí nastavení pro celé zařízení nastavte pomocí Accept.payments.setOptions(display = ...), nebo jej přepište pro jednotlivé volání přes pay(..., display = ...).

Nastavte cíl před autentizací

Předběžná příprava terminálu (warm-up) spuštěná voláním Accept.auth.authenticate() se řídí stejným cílem, proto zavolejte setOptions(display = ...) před authenticate(), pokud chcete, aby ani samotná příprava neprobíhala na obrazovce obchodníka.

Ukázková aplikace :dualscreen předvádí kompletní placení na dvou panelech: obchodník pracuje na primárním panelu, zatímco PaymentDisplay.CustomerFacing zobrazí platební UI pluginu na obrazovce zákazníka.

Výsledek platby

Konečná událost PaymentEvent.Result nese PayResult. Každá varianta nese metadata, která vracejí to, co jste předali do pay(), pokud je plugin vrátí (může být null):

PayResultVýznam
Success(paymentToken, metadata, authCode, receiptDetails)Autorizováno a strženo. Prostředky budou zúčtovány obchodníkovi. authCode je autorizační kód zpracovatele hlášený terminálem; má informativní charakter a v závislosti na zpracovateli může chybět.
Declined(metadata, receiptDetails)Zamítnuto zpracovatelem plateb nebo vydavatelem karty.
Canceled(metadata)Zrušeno zákazníkem nebo obchodníkem před dokončením.
Failed(reason, metadata)Nedokončeno; reason je PayFailureReason.

PayFailureReason je jedna z hodnot INVALID_INTENT, NOT_ACTIVATED, TERMINAL_INIT_FAILED, PAYMENT_SETUP_FAILED, PLUGIN_UNAVAILABLE nebo UNKNOWN.

Čtení údajů účtenky

Success a Declined nesou volitelné receiptDetails: ReceiptDetails? se strukturovanými údaji karetní účtenky hlášenými terminálem, určené pro volající, kteří tisknou vlastní oficiální karetní účtenku místo toho, aby se spoléhali jen na hostovanou stránku PaymentStatus.receiptUrl.

PoleTypPopis
merchantIdString?ID obchodníka hlášené zpracovatelem.
terminalIdString?ID terminálu hlášené zpracovatelem.
cardBrandString?Značka karty, např. Visa nebo Mastercard.
appLabelString?Označení aplikace EMV.
aidString?Identifikátor aplikace EMV.
maskedPanString?Maskované číslo karty.
responseCodeString?Kód odpovědi zpracovatele.
transactionTimestampString?Časové razítko transakce ve formátu ISO-8601.

Každé pole je volitelné a bez záruky; volající musí počítat s tím, že kterékoli z nich, případně i samotné receiptDetails, bude chybět.

Čtení podrobností platby

PayResult.Success vám poskytne paymentToken. Úplný záznam, včetně receiptUrl, požadované a zúčtované částky a časových razítek, načtete pomocí Accept.payments.status():

val status: PaymentStatus = Accept.payments.status(paymentToken)
status.state       // PENDING, SUCCESS, CANCELLED, REFUNDED, FAILED, ACTION_NEEDED
status.receiptUrl  // for your receipt UI

Probíhající platbu zastavíte pomocí Accept.payments.cancel(paymentToken), které vrací výsledný PaymentState.

Vrácení platby

Dokončenou platbu vrátíte pomocí Accept.payments.refund(paymentToken, reason), které rovněž vrací výsledný PaymentState.

val state: PaymentState = Accept.payments.refund(paymentToken, RefundReason.CUSTOMER_REQUEST)

RefundReason je jedna z hodnot CUSTOMER_REQUEST, DUPLICATE, FRAUDULENT nebo EXPIRED_CHARGE.

Zpracování chyb

Platba, která dorazí do terminálu, ale neuspěje, není výjimka; přijde jako PaymentEvent.Result s PayResult.Declined, Canceled nebo Failed(reason). Selhání, která zabrání vytvoření platby, přicházejí jako PaymentEvent.CreationFailed(cause), kde cause je typovaná AcceptException. Výjimky specifické pro tuto integraci:

VýjimkaKdy nastaneCo dělat
PluginUnavailablePlugin Tapaya Terminal není nainstalovaný nebo se nepodařilo navázat spojení s jeho službou.Vyzvěte k instalaci pomocí Accept.plugin.install().
CurrencyNotAvailableForMerchant(code)Obchodník nemá nastavenou požadovanou měnu.Účtujte v povolené měně, nebo aktualizujte konfiguraci obchodníka.
AmountBelowMinimum(minimum, currency)Částka je pod nastaveným minimem.Účtujte částku rovnou minimum nebo vyšší.
TransactionTimeoutOutOfRange(transactionTimeout)transactionTimeout byl mimo rozsah 0 až 120 sekund.Předejte hodnotu v povoleném rozsahu, nebo ji vynechte, aby se použila výchozí hodnota.
LocationPermissionRequiredVaše aplikace nemá udělené oprávnění k poloze.Vyžádejte ACCESS_FINE_LOCATION a akci opakujte.
LocationUnavailable / LocationTimeoutPolohu zařízení se nepodařilo určit.Požádejte uživatele, aby zapnul polohu, a akci opakujte.
SessionExpiredAutentizovaná relace vypršela.Znovu se autentizujte pomocí Accept.auth.authenticate().
NoInternetConnectionNení k dispozici síťové připojení.Požádejte uživatele, aby se znovu připojil, a akci opakujte.
PaymentInProgressPředchozí volání pay() stále čeká na výsledek.Počkejte, až se aktuální platba dokončí.

Accept.payments.status(), cancel() a refund() jsou funkce suspend a tyto výjimky vyhazují (místo vysílání událostí); obalte je do try-catch. status()/cancel()/refund() navíc pro neznámý token vyhazují PaymentNotFound. Úplný seznam najdete v části Zpracování chyb.

Časové limity

Platbu kartou je nutné na terminálu dokončit krátce po jejím zahájení. Pokud zákazník kartu nikdy nepřiloží nebo se plugin Tapaya Terminal během platby zavře, platba skončí jako PayResult.Canceled nebo PayResult.Failed a podkladová platba se na straně serveru zruší a nikdy se nezúčtuje. Než platbu považujete za dokončenou, vždy si ověřte konečný stav pomocí Accept.payments.status().

Integrace bez SDK

Pokud nemůžete Accept SDK vložit do aplikace (multiplatformní framework, tenký POS shell, webová pokladna), můžete Tapaya Terminal ovládat přímo pomocí deeplinku. Rozdělení práce se tím mění: platbu přes Tapaya API vytváříte vy a vy se také dotazujete na její stav, abyste zjistili výsledek; Tapaya Terminal obstarává jen práci s kartou.

Postup:

  1. Vytvořte platbu ze svého backendu přes Payments API a uložte si její paymentId.
  2. Otevřete Tapaya Terminal s tímto paymentId pomocí platebního deeplinku.
  3. Dotazujte se na stav platby přes Payments API, dokud neopustí stav PENDING. Deeplinky nemají zpětný kanál, takže dotazování je jediným spolehlivým zdrojem výsledku.

Poznámka

Tapaya Terminal musí být nainstalovaný, přihlášený k účtu obchodníka a nastavený pro měnu platby. V tomto režimu neprobíhá žádná aktivace; obchodníkem odpovědným za transakci (merchant of record) je přihlášený účet. V případě potřeby obchodníka přihlaste pomocí přihlašovacího deeplinku.

Schéma určuje, které sestavení Tapaya Terminal odkaz obslouží:

ProstředíSchéma
Produkcetapaya-accept
Sandboxtapaya-accept-sandbox

Pokud se odkaz nepodaří otevřít, dané sestavení Tapaya Terminal není nainstalované.

tapaya-accept://pay?paymentId=<paymentId>&amount=<amount>&currency=<ISO4217>
Parametr dotazuPovinnéPoznámky
paymentIdAnoPlatba vytvořená v kroku 1.
amountAnoČástka v hlavních jednotkách jako celé číslo: amount=15 naúčtuje 15,00. Pozor, tím se liší od SDK, které přijímá nejmenší jednotky.
currencyAnoKód měny ISO 4217, např. CZK.
val uri = Uri.parse("tapaya-accept://pay?paymentId=$paymentId&amount=15&currency=CZK")
try {
    startActivity(Intent(Intent.ACTION_VIEW, uri).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
} catch (e: ActivityNotFoundException) {
    // Tapaya Terminal is not installed, send the user to Google Play
}

Přihlaste obchodníka do Tapaya Terminal pomocí jeho merchantToken, identifikátoru obchodníka, který nastavíte přes POST /merchant/auth/register:

tapaya-accept://login?token=<merchantToken>
Parametr dotazuPovinnéPoznámky
tokenAnomerchantToken obchodníka. Jde o vaše stabilní ID obchodníka, nikoli o krátkodobý token SDK z POST /merchant/auth/login.
activationCodeNePředvyplní aktivační kód terminálu.

Otestování integrace

  1. Inicializujte s isProduction = false (výchozí hodnota); platby v sandboxu nikdy nepřesouvají skutečné peníze.
  2. Nainstalujte plugin Tapaya Terminal do testovacího zařízení, přihlaste se testovacím obchodníkem ze sandboxu a proveďte aktivaci.
  3. Proveďte platbu na malou částku a ověřte celý tok Success od začátku do konce, včetně vašeho UI účtenky.
  4. Vyzkoušejte cesty selhání: zamítněte platbu na terminálu, jednu zrušte a jednu zahajte v nepodporované měně; vaše zpracování PayResult/CreationFailed by mělo pokrýt každou z nich.

Až budete připraveni spustit ostrý provoz, nastavte v initialize hodnotu isProduction = true a autentizujte se produkčními přihlašovacími údaji. Kód plateb se nemění.

Chybí vám něco na této stránce?Navrhněte funkci. Připojíme tuto stránku, aby tým viděl souvislosti.