Mobile SDK Integration
Integrate the Tapaya Accept SDK into your mobile application.
The Tapaya Accept SDK for Android provides a robust, secure, and easy-to-use toolkit for embedding payment processing directly into your Android application. Designed with modern Android development practices in mind, it supports Kotlin Coroutines, lifecycle-aware components, and a clean API surface.
Installation
Add the Tapaya Accept SDK dependency to your module-level build.gradle file.
dependencies {
implementation("com.tapaya:accept:1.10.0")
}Initialization
The Accept singleton is the entry point for all SDK functionality. Initialize it once (typically in
your Application class), then authenticate separately with a merchant token.
Step 1: Initialize
// Sandbox is the default (isProduction = false)
Accept.initialize(context = this)
// Target production explicitly
Accept.initialize(context = this, isProduction = true)
// Or drive it from your build type
Accept.initialize(context = this, isProduction = !BuildConfig.DEBUG)Step 2: Authenticate
Call Accept.auth.authenticate() after initialization to log in a merchant with a token obtained from your
backend. It is a suspend function and throws an AcceptException on failure. On success, Accept.state
transitions to SdkState.Authenticated.
// suspend; call from a coroutine
try {
val merchantToken = myBackendApi.getMerchantToken()
Accept.auth.authenticate(merchantToken)
// Accept.state now emits SdkState.Authenticated
} catch (e: AcceptException) {
// handle authentication failure
}You can observe Accept.state (a StateFlow<SdkState>) to react to lifecycle changes —
Idle → Initialized → Authenticated.
Core Concepts
The Accept object exposes the SDK through a small set of surfaces:
| Surface | Purpose |
|---|---|
Accept.auth | Authenticate a merchant session. |
Accept.merchant | Merchant profile, config, onboarding status. |
Accept.payments | Create and manage card payments. |
Accept.plugin | Terminal (plugin app) install and activation. |
Accept.displays | Device display info for dual-sided ("double-sided") terminals. |
Accept.sdk | SDK-level config (e.g. minimum amounts). |
Accept.state | StateFlow<SdkState> lifecycle. |
Taking a Payment
Call Accept.payments.pay() with the amount (in the currency's minor unit) and an ISO 4217 currency code. It
returns a Flow<PaymentEvent>; collect it to observe progress and the terminal outcome. pay() never throws —
failures arrive as PaymentEvent.CreationFailed.
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | Long | ✓ | Amount to charge, in the minor unit of currency. |
currency | String | ✓ | ISO 4217 currency code. |
paymentToken | String? | Resumes an existing payment session instead of starting a new one. Defaults to null. | |
receiptConfig | PaymentReceiptConfig? | Overrides the post-payment receipt screen for this call only. Defaults to null. See Configuring the receipt screen. | |
nfcPosition | NfcPositionConfig? | Overrides where the tap-to-pay animation points, for this call only. Defaults to null. See Positioning the NFC tap animation. | |
metadata | Map<String, String> | Key-value data attached to the payment, echoed back on the PayResult if the plugin returns it. Defaults to emptyMap(). | |
tip | Long? | Fixed tip, in the minor unit of currency. When set, the plugin skips its own tip screen and charges amount + tip. Defaults to null. | |
transactionTimeout | Duration? | How long the terminal waits for a card tap, 0 to 120 seconds. Defaults to null (the companion's own ~60 second fallback). Out of range surfaces as TransactionTimeoutOutOfRange via PaymentEvent.CreationFailed. | |
display | PaymentDisplay? | Which screen runs the plugin's payment UI on a dual-sided device. Defaults to null, which falls back to what setOptions() set. See Targeting a display. |
Accept.payments.pay(amount = 15000, currency = "CZK") // 150.00 Kč
.collect { event ->
when (event) {
PaymentEvent.Creating -> Log.d("pay", "creating")
is PaymentEvent.Created -> Log.d("pay", "created ${event.paymentToken}")
PaymentEvent.Launched -> Log.d("pay", "launched")
is PaymentEvent.Result -> Log.d("pay", event.payResult.toString())
is PaymentEvent.CreationFailed -> Log.e("pay", event.cause.toString())
}
}The terminal PaymentEvent.Result carries a PayResult — Success(paymentToken, metadata, authCode, receiptDetails), Declined(metadata, receiptDetails), Canceled(metadata), or Failed(reason, metadata). See
Payment outcome for the full breakdown. Query a payment later with
Accept.payments.status(paymentToken), stop it with Accept.payments.cancel(paymentToken), or refund it with
Accept.payments.refund(paymentToken, reason).
Plugin App
Every card payment hands off to Tapaya Terminal, the Tapaya plugin app; it is always required, there is no card payment path that skips it. Check whether it is installed, then activate the terminal, or open the store listing so the merchant can install it.
if (Accept.plugin.isInstalled()) {
// suspend; returns ActivateTerminalResult (Success / Canceled / Failed)
Accept.plugin.activateTerminal()
} else {
Accept.plugin.install() // opens the store listing
}Read the plugin's current state with Accept.plugin.status(), or sign the merchant out of the plugin with
Accept.plugin.logout().
Permissions
Card payments require the host app to hold location permission (ACCESS_FINE_LOCATION). The SDK does not
request it for you — grant it with the standard Android permission APIs before calling pay().
class PaymentActivity : AppCompatActivity() {
private val requestLocation = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) launchPaymentFlow() else showPermissionRationale()
}
fun startPayment() {
requestLocation.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
}If location is missing or cannot be resolved when pay() runs, the flow emits PaymentEvent.CreationFailed
with LocationPermissionRequired, LocationUnavailable, or LocationTimeout as the cause.
Error Handling
Every checked failure is a subclass of the sealed AcceptException. Suspend calls (authenticate,
merchant.*, payments.status/cancel/refund, plugin.activateTerminal/status/logout/updateInfo,
clear) throw one on failure. payments.pay() is the exception: it never throws and instead reports failures as
PaymentEvent.CreationFailed(cause).
Exception Types
| Exception | Description |
|---|---|
| Lifecycle | |
NotInitialized | A call needs Accept.initialize() first. |
NotAuthenticated | A call needs Accept.auth.authenticate() first. |
SessionExpired | Authenticated session expired; re-authenticate. |
UnknownEnvironment(key) | Environment key not recognized (internal initialize overload). |
| Payments | |
PaymentInProgress | A previous pay() is still awaiting a result. |
CurrencyNotAvailableForMerchant(code) | Currency not configured for the merchant. |
UnsupportedCurrency(code) | Currency not recognized by the SDK. |
AmountBelowMinimum(minimum, currency) | Amount below the configured minimum. |
TransactionTimeoutOutOfRange(transactionTimeout) | transactionTimeout outside the 0 to 120 second range. |
PaymentCreationFailed(cause) | Any other failure while creating a payment. |
PaymentNotFound | status()/cancel()/refund() token unknown to the backend. |
| Location | |
LocationPermissionRequired | Host app lacks location permission. |
LocationUnavailable | No provider could produce a location fix. |
LocationTimeout | Location fix not resolved in time. |
| Plugin / Terminal | |
PluginUnavailable | Plugin app not installed, or its service could not be bound. |
PluginTimeout | Plugin did not respond in time. |
ActivationInProgress | A previous activateTerminal() is still awaiting a result. |
NoTidsAvailableForMerchant | Merchant has no unassigned terminal (TID) left to activate. |
PluginAlreadyAuthenticated | Plugin already authenticated for this merchant. |
PluginMerchantMismatch | Plugin authenticated under a different merchant; call plugin.logout() first. |
MerchantOnboardingIncomplete(state) | Merchant has not finished onboarding. |
| System | |
NoInternetConnection | Device has no network connectivity. |
Unknown(cause) | Any failure the SDK does not recognize as one of its own. |
Result vs. exception
A payment that reaches the terminal but does not succeed is not an exception — it arrives as
PaymentEvent.Result carrying PayResult.Declined, Canceled, or Failed(reason). Likewise, terminal
activation reports ActivateTerminalResult.Failed(reason).
Handling Exceptions
Wrap suspend calls in a try-catch; branch on specific cases or catch AcceptException broadly.
try {
val status = Accept.payments.status(paymentToken)
} catch (e: SessionExpired) {
// Re-authenticate
} catch (e: NoInternetConnection) {
// Ask the user to check connectivity
} catch (e: AcceptException) {
// Handle any other SDK error
Log.e("Payment", "Error: ${e.message}")
}Logging
Enable debug logging during development to troubleshoot issues.
Accept.setDebugLoggingEnabled(true)Logging Out
Clear all locally persisted SDK state (auth token, device id, cached config) and reset Accept.state to
SdkState.Idle. Call this on user logout.
Accept.clear() // suspend