ATI SDK
!WARNING
Draft / Pre-release API
Wallee Terminal API is currently under active development.
This documentation is published for early integrator review and feedback only.
The public API, naming, transaction flows, capabilities, and error models may still change before the first stable release.
Do not treat this draft documentation as a production-ready or backward-compatible contract yet.
Overview
ati-sdk is the public Android library for integrator apps. It exposes a Java-friendly callback API and Kotlin coroutine extensions for the Terminal API.
The SDK entry point is AtiSdk. The Terminal API is grouped by domain:
sdk.terminal.paymentssdk.terminal.configsdk.terminal.batchsdk.terminal.identification
Service communication and transport details are handled internally by the SDK.
Add The SDK
The latest published version is available on Maven Central.
In your root settings.gradle.kts, configure dependency repositories with Google's Android repository and Maven Central:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}In your app module build.gradle.kts, add the SDK dependency:
dependencies {
implementation("com.wallee.terminal:terminal-api:<version>")
}Artifact coordinates:
Group:
com.wallee.terminalArtifact:
terminal-api
Replace <version> with the Terminal API SDK version provided by Wallee.
Clients using Maven Central do not need GitHub Packages or any package registry credentials.
Manifest Setup
For Android 11 and later, declare package visibility for the SoftPOS host application:
<queries>
<package android:name="com.wallee.softpos" />
<intent>
<action android:name="com.wallee.atisdk.action.BIND_TERMINAL_SERVICE" />
</intent>
</queries>When the SDK is configured for another supported host, query that package too. For Paydroid, the package is com.wallee.android.pinpad. Include every host package the app may configure at runtime. The bind action does not change.
The configured host package must match the installed SoftPOS package. The default host package is com.wallee.softpos.
Create The SDK
Keep the SDK in an application-scoped holder. This uses the SoftPOS default host package:
class MyApplication : Application() {
val atiSdk: AtiSdk by lazy {
AtiSdkClient(this)
}
}Select Paydroid by configuring its host package:
class MyApplication : Application() {
val atiSdk: AtiSdk by lazy {
AtiSdkClient(
this,
AtiSdkConfig(hostPackageName = AtiSdkConstants.PAYDROID_HOST_PACKAGE),
)
}
}Connect
Kotlin coroutine style:
val sdk = (application as MyApplication).atiSdk
lifecycleScope.launch {
try {
sdk.connect()
} catch (error: AtiSdkException) {
// Handle connection failure.
}
}Java callback style:
TerminalCall<AtiConnectionInfo> call = sdk.connect(new TerminalCallback<AtiConnectionInfo>() {
@Override
public void onSuccess(AtiConnectionInfo info) {
int apiVersion = info.getApiVersion();
}
@Override
public void onFailure(TerminalException error) {
// Handle connection failure.
}
});Disconnect when the integration no longer needs the Terminal service:
sdk.disconnect()Run A Purchase
Kotlin coroutine style:
val request = TransactionRequest.purchase(
BaseTransactionRequest.builder()
.currency("CHF")
.merchantReference("order-123")
.addLineItem(
PaymentLineItem.builder("item-1", BigDecimal("10.00"))
.name("Demo item")
.build()
)
.build()
)
val context = sdk.terminal.payments.createTransaction(request)
val result = sdk.terminal.payments.executeTransaction(context.transactionId)
if (result is PurchaseResult && result.outcome == RequestOutcome.APPROVED) {
val transactionReferenceNumber = result.transaction?.transactionReferenceNumber
}Java callback style:
sdk.getTerminal().getPayments().createTransaction(
request,
new TerminalCallback<TransactionContext>() {
@Override
public void onSuccess(TransactionContext context) {
String transactionId = context.getTransactionId();
sdk.getTerminal().getPayments().executeTransaction(
transactionId,
new TerminalCallback<TransactionResult>() {
@Override
public void onSuccess(TransactionResult result) {
String runtimeTransactionId = result.getTransactionId();
}
@Override
public void onFailure(TerminalException error) {
// Handle technical failure.
}
}
);
}
@Override
public void onFailure(TerminalException error) {
// Handle technical failure.
}
}
);Run A Void
Create a void context, then execute the returned transactionId. Ordinary reversal does not accept a transaction selector. The transaction to reverse is selected by the terminal host according to its reversal rules. Selecting an arbitrary original transaction is not supported by this API, and ReversalRequest takes no transaction selector. Reservation cancellation is a separate API and is available only where the terminal host supports it; the current SoftPOS host does not.
val request = ReversalRequest(
showTransactionResultScreens = false,
)
val context = sdk.terminal.payments.createVoid(request)
val result = sdk.terminal.payments.executeTransaction(context.transactionId)
if (result is ReversalResult && result.outcome == RequestOutcome.APPROVED) {
val reversalReference = result.transaction?.transactionReferenceNumber
val reversalSequenceCounter = result.transaction?.protocolData?.transactionSequenceCounter
val originalTransactionSequenceCounter =
result.transaction?.protocolData?.originalTransactionSequenceCounter
val merchantReference = result.transaction?.merchantReference
}For a reversal result, transactionSequenceCounter identifies the reversal transaction itself. When available, originalTransactionSequenceCounter identifies the transaction that was reversed. Neither value is the Terminal API runtime transactionId.
Run Batch Processing (Submission + Transmission)
processBatch asks the terminal to submit and transmit its stored transactions in a single request. No transaction context is created; the call completes once the terminal has finished both steps.
val result = sdk.terminal.batch.processBatch(BatchProcessRequest())
when (result.outcome) {
RequestOutcome.SUCCESSFUL -> {
val receipts = result.receipts
}
RequestOutcome.USER_ERROR -> {
val code = result.errorReason?.code
val message = result.errorReason?.message
}
else -> Unit
}Business-level failures reported by the terminal or acquirer host (for example, no connectivity) are returned as a successful call with outcome = USER_ERROR and errorReason set. Technical failures (terminal busy, service unavailable, timeout) are reported as TerminalException. Final balance (executeFinalBalance) follows the same result contract.
Run A Final Balance
executeFinalBalance asks the terminal to run a final balance, which closes the current terminal session with the acquirer host. Like batch processing, no transaction context is created; the call completes once the terminal has received the final balance response, and any receipts it produced are returned in the result.
val result = sdk.terminal.batch.executeFinalBalance(FinalBalanceRequest())
when (result.outcome) {
RequestOutcome.SUCCESSFUL -> {
val receipts = result.receipts
}
RequestOutcome.USER_ERROR -> {
val code = result.errorReason?.code
val message = result.errorReason?.message
}
else -> Unit
}outcome = SUCCESSFUL confirms that the terminal received the final balance response; it does not assert successful settlement by every acquirer. Business-level failures (for example, no connectivity) are returned as outcome = USER_ERROR with errorReason set, and technical failures (terminal busy, service unavailable, timeout) are reported as TerminalException.
Cancel Execution
Ask the Terminal service to stop an execution that is still in progress:
val cancellation = sdk.terminal.payments.cancelTransactionExecution(transactionId)TerminalCall.cancel() cancels the local caller handle. User or terminal cancellation is returned as a TransactionResult business outcome.
Get Last Result
Look up the latest purchase or reversal result from VCS. The lookup uses receipt reprint and can print the receipt again. FOUND requires the terminal host to match the VCS transaction type, terminal, and EP2 counter to the calling ATI app; other callers cannot read it. Approved and declined backend outcomes may be recovered after a lost callback or SoftPOS restart. Local user errors and executions without a backend result cannot be recovered. NOT_FOUND also applies if a later terminal transaction has replaced this caller's result or ownership cannot be established. Reprint and connection failures are returned as technical terminal errors.
val lastResult = sdk.terminal.payments.getLastResult()
if (lastResult.state == LastPaymentResultState.FOUND) {
val transactionResult = lastResult.result
}Other Payment Operations
Additional payment-domain operations also create transaction contexts:
val refund = sdk.terminal.payments.createRefund(refundRequest)
val capture = sdk.terminal.payments.createReservationCapture(captureRequest)
val adjustment = sdk.terminal.payments.createReservationAdjustment(adjustmentRequest)
val cancellation = sdk.terminal.payments.createReservationCancellation(cancellationRequest)
val voidContext = sdk.terminal.payments.createVoid(voidRequest)Execute the returned transactionId with sdk.terminal.payments.executeTransaction(...).
The current SoftPOS service supports unlinked refunds in terminal-configured currencies with a positive amount matching the currency exponent and text receipts. RefundRequest.currency defaults to CHF for existing callers. A currency missing from terminal configuration fails before payment starts. The service returns an unsupported-capability error for linked refunds (transactionReferenceNumber or acquirerId) and HTML receipts. Check supportsRefund before creating a refund.
Use RefundRequest.fromBase(baseTransactionRequest) to send the same line items, currency, and transaction options as purchase. The refund amount is the sum of the line items. The original acquirer reference, when needed, remains separate from base.transactionRefNumber, which is the client reference. Existing amount-only refund requests remain supported.
Config, Batch, And Identification
val capabilities = sdk.terminal.config.getCapabilities()
val status = sdk.terminal.config.getTerminalStatus()
val info = sdk.terminal.config.getTerminalInfo()
val configuration = sdk.terminal.config.executeConfiguration() // SI(CONFIG)
val initialisation = sdk.terminal.config.executeInitialisation() // SI(INIT)
val activation = sdk.terminal.config.activateDevice("012345")
val batchResult = sdk.terminal.batch.processBatch(batchRequest)
val finalBalance = sdk.terminal.batch.executeFinalBalance(finalBalanceRequest)
val panHash = sdk.terminal.identification.getPanHash(panHashRequest)Device activation reports success only after initiation finishes. Do not log activation codes. executeConfiguration() downloads terminal configuration using SI(CONFIG) without running acquirer initialization. executeInitialisation() runs SI(INIT) separately and reports a final outcome = SUCCESSFUL or outcome = USER_ERROR. Device activation uses the same result structure. A USER_ERROR contains a nonblank errorReason.code and errorReason.message; the YAML contract names this object reason. Technical failures (such as invalid requests, busy terminals, IPC failure or timeout) use TerminalException. Check supportsConfiguration, supportsInitialisation and supportsDeviceActivation before starting these operations.
Before dispatching any of these setup commands, SoftPOS applies the SDK preflight checks. A rejected request uses TerminalCallback.onFailure (or throws TerminalException in the suspend API), preserving its error code, message, and recoverability. It is not a USER_ERROR business outcome:
| Rejection | Error code | Recoverability | Message |
|---|---|---|---|
Device already activated (activateDevice only) | INVALID_REQUEST | NON_RETRYABLE | Device is already activated |
| Enrollment not ready | TERMINAL_NOT_READY | RETRYABLE | Enrollment not ready |
| Certificate not ready | TERMINAL_NOT_READY | RETRYABLE | Original SDK certificate error message |
| Terminal busy | TERMINAL_BUSY | RETRYABLE | Original SDK busy error message |
Activation may wait for foreground enrollment startup within its request deadline before checking readiness. A rejected activation never sends the code to the backend and never implicitly resets the device. For an already linked device, run executeInitialisation() instead of submitting another activation code. Capability, terminal info, and status snapshots do not require enrollment readiness; terminal info still requires a configured terminal ID.
Activation, SI(CONFIG), and SI(INIT) final callbacks use asynchronous Binder delivery. Android may freeze the caller while SoftPOS is in the foreground; the outcome is queued until the caller resumes. After a final business result (SUCCESSFUL or USER_ERROR), SoftPOS returns to the caller, where the queued callback is delivered. Sending the callback does not wait for the caller to process it. For the current development delivery, TERMINAL_API_VERSION and TERMINAL_SERVICE_VERSION remain 1. Update the development host and client SDK together. Earlier builds that also advertise version 1 are not guaranteed to understand the changed Config result DTOs; matching version numbers alone do not establish compatibility. A different numeric version is still rejected during connection.
getTerminalInfo() requires only the configured terminal ID. Serial number, portal space, merchant, and acquirer metadata are null when unavailable; Android restrictions on hardware serial access do not make an initialized terminal unavailable.
The identification API uses PAN hash terminology and does not expose raw PAN data.
Important Terminology
Payment: API domain grouping for financial terminal operations.
TransactionContext: executable runtime context created by a payment-domain operation.
transactionId: runtime context identifier used for execute, cancel, and recovery.
transactionReferenceNumber: acquirer/payment reference returned after execution. It is not the same as
transactionId.Reservation: authorization-like hold.
Capture: completion of a reservation.
Reservation cancellation: cancellation of a reserved transaction. It is distinct from
cancelTransactionExecution.Void: payment-domain operation for voiding a previous transaction. Final product terminology is tracked in the open questions.
Error Handling
Infrastructure and technical failures are reported as TerminalException from Terminal calls.
Business outcomes, including declines and user or terminal cancellation, are represented as TransactionResult values.
AtiSdkException is used for SDK connection state and lower-level setup failures.
Changelog
Public release notes are published in CHANGELOG.md and on the GitLab Pages/KDoc site: View the changelog.
Demo App Note
The repository includes a sample client app for local development. Any demo UI shown by a payment Terminal service is not part of the SDK contract.
API Reference Generation
Generate HTML API documentation from KDoc:
./gradlew :ati-sdk:dokkaHtmlGenerated output:
ati-sdk/build/dokka/html