App Tapaya Terminal
Accetti pagamenti contactless con carta affidando l'interazione con la carta all'app plugin Tapaya Terminal.
Accetti pagamenti contactless con carta dalla Sua app Android affidando l'interazione con la carta all'app plugin Tapaya Terminal.
La vendita resta sotto il controllo della Sua app; il plugin gestisce l'avvicinamento NFC, l'inserimento del PIN e l'elaborazione
della carta, quindi il risultato torna tramite il flusso di eventi di Accept.payments.pay().
Usi questa integrazione se desidera Tap to Pay senza incorporare un processore di pagamento nella Sua app. Se invece la Sua piattaforma è abilitata a Tap to Pay incorporato, consulti la guida Embedded Tap to Pay.
Opzioni di integrazione
| Opzione | Ideale per | Consegna del risultato |
|---|---|---|
| Accept SDK (consigliato) | App Android native | Accept.payments.pay() emette un Flow<PaymentEvent>: creazione, avvio e risultato finale del terminale, gestiti automaticamente |
| Deeplink | WebView, framework multipiattaforma, codici QR | Lei crea il pagamento tramite la Tapaya API e ne interroga periodicamente lo stato; il plugin si apre tramite un deeplink |
Il percorso tramite SDK è descritto per primo; per il contratto del deeplink consulti Integrare senza l'SDK.
Come funziona
- La Sua app avvia un pagamento con
Accept.payments.pay(amount, currency). - L'SDK registra il pagamento nel backend Tapaya e avvia il plugin Tapaya Terminal sullo stesso dispositivo.
- Il cliente avvicina la carta o il telefono allo schermo di Tapaya Terminal.
- Tapaya Terminal conclude il pagamento con il backend Tapaya.
- L'SDK osserva il pagamento finché non raggiunge uno stato finale ed emette
PaymentEvent.Resultcon unPayResult. Se il pagamento non può essere creato affatto, emette invecePaymentEvent.CreationFailed, di solito entro un secondo.
Prerequisiti
- Il plugin Tapaya Terminal installato sul dispositivo e attivato per il Suo esercente (consulti Installare e attivare il terminale).
- Un dispositivo con NFC e Android 11 (API 30) o versione successiva.
- L'Accept SDK installato e inizializzato, con l'esercente che ha effettuato l'accesso.
- L'autorizzazione alla posizione (
ACCESS_FINE_LOCATION) concessa alla Sua app in fase di esecuzione.
Installare e attivare il terminale
L'attivazione va eseguita una sola volta per dispositivo. Collega il plugin Tapaya Terminal allo stesso account esercente con cui la Sua app ha effettuato l'accesso.
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 è uno tra INVALID_INTENT, HANDOVER_FAILED, CONFIRM_FAILED,
MERCHANT_NOT_ONBOARDED o UNKNOWN.
Nota
Accept.plugin.isInstalled() restituisce false quando il plugin Tapaya Terminal non è presente. Legga lo stato
attuale del plugin in qualsiasi momento con Accept.plugin.status() e protegga di conseguenza la Sua UI di pagamento.
activateTerminal() inoltre lancia NoTidsAvailableForMerchant quando all'esercente non resta alcun terminale non assegnato
(TID) da attivare, separatamente dagli esiti ActivateTerminalResult.Failed indicati sopra. Racchiuda la chiamata in un
try-catch se desidera gestire questo caso in modo distinto, ad esempio invitando l'esercente a liberare o
richiedere un altro TID.
Verificare gli aggiornamenti del plugin
Accept.plugin.updateInfo() indica se su Google Play è disponibile una build più recente del plugin Tapaya Terminal,
così può invitare l'esercente ad aggiornarlo prima che un pagamento incontri un'incompatibilità di versione.
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 */ }
}Nota
L'SDK non interroga mai Google Play direttamente; l'app plugin verifica da sé tramite Play Core e comunica il risultato
attraverso il proprio canale di stato esistente. Una build del plugin precedente a quella che ha introdotto questa funzionalità restituisce sempre
Unknown.
Creare un pagamento
Gli importi sono sempre espressi nell'unità minima della valuta: haléř per CZK, centesimi per EUR. 1500 con
la valuta "CZK" addebita 15,00 Kč. Le valute sono codici ISO 4217.
pay() restituisce un Flow<PaymentEvent>; lo raccolga per osservare l'avanzamento e il risultato finale. Non lancia mai eccezioni;
gli errori si manifestano come 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
}
}Parametri:
| Parametro | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
amount | Long | ✓ | Importo da addebitare, nell'unità minore di currency (ad es. centesimi per USD, haléř per CZK). |
currency | String | ✓ | Codice valuta ISO 4217. |
paymentToken | String? | Riprende una sessione di pagamento esistente invece di avviarne una nuova. Il valore predefinito è null. | |
receiptConfig | PaymentReceiptConfig? | Sostituisce la schermata della ricevuta mostrata dopo un pagamento approvato, solo per questa chiamata. Il valore predefinito è null, che applica il valore predefinito impostato con Accept.payments.setOptions(). | |
nfcPosition | NfcPositionConfig? | Sostituisce il punto indicato dall'animazione di avvicinamento, solo per questa chiamata. Il valore predefinito è null, che applica il valore predefinito impostato con Accept.payments.setOptions(). | |
metadata | Map<String, String> | Dati chiave-valore associati al pagamento. Vengono inviati al plugin Tapaya Terminal nell'intent PAY e restituiti nel PayResult risultante, se il plugin li restituisce. Il valore predefinito è emptyMap(). | |
tip | Long? | Mancia fissa, nell'unità minore di currency. Se impostata, il plugin salta la propria schermata interattiva per la mancia e addebita amount + tip. Il valore predefinito è null (il plugin chiede la mancia al pagatore). | |
transactionTimeout | Duration? | Sostituisce il tempo di attesa del terminale per l'avvicinamento della carta, solo per questa chiamata. Il valore predefinito è null, che applica il valore predefinito impostato con Accept.payments.setOptions(). Consulti Impostare un timeout della transazione. | |
display | PaymentDisplay? | Sostituisce lo schermo su cui viene eseguita la UI di pagamento del plugin, solo per questa chiamata. Il valore predefinito è null, che applica il valore predefinito impostato con Accept.payments.setOptions(). Consulti Scegliere il display. |
Durante il pagamento il plugin Tapaya Terminal è in primo piano. La Sua app torna automaticamente in primo piano
quando il pagamento termina. Per riprendere un pagamento interrotto, passi di nuovo il relativo paymentToken a
pay(amount, currency, paymentToken).
La restituzione di metadata non è garantita
metadata viene restituito in PayResult solo se il plugin lo restituisce, quindi lo consideri un contesto non garantito
(ad es. un ID ordine per i Suoi log), non dati che la Sua app ha bisogno di ricevere indietro.
Scegliere la valuta
pay() addebita nella valuta currency che passa, come codice ISO 4217 in maiuscolo, con amount nell'unità minima
di quella valuta. Valute supportate elenca tutte le valute supportate da Tapaya, ma un esercente
di solito è abilitato solo per alcune di esse.
Chiami Accept.merchant.availableCurrencies() per ottenere i codici ISO 4217 per cui l'esercente è abilitato. Un pagamento in qualsiasi altra
valuta genera l'errore CurrencyNotAvailableForMerchant.
Ogni valuta ha inoltre un importo minimo pagabile, applicato a livello di piattaforma. Recuperi i minimi attuali con
Accept.sdk.minimumAmounts(), che restituisce una List<MinimumAmount> (currency: String, amount: Long, nell'unità
minore della valuta). Una chiamata a pay() al di sotto di tale minimo genera l'errore
PaymentEvent.CreationFailed(AmountBelowMinimum(minimum, currency)). Consulti
Gestione degli errori per l'elenco completo delle eccezioni.
Configurare la schermata della ricevuta
receiptConfig determina cosa mostra il plugin Tapaya Terminal al pagatore dopo un pagamento approvato, per quanto
tempo resta sullo schermo e se viene stampato. Non ha alcun effetto sugli esiti rifiutati, stornati o in attesa di
storno; questi mostrano sempre la propria schermata di esito, indipendentemente da questa impostazione.
PaymentReceiptConfig | Descrizione |
|---|---|
DismissImmediately | Al pagatore non viene mostrato nulla. |
Show(showQrCodeReceipt, dismissBehavior, printBehavior) | Mostra la schermata della ricevuta. showQrCodeReceipt: Boolean = true attiva o disattiva un codice QR con il link alla ricevuta. dismissBehavior: DismissBehavior = DismissBehavior.Manual determina come si chiude: Manual (resta finché il pagatore non la chiude) o Delayed(delay: Duration) (si chiude automaticamente dopo delay se il pagatore non l'ha chiusa prima). printBehavior: ReceiptPrintBehavior = ReceiptPrintBehavior.OnDemand controlla lo scontrino fisico: Automatic (viene stampato non appena compare la schermata), OnDemand (solo quando lo richiede il cassiere) o Disabled (non viene mai stampato). |
Imposti un valore predefinito per l'intero dispositivo una sola volta con Accept.payments.setOptions() e lo sostituisca per un singolo pagamento tramite
pay(..., receiptConfig = ...). Finché non viene chiamato setOptions(), il valore predefinito è Show con ricevuta QR, chiusura
manuale e stampa su richiesta, in linea con il comportamento di riserva del plugin stesso.
// 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,
)Posizionare l'animazione di avvicinamento NFC
nfcPosition è un'indicazione che comunica al plugin Tapaya Terminal dove si trova l'antenna NFC di questo dispositivo, così da poter
posizionare correttamente l'animazione “avvicini qui”. Il plugin determina prima autonomamente la posizione dell'antenna (rilevamento in tempo reale della piattaforma,
poi un database di dispositivi incluso, poi qualsiasi impostazione configurata dall'esercente direttamente nel plugin); fornisca questo valore solo
per sostituire tale determinazione, ad esempio su un dispositivo che il plugin non riesce a rilevare o che ha registrato in modo errato nel database.
NfcPositionConfig | Descrizione |
|---|---|
Unspecified | Nessuna sostituzione (predefinito). Il plugin determina la posizione autonomamente e mantiene qualsiasi posizione già configurata lì dall'esercente. |
OnDevice(surface, xFraction, yFraction) | L'antenna si trova sul dispositivo stesso; l'animazione punta a (xFraction, yFraction) su surface, che può essere OnDeviceSurface.BACK o OnDeviceSurface.FRONT_SCREEN. |
ExternalAbove | Un modulo lettore separato si trova sopra il telefono (supporto smart-POS); non c'è alcun punto sul dispositivo da indicare. |
Imposti un valore predefinito per l'intero dispositivo con Accept.payments.setOptions(nfcPosition = ...), oppure lo sostituisca per singola chiamata tramite
pay(..., nfcPosition = ...). setOptions() modifica solo le opzioni che passa, quindi setOptions(nfcPosition = ...)
da solo non altera un valore predefinito di receiptConfig impostato in precedenza, e viceversa.
Impostare un timeout della transazione
transactionTimeout determina per quanto tempo il plugin Tapaya Terminal attende che il cliente avvicini la carta prima
di rinunciare. Accetta qualsiasi Duration da zero fino a 120 secondi; qualsiasi valore al di fuori di questo intervallo si manifesta come
TransactionTimeoutOutOfRange(transactionTimeout) tramite PaymentEvent.CreationFailed, poiché pay() non lancia mai eccezioni
in modo sincrono.
Accept.payments.pay(
amount = 1500,
currency = "CZK",
transactionTimeout = 90.seconds,
)Lasci il valore predefinito null per usare il timeout di riserva dell'app companion, di circa 60 secondi, oppure imposti un valore
predefinito per l'intero dispositivo con Accept.payments.setOptions(transactionTimeout = ...).
Scegliere il display
Su un dispositivo a doppio schermo (“double-sided”), un pannello è rivolto verso l'esercente e un secondo pannello, lato cliente, è rivolto verso
il pagatore. Accept.displays indica di quali display dispone il dispositivo in questo momento, e l'opzione display indica al plugin
su quale pannello eseguire la propria UI di pagamento.
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,
)PaymentDisplay | Descrizione |
|---|---|
Default | Il display corrente dell'app host (comportamento predefinito, invariato sui dispositivi a schermo singolo). |
CustomerFacing | Il pannello secondario di un dispositivo a doppio schermo. |
Specific(displayId) | Un display specifico, indicato dall'id riportato in Accept.displays.all(). |
Una destinazione che non può essere rispettata, ad esempio CustomerFacing su un dispositivo a schermo singolo, ricade silenziosamente su
Default. Imposti un valore predefinito per l'intero dispositivo con Accept.payments.setOptions(display = ...), oppure lo sostituisca per singola chiamata tramite
pay(..., display = ...).
Imposti la destinazione prima dell'autenticazione
Il riscaldamento (warm-up) del terminale avviato da Accept.auth.authenticate() segue la stessa destinazione, quindi chiami
setOptions(display = ...) prima di authenticate() se desidera che anche il warm-up non compaia sullo schermo
dell'esercente.
L'app di esempio :dualscreen mostra un checkout completo su due pannelli: l'esercente lavora sul pannello principale mentre
PaymentDisplay.CustomerFacing porta la UI di pagamento del plugin sullo schermo del cliente.
Esito del pagamento
L'evento finale PaymentEvent.Result contiene un PayResult. Ogni variante contiene metadata, che riporta quanto Lei ha
passato a pay() se il plugin lo restituisce (può essere null):
PayResult | Significato |
|---|---|
Success(paymentToken, metadata, authCode, receiptDetails) | Autorizzato e acquisito. I fondi verranno regolati a favore dell'esercente. authCode è il codice di autorizzazione del processore riportato dal terminale; è informativo e può mancare a seconda del processore. |
Declined(metadata, receiptDetails) | Rifiutato dal processore della carta o dalla banca emittente. |
Canceled(metadata) | Annullato dal cliente o dall'esercente prima del completamento. |
Failed(reason, metadata) | Non completato; reason è un PayFailureReason. |
PayFailureReason è uno tra INVALID_INTENT, NOT_ACTIVATED, TERMINAL_INIT_FAILED, PAYMENT_SETUP_FAILED,
PLUGIN_UNAVAILABLE o UNKNOWN.
Leggere i dettagli della ricevuta
Success e Declined contengono un receiptDetails: ReceiptDetails? facoltativo con i dati strutturati della ricevuta della carta
riportati dal terminale, pensato per chi stampa una propria ricevuta ufficiale della carta invece di affidarsi soltanto
alla pagina ospitata di PaymentStatus.receiptUrl.
| Campo | Tipo | Descrizione |
|---|---|---|
merchantId | String? | ID esercente riportato dal processore. |
terminalId | String? | ID terminale riportato dal processore. |
cardBrand | String? | Circuito della carta, ad es. Visa o Mastercard. |
appLabel | String? | Etichetta dell'applicazione EMV. |
aid | String? | Identificatore dell'applicazione EMV. |
maskedPan | String? | Numero di carta mascherato. |
responseCode | String? | Codice di risposta del processore. |
transactionTimestamp | String? | Timestamp della transazione in formato ISO-8601. |
Ogni campo è facoltativo e non garantito; il chiamante deve essere pronto a gestire l'assenza di uno qualsiasi di essi, o dello stesso receiptDetails.
Leggere i dettagli del pagamento
PayResult.Success fornisce il paymentToken. Recuperi il record completo, inclusi receiptUrl, gli importi richiesti e
regolati e i timestamp, con Accept.payments.status():
val status: PaymentStatus = Accept.payments.status(paymentToken)
status.state // PENDING, SUCCESS, CANCELLED, REFUNDED, FAILED, ACTION_NEEDED
status.receiptUrl // for your receipt UIInterrompa un pagamento in corso con Accept.payments.cancel(paymentToken), che restituisce il PaymentState risultante.
Emettere un rimborso
Rimborsi un pagamento concluso con Accept.payments.refund(paymentToken, reason), che restituisce anch'esso il
PaymentState risultante.
val state: PaymentState = Accept.payments.refund(paymentToken, RefundReason.CUSTOMER_REQUEST)RefundReason è uno tra CUSTOMER_REQUEST, DUPLICATE, FRAUDULENT o EXPIRED_CHARGE.
Gestione degli errori
Un pagamento che raggiunge il terminale ma non va a buon fine non è un'eccezione; arriva come
PaymentEvent.Result con PayResult.Declined, Canceled o Failed(reason). Gli errori che impediscono
la creazione del pagamento arrivano come PaymentEvent.CreationFailed(cause), dove cause è un'AcceptException
tipizzata. Quelle specifiche di questa integrazione:
| Eccezione | Quando si verifica | Cosa fare |
|---|---|---|
PluginUnavailable | Il plugin Tapaya Terminal non è installato o non è stato possibile collegarsi al suo servizio. | Inviti all'installazione con Accept.plugin.install(). |
CurrencyNotAvailableForMerchant(code) | L'esercente non è configurato per la valuta richiesta. | Addebiti in una valuta abilitata, oppure aggiorni la configurazione dell'esercente. |
AmountBelowMinimum(minimum, currency) | L'importo è inferiore al minimo configurato. | Addebiti un importo pari o superiore a minimum. |
TransactionTimeoutOutOfRange(transactionTimeout) | transactionTimeout era al di fuori dell'intervallo da 0 a 120 secondi. | Passi un valore compreso nell'intervallo, oppure lo ometta per usare il valore predefinito. |
LocationPermissionRequired | Alla Sua app non è stata concessa l'autorizzazione alla posizione. | Richieda ACCESS_FINE_LOCATION, quindi riprovi. |
LocationUnavailable / LocationTimeout | Non è stato possibile determinare la posizione del dispositivo. | Chieda all'utente di attivare la posizione e riprovi. |
SessionExpired | La sessione autenticata è scaduta. | Ripeta l'autenticazione con Accept.auth.authenticate(). |
NoInternetConnection | Nessuna connessione di rete. | Chieda all'utente di riconnettersi e riprovi. |
PaymentInProgress | Una chiamata pay() precedente è ancora in attesa di un risultato. | Attenda che il pagamento in corso termini. |
Accept.payments.status(), cancel() e refund() sono funzioni suspend e lanciano queste eccezioni
(invece di emettere eventi); le racchiuda in un try-catch. status()/cancel()/refund() lanciano inoltre
PaymentNotFound per un token sconosciuto. Consulti l'elenco completo in
Gestione degli errori.
Timeout
Un pagamento con carta deve essere completato sul terminale poco dopo il suo avvio. Se il cliente non avvicina mai la carta, o se il
plugin Tapaya Terminal viene chiuso durante il pagamento, il pagamento termina come PayResult.Canceled o
PayResult.Failed, e il pagamento sottostante viene annullato lato server e non viene mai regolato. Verifichi sempre lo
stato finale con Accept.payments.status() prima di considerare completato un pagamento.
Integrare senza l'SDK
Se non può incorporare l'Accept SDK (un framework multipiattaforma, una shell POS leggera, una cassa basata sul web), può controllare Tapaya Terminal direttamente con un deeplink. La suddivisione del lavoro cambia: è Lei a creare il pagamento tramite la Tapaya API ed è Lei a interrogarne periodicamente lo stato per conoscere il risultato; Tapaya Terminal gestisce solo l'interazione con la carta.
Il flusso:
- Crei un pagamento dal Suo backend tramite la Payments API e conservi
il relativo
paymentId. - Apra Tapaya Terminal con quel
paymentIdusando il deeplink di pagamento. - Interroghi periodicamente lo stato del pagamento tramite la Payments API finché non esce
da
PENDING. I deeplink non hanno un canale di ritorno, quindi l'interrogazione periodica è l'unica fonte attendibile.
Nota
Tapaya Terminal deve essere installato, con accesso effettuato all'account esercente e configurato per la valuta del pagamento. In questa modalità non c'è alcun passaggio di attivazione; il merchant of record è l'account che ha effettuato l'accesso. Se necessario, faccia accedere un esercente con il deeplink di accesso.
Schemi dei deeplink
Lo schema determina quale build di Tapaya Terminal gestisce il link:
| Ambiente | Schema |
|---|---|
| Produzione | tapaya-accept |
| Sandbox | tapaya-accept-sandbox |
Se un link non viene risolto, quella build di Tapaya Terminal non è installata.
Deeplink di pagamento
tapaya-accept://pay?paymentId=<paymentId>&amount=<amount>¤cy=<ISO4217>| Parametro di query | Obbligatorio | Note |
|---|---|---|
paymentId | Sì | Il pagamento creato nel passaggio 1. |
amount | Sì | Importo in unità principali come numero intero: amount=15 addebita 15,00. Attenzione: questo differisce dall'SDK, che accetta unità minori. |
currency | Sì | Codice valuta ISO 4217, ad es. CZK. |
val uri = Uri.parse("tapaya-accept://pay?paymentId=$paymentId&amount=15¤cy=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
}Deeplink di accesso
Faccia accedere un esercente a Tapaya Terminal con il suo merchantToken, l'identificatore dell'esercente impostato con POST /merchant/auth/register:
tapaya-accept://login?token=<merchantToken>| Parametro di query | Obbligatorio | Note |
|---|---|---|
token | Sì | Il merchantToken dell'esercente. È il Suo ID esercente stabile, non il token SDK di breve durata di POST /merchant/auth/login. |
activationCode | No | Precompila il codice di attivazione del terminale. |
Testare l'integrazione
- Inizializzi con
isProduction = false(il valore predefinito); i pagamenti in sandbox non muovono mai denaro reale. - Installi il plugin Tapaya Terminal sul dispositivo di test, acceda con un esercente della sandbox ed esegua l'attivazione.
- Esegua un pagamento di importo ridotto e verifichi il flusso
Successdall'inizio alla fine, inclusa la Sua UI della ricevuta. - Provi i percorsi di errore: rifiuti un pagamento sul terminale, ne annulli uno e ne avvii uno con una valuta
non supportata; la Sua gestione di
PayResult/CreationFaileddovrebbe coprire ciascun caso.
Quando è pronto per andare live, imposti isProduction = true in initialize ed esegua l'autenticazione con le credenziali di
produzione. Il codice dei pagamenti non cambia.