Changelog
Stay up to date with the latest changes across Tapaya products.
- 1.19.1
Accept Android 1.19.1
FixedFixed
- Terminal activation could reject a valid activation code when the SDK authenticated before the companion was installed.
- 1.19.0
Accept Android 1.19.0
ChangedChanged
AcceptAuth.authenticate()uses the device id owned by the companion app when it reports one, so every host app on a device, and a host app reinstalled after an uninstall, is the same device on the backend. The stored id, then a new random one, are used only while no companion can answer. A companion build that predates this reports no id, and the SDK behaves as before.PluginStatus.MerchantMismatch(andPluginMerchantMismatch) are also reported when the companion is activated for a different device, not only a different merchant. The remedy is the same:logout(), then activate again. Devices are compared only once the SDK has authenticated.
- 1.18.0
Accept Android 1.18.0
AddedAdded
NfcPositionConfig.ExternalReadertakes an optionalindicator: ReaderIndicator, telling the companion which screen edge (ReaderEdge) the external reader sits beyond and where along it (offsetFraction, 0 to 1). The tap screen then shows animated chevrons pointing at the reader, for kiosks and docks where the reader is outside the screen. Null keeps today's behavior, and a companion build that predates the option ignores it.
- 1.17.0
Accept Android 1.17.0
AddedFixedAdded
CustomerFacingFlipConfigonAcceptPayments.setOptionsandAcceptPayments.pay, controlling whether the companion rotates the payment UI 180° when the merchant turns the device toward the customer.Disabledturns it off for hardware that is already customer-facing, such as a fixed POS, a dual-sided panel, or a device in a stand. On those the turn never happens and the motion sensors only produce accidental flips. Defaults toAutomatic, which is the existing behavior; a companion build that predates the option ignores it.
Fixed
AcceptPlugin.activateTerminal()throwsPluginUnavailable, as documented, when the plugin is not installed or cannot handle activation. It returnedActivateTerminalResult.Failed(UNKNOWN)instead, after needlessly generating a handover code.
- 1.16.0
Accept Android 1.16.0
AddedChangedAdded
AcceptPlugin.updateInfo()answers from the backend's release metadata, unauthenticated, against the installed package, so it works with no store and with the plugin absent. A Play-installed plugin is still measured against Play, since a release reaches the backend while the same build can sit in review; the plugin's own check stays the fallback.- accept-installer: new optional artifact,
com.tapaya:accept-installer. On a storeless deviceAcceptPlugin.install()downloads the APK and installs it through the system installer. Depending on the artifact is the opt-in — it carriesREQUEST_INSTALL_PACKAGES, which Play requires integrators to declare, and:acceptnever asks for it. The APK is verified against its SHA-256, declared package and version code, a pinned signer, and the installed plugin's own signer before anything is committed. PluginStoreUnavailable,PluginSelfInstallUnavailable,PluginInstallPermissionRequired,PluginInstallAuthRequiredandPluginDownloadNotPermittederrors fromAcceptPlugin.install()— no store, no installer artifact, no "Install unknown apps" grant, no merchant session, or a merchant the backend does not allow the APK. The last is an entitlement decision rather than anything retryable: point the merchant at their account manager or developers@tapaya.com.
Changed
AcceptPlugin.install()no longer always opens a store listing: with no store it takes the download path above, which needs an authenticated merchant and says so before the transfer starts rather than after.
- 1.15.0
Accept Android 1.15.0
FixedFixed
- Payments failed with
LocationUnavailableorLocationTimeouton terminals whosefusedprovider reports itself present and enabled but never produces a fix. On API 31+ a live fix was requested fromfusedalone, so a null or silent answer ended the request even whilenetworkandgpswere enabled and working. A live fix now races every enabled provider at once and takes the first to answer, failing only once all of them have answered without a location. The OS last-known lookup that seeds the cache also coversfusedandpassivenow, so a cold start more often avoids blocking at all.
- Payments failed with
- 1.14.2
Accept Android 1.14.2
ChangedChanged
- Payments no longer wait on a location fix. Every
pay()used to race a live fix against a timeout before the payment could be created, so a terminal on a weak location stack paid that latency on every transaction and reached its last-known fix only after the timeout had already been spent. The most recent known fix is now served immediately, and is refreshed in the background — off the payment path — once it is older than an hour. The cache is seeded from the OS last-known location on a cold start, so only a terminal that has never held a fix blocks at all, and that first wait is capped at 30 seconds. A terminal does not move between transactions, so an hour-old fix describes its location as well as a fresh one. One consequence worth knowing: a cached fix no longer has an upper age bound, so a terminal that has been offline for days serves a days-old fix and refreshes afterwards.
- Payments no longer wait on a location fix. Every
- 1.14.1
Accept Android 1.14.1
FixedFixed
- Payments on devices with a weak or non-GMS location stack failed with
LocationUnavailableeven though the device held a usable fix. The fused provider returned nothing withinFRESH_FIX_TIMEOUT_MS, and the last-known fallback then rejected the only fix on the device becauseMAX_CACHED_LOCATION_AGE_MSwas 2 minutes — on a stationary countertop terminal the last fix is routinely tens of minutes old. The cached/last-known window is now 30 minutes, so the fallback accepts the fixes it exists to accept. A terminal does not move between transactions, so a half-hour-old fix describes its location as well as a fresh one; the fresh-fix attempt still runs first and is still preferred when it succeeds.
- Payments on devices with a weak or non-GMS location stack failed with
- 1.14.0
Accept Android 1.14.0
AddedFixedAdded
Accept.setTheme()/getTheme()let host apps forward org branding (colors + logos) to the companion app's activation, status, and payment screens.AcceptThememirrors the existing org-theme wire format field-for-field, pushed to the plugin via thesetTheme()AIDL call and aTHEME_JSONintent extra onPAY/ACTIVATE_TERMINAL/WARM_UP.setTheme()persists until changed or cleared (null, orclear()) — it's session-wide branding, not a per-payment override — and is safe to call before or afterinitialize().Accept.createLocalThemeImageUri()covers logos the host app has locally (e.g. picked from the photo gallery) rather than hosted at a stable URL: writes the bytes to SDK-private storage and returns acontent://URI the companion is granted read access to, usable anywhereAcceptTheme's image fields take a URL.
Fixed
activateTerminal()now fails fast withMerchantOnboardingIncompletewhen the merchant hasn't finished onboarding or its card payment method isn't ready, instead of proceeding to build the intent and launch the plugin activity. The gate had been temporarily disabled while merchant onboarding was being fixed on the backend; it's re-enabled now that the backend fix has shipped.- Plugin binder calls (bind/connect/AIDL call) now run on
Dispatchers.IOinstead of whatever dispatcher the caller launched on. The cold-bind wait (up toBIND_TIMEOUT_MS) previously ran on Main by default when called from a ViewModel'sviewModelScope.launch, freezing input dispatch and risking an ANR.setTheme()is also markedoneway, so pushing a theme never waits on the companion at all.
- 1.13.0
Accept Android 1.13.0
ChangedFixedChanged
- A plugin bind that fails now says why.
PluginUnavailableused to be raised behind a singleplugin bind failedline, which reads identically whether the plugin is not installed, installed but disabled, uninstalled-for-this-user with its data kept, or merely invisible because the host app's merged manifest is missing the<queries>entry API 30+ package visibility requires. The SDK now tells those four apart and logs the distinction to integrators, alongside the counts of services that resolved with and without disabled components, the plugin's version and install source, and which other environment's plugin build is on the device. A successful bind records the same identity once, and again whenever it changes, so a later failure can be read against a baseline instead of in isolation — a version skew and a plugin disabled after the fact no longer look alike in a field report. Diagnostics only: no public type changed, andisInstalled()still answers exactly what it did before, a disabled plugin included.
Fixed
- Every failed bind leaked a
ServiceConnection.bindService()registers the connection before it asks the system to bind, so neither afalsereturn nor a thrownSecurityExceptionunregisters it, and the hostContextaccumulated one leaked connection per failure (ServiceConnectionLeaked). Both paths now unbind. - A
SecurityExceptionfrombindService()— the plugin's service not exported to the host app, or gated behind a permission it does not hold — propagated to the caller as-is instead of thePluginUnavailablethe API documents. It is now caught, logged with its cause attached, and reported asPluginUnavailable. - A plugin whose process died before it connected — killed, updated, or force-stopped mid-bind — made the caller wait out the full bind timeout for a connection that was never coming. The binding's death now fails the call immediately.
- A plugin bind that fails now says why.
- 1.12.0
Accept Android 1.12.0
AddedAdded
- The hosted receipt page's URL is now available from the payment flow itself, instead of only from a later
PaymentStatuslookup.PaymentEvent.CreatedcarriesreceiptUrlalongside the payment token — so the link is in hand before the plugin is even launched — andPayResult.Success/PayResult.Declinedcarry it too, so a terminal result is self-contained. The URL resolves to the finished receipt once the payment settles; the link itself is stable from creation. Both fields are nullable and default tonull, so existing construction andcopy()calls keep compiling.
- The hosted receipt page's URL is now available from the payment flow itself, instead of only from a later
- 1.11.0
Accept Android 1.11.0
AddedAdded
NfcPositionConfig.ExternalReaderfor setups where the antenna is on a separate reader that is not above the device — a counter reader, a side-mounted pinpad, a customer-facing dock. There is no point on the device to place and no direction to point at, so the companion drops its tap indicator entirely and shows the amount with a prompt underneath it instead. Its optionalmessagereplaces the companion's own localized prompt for setups the default wording doesn't describe; you own its localization, blank counts as unset, and the companion trims and clamps it. Companion app 1.5.2 or newer is required — older builds treat the new mode as no override at all and keep resolving antenna position themselves, so nothing breaks.
- 1.10.0
Accept Android 1.10.0
AddedChangedAdded
AcceptPlugin.activateTerminal()now throwsNoTidsAvailableForMerchantwhen the merchant has no unassigned terminal (TID) left to activate, instead of a genericUnknownerror.- Dual-sided ("double-sided") device support:
Accept.displays(all(),customerFacing(),isDualScreen) reports the device's screens, and the newPaymentDisplayoption onAcceptPayments.setOptions(display = ...)/pay(display = ...)runs the plugin's payment UI on the customer-facing panel while the host app keeps the merchant one. A target that can't be honored falls back to the default display, so single-screen behavior is unchanged. The terminal warm-up fired byauthenticate()follows the same target. New:dualscreensample app shows the full two-panel checkout.
Changed
PayHostActivitynow declares an emptytaskAffinity, so a payment always starts a fresh task instead of stacking onto the host app's. Required for the cross-display launch above; on a single-screen device the only visible difference is that the invisible host no longer joins the host app's back stack.- The SDK's remote diagnostics now identify the device behind a log line: device id, brand, model, OS version, and the host app's package name, plus a one-time record of the device's display layout at
initialize(). Internal diagnostics only — nothing about the merchant's customers is collected, and the sink stays off entirely in debug builds.
- 1.9.1
Accept Android 1.9.1
FixedFixed
- Location fixes now fall back to the OS's last-known location when a fresh fix times out or the live request reports unavailable, instead of failing outright. On Android 11 (API 30),
LocationManager.getCurrentLocation()is no longer used — it's unreliable on that API level — in favor of a one-shotrequestLocationUpdates()request. Also guards against duplicate/late location callbacks and prefers the network provider before GPS.
- Location fixes now fall back to the OS's last-known location when a fresh fix times out or the live request reports unavailable, instead of failing outright. On Android 11 (API 30),
- 1.9.0
Accept Android 1.9.0
AddedAdded
AcceptPlugin.updateInfo()reports whether a newer version of the plugin app is available on Play (PluginUpdateInfo:UpToDate/UpdateAvailable(priority)/Unknown). The plugin app checks itself via Play Core's in-app update API and reports the result over the existing status channel — the SDK never queries Play directly. Requires a plugin app build that sets the newupdate_available/update_prioritystatus fields; older plugin builds reportUnknown.
- 1.8.0
Accept Android 1.8.0
AddedAdded
ReceiptDetailsonPayResult.Success/PayResult.Declinedexposes structured card-receipt data reported by the terminal — merchant ID, terminal ID, card brand, EMV application label/AID, masked PAN, response code, and an ISO-8601 transaction timestamp — for callers that print their own official card receipt instead of relying solely onPaymentStatus.receiptUrl's hosted HTML page. Every field is optional/best-effort.
- 1.7.0
Accept Android 1.7.0
AddedChangedFixedAdded
- Optional
transactionTimeout(Duration) onAcceptPayments.pay()andsetOptions()controls how long the companion waits for a card tap. Validated to 0..120 seconds; out-of-range surfaces asTransactionTimeoutOutOfRangeviaPaymentEvent.CreationFailed(pay()never throws synchronously).
Changed
AcceptPayments.refund()is now public (previously internal and unreachable by SDK consumers), matchingcancel()'s KDoc/throws style.
Fixed
- Consumer ProGuard/R8 rules:
AcceptExceptionsubtypes not ending inExceptionare now kept by inheritance instead of name-suffix matching, so future subtypes can't fall through the cracks. Also keepsAcceptLogLevel, and scopes the ParcelableCREATORkeep rule to the SDK's own package instead of the whole app.
- Optional
- 1.6.0
Accept Android 1.6.0
AddedChangedFixedAdded
- Optional
tip(Long, minor units) onAcceptPayments.pay()— when set, the plugin skips its own interactive tip screen and chargesamount + tip. PayResult.Success.authCodeexposes the processor authorization code reported by the terminal. Nullable/informational; may be absent depending on the processor.ReceiptPrintBehavioronPaymentReceiptConfig.Show(Automatic,OnDemand,Disabled) controls when the companion prints the physical receipt. Defaults toOnDemand.nfcPositionpayment config hint (NfcPositionConfig) tells the companion where the device's NFC antenna is, to place the tap animation correctly. Defaults toUnspecified.- Optional
metadataparam onAcceptPayments.pay(), threaded through to the plugin and echoed back on allPayResultvariants.
Changed
- Payment extras are no longer logged in full (
extras=...) to avoid leaking metadata values; only extra keys are logged now.
Fixed
- Removed dead
AcceptService/AcceptConfigProvidermanifest entries.
- Optional
- 1.5.0
Accept Android 1.5.0
AddedChangedAdded
Accept.sdk.version,Accept.sdk.isProduction, andAccept.sdk.deviceId()for diagnostics and support — version is available beforeinitialize();isProductionreflects the active environment;deviceId()is a stable, DataStore-backed identifier available afterauthenticate().PaymentReceiptConfigcontrols the receipt screen the companion shows after an *approved* payment:DismissImmediately, orShow(showQrCodeReceipt, dismissBehavior)withDismissBehavior.ManualorDismissBehavior.Delayed(duration). Set a default viaAcceptPayments.setOptions(receiptConfig), or override per call viapay(..., receiptConfig). Has no effect on declined, reversed, or pending-reversal outcomes.
Changed
- The plugin's terminal now pre-warms in the background. Pure optimization — silent no-op until the companion app supports it; the pay path is unaffected either way.
- 1.4.3
Accept Android 1.4.3
AddedChangedAdded
Accept.setLogLevel(AcceptLogLevel)for finer control over diagnostic log verbosity, with the newAcceptLogLevelenum (NONE,ERROR,WARN,INFO,DEBUG,VERBOSE). Defaults toDEBUGagainst sandbox andNONEagainst production.Accept.setLogger(AcceptLogger?)to route consumer-facing SDK logs into a custom sink (Timber, Crashlytics, etc.); passnullto restore the default Logcat sink. Only already-redacted, consumer-facing messages reach the logger.
Changed
Accept.setDebugLoggingEnabled(enabled)is now shorthand forsetLogLevel:truemaps toAcceptLogLevel.DEBUG,falsetoAcceptLogLevel.NONE. PrefersetLogLevelfor finer control.
- 1.4.2
Accept Android 1.4.2
ChangedChanged
- The published
-sources.jarand API reference documentation now contain only the public API;
- The published
- 1.4.1
Accept Android 1.4.1
FixedFixed
- CI: publish the API reference documentation site on release. No functional changes to the SDK from 1.4.0.
- 1.4.0
Accept Android 1.4.0
AddedChangedAdded
- New
Acceptentry point exposing SDK lifecycle as an observablestate: StateFlow<SdkState>. AcceptAuth.authenticate(merchantToken)for merchant authentication.AcceptSdk.minimumAmounts()for merchant-configured minimum payment amounts per currency.AcceptMerchantsurface:info(),config(),onboardingStatus(),availableCurrencies().AcceptPayments.pay()returning aFlow<PaymentEvent>(Creating,Created,Launched,Result,CreationFailed), plusstatus()andcancel().PaymentEvent.Created— the payment token is now available as soon as the backend creates the payment, before the plugin is launched.AcceptPluginsurface for the Accept terminal companion app:isInstalled(),install(),status(),activateTerminal(), andlogout().pay()now fails fast withPluginNotReadyForPaymentsif the plugin isn't authenticated and ready, instead of surfacing a downstream plugin error.- Typed, sealed
AcceptExceptionhierarchy for public failure modes (auth, plugin, location, network, payment) — see KDoc@throwson each function for the exact set. Accept.clear()to reset locally persisted SDK state on logout.Accept.setDebugLoggingEnabled()for opt-in internal debug logging.- Location fix caching to speed up payment creation.
Changed
pay()is nowFlow-based instead of Activity-for-result; results are correlated by request ID and concurrent launches are rejected withPaymentInProgress.- Terminal activation is now a suspend function guarded against concurrent launches (
ActivationInProgress). - Default environment is
SANDBOX; target production explicitly viaAccept.initialize(context, isProduction = true).
- New
- 1.3.2
Accept Android 1.3.2
FixedFixed
AcceptUnknownExceptionis now always constructed with a message, improving error reporting.- Companion-app discovery now matches only
com.tapaya.accept.*packages. - Removed a hardcoded demo token.
- 1.3.1
Accept Android 1.3.1
ChangedChanged
- Updated sandbox base URL to
api.sandbox.tapaya.com. - Example app rewritten to match the iOS example app flow: initialize → login (with auto-register on 401/404) → authenticate state machine, KYB, onboarding status, card status, and companion install/pair.
- Updated sandbox base URL to
- 1.3.0
Accept Android 1.3.0
AddedChangedAdded
- Optional
com.tapaya:accept-stripemodule providing embedded Stripe Tap to Pay. The corecom.tapaya:acceptartifact no longer bundles the Stripe Terminal SDK. - Companion-app management API on
Accept:isCompanionAppInstalled(),pairWithCompanionApp()andpresentCompanionAppSheet().
Changed
- Reduced size of Accept SDK.
- Companion app hand-off now uses an Android Intent instead of a
com.tapaya.accept://deep link.
- Optional