Tapaya
Accettazione in presenzaGuida all'integrazioneApp Tapaya Terminal

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

OpzioneIdeale perConsegna del risultato
Accept SDK (consigliato)App Android nativeAccept.payments.pay() emette un Flow<PaymentEvent>: creazione, avvio e risultato finale del terminale, gestiti automaticamente
DeeplinkWebView, framework multipiattaforma, codici QRLei 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

  1. La Sua app avvia un pagamento con Accept.payments.pay(amount, currency).
  2. L'SDK registra il pagamento nel backend Tapaya e avvia il plugin Tapaya Terminal sullo stesso dispositivo.
  3. Il cliente avvicina la carta o il telefono allo schermo di Tapaya Terminal.
  4. Tapaya Terminal conclude il pagamento con il backend Tapaya.
  5. L'SDK osserva il pagamento finché non raggiunge uno stato finale ed emette PaymentEvent.Result con un PayResult. Se il pagamento non può essere creato affatto, emette invece PaymentEvent.CreationFailed, di solito entro un secondo.

Prerequisiti

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:

ParametroTipoObbligatorioDescrizione
amountLong✓Importo da addebitare, nell'unità minore di currency (ad es. centesimi per USD, haléř per CZK).
currencyString✓Codice valuta ISO 4217.
paymentTokenString?Riprende una sessione di pagamento esistente invece di avviarne una nuova. Il valore predefinito è null.
receiptConfigPaymentReceiptConfig?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().
nfcPositionNfcPositionConfig?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().
metadataMap<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().
tipLong?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).
transactionTimeoutDuration?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.
displayPaymentDisplay?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.

PaymentReceiptConfigDescrizione
DismissImmediatelyAl 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.

NfcPositionConfigDescrizione
UnspecifiedNessuna 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.
ExternalAboveUn 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,
)
PaymentDisplayDescrizione
DefaultIl display corrente dell'app host (comportamento predefinito, invariato sui dispositivi a schermo singolo).
CustomerFacingIl 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):

PayResultSignificato
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.

CampoTipoDescrizione
merchantIdString?ID esercente riportato dal processore.
terminalIdString?ID terminale riportato dal processore.
cardBrandString?Circuito della carta, ad es. Visa o Mastercard.
appLabelString?Etichetta dell'applicazione EMV.
aidString?Identificatore dell'applicazione EMV.
maskedPanString?Numero di carta mascherato.
responseCodeString?Codice di risposta del processore.
transactionTimestampString?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 UI

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

EccezioneQuando si verificaCosa fare
PluginUnavailableIl 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.
LocationPermissionRequiredAlla Sua app non è stata concessa l'autorizzazione alla posizione.Richieda ACCESS_FINE_LOCATION, quindi riprovi.
LocationUnavailable / LocationTimeoutNon è stato possibile determinare la posizione del dispositivo.Chieda all'utente di attivare la posizione e riprovi.
SessionExpiredLa sessione autenticata è scaduta.Ripeta l'autenticazione con Accept.auth.authenticate().
NoInternetConnectionNessuna connessione di rete.Chieda all'utente di riconnettersi e riprovi.
PaymentInProgressUna 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:

  1. Crei un pagamento dal Suo backend tramite la Payments API e conservi il relativo paymentId.
  2. Apra Tapaya Terminal con quel paymentId usando il deeplink di pagamento.
  3. 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.

Lo schema determina quale build di Tapaya Terminal gestisce il link:

AmbienteSchema
Produzionetapaya-accept
Sandboxtapaya-accept-sandbox

Se un link non viene risolto, quella build di Tapaya Terminal non è installata.

tapaya-accept://pay?paymentId=<paymentId>&amount=<amount>&currency=<ISO4217>
Parametro di queryObbligatorioNote
paymentIdSìIl pagamento creato nel passaggio 1.
amountSìImporto in unità principali come numero intero: amount=15 addebita 15,00. Attenzione: questo differisce dall'SDK, che accetta unità minori.
currencySìCodice valuta ISO 4217, ad es. 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
}

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 queryObbligatorioNote
tokenSìIl merchantToken dell'esercente. È il Suo ID esercente stabile, non il token SDK di breve durata di POST /merchant/auth/login.
activationCodeNoPrecompila il codice di attivazione del terminale.

Testare l'integrazione

  1. Inizializzi con isProduction = false (il valore predefinito); i pagamenti in sandbox non muovono mai denaro reale.
  2. Installi il plugin Tapaya Terminal sul dispositivo di test, acceda con un esercente della sandbox ed esegua l'attivazione.
  3. Esegua un pagamento di importo ridotto e verifichi il flusso Success dall'inizio alla fine, inclusa la Sua UI della ricevuta.
  4. 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/CreationFailed dovrebbe 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.

Manca qualcosa in questa pagina?Richieda una funzionalità. Alleghiamo questa pagina, così il team vede il contesto.