Skip to main content

Transaction lifecycle

This page describes how a till integrates with the wallee Terminal API on the same Android device as the wallee SoftPOS APK, how a payment transaction flows through the system, and how the till should recover after a crash, restart, or reboot.

Architecture​

The till app and the wallee SoftPOS APK run on the same Android device. The SDK exposes a service-based API; transport details are handled internally by the SDK.


[ Till APK ] --(ATI SDK)--> [ wallee SoftPOS APK ] --(network)--> [ wallee Portal ]


The portal is the authoritative source of transaction state. The SDK is the till's interface to that state.

Identifiers​

Three identifiers are involved in every transaction. They are owned by three different parties and exist at different points in time.

IdentifierOwnerCreated whenPurpose
merchantReferenceTillBefore createTransaction is calledTill's own business reference for the order
transactionIdSDKReturned by createTransaction in TransactionContextRuntime context identifier for execute, cancel, recovery
transactionReferenceNumberAcquirerReturned in TransactionResult after executionAcquirer or payment processor reference


The till must store all three to fully reconcile a transaction.

Happy Path​

  1. Till generates a merchantReference and persists it to local storage with state PENDING_CREATE.
  2. Till calls sdk.terminal.payments.createTransaction(request).
  3. SDK returns a TransactionContext. Till persists the transactionId against the same row and updates state to PENDING_EXECUTE.
  4. Till calls sdk.terminal.payments.executeTransaction(transactionId).
  5. SDK returns a TransactionResult. Till persists outcome and transactionReferenceNumber, updates state to a terminal value (APPROVED, DECLINED, CANCELLED, FAILED).
  6. Till completes its own business booking (receipt, journal, fiscal memory).

Persistence Requirements On The Till​

The till must persist transaction state in durable storage. In-memory state is lost on process death, app restart, or device reboot, which makes recovery impossible because the till no longer knows which references to query.


Recommended storage is Android Room.


Minimum schema:

FieldNotes
merchantReferencePrimary business key; required before any SDK call
transactionIdPopulated after createTransaction returns
transactionReferenceNumberPopulated after executeTransaction returns
stateTill-side state machine (see below)
createdAt, updatedAtTimestamps
amount, currency, lineItemsFor reconciliation and receipt reprint
rawResultPayloadPersisted TransactionResult for audit


Till-side state machine:

PENDING_CREATE -- createTransaction returned --> PENDING_EXECUTE

PENDING_EXECUTE -- result received --> RESULT_RECEIVED

RESULT_RECEIVED -- booking completed --> BOOKED


The write to PENDING_CREATE must be committed to disk before the SDK call is made. This is a write-ahead pattern: if the till crashes during the SDK call, the persisted row is the only evidence that a transaction was attempted.

Recovery​

Recovery runs on application start, before the till accepts new orders.


For each row in the local database that is not in a terminal acknowledged state, the till must reconcile.


The currently available API for recovery is:

val lastResult = sdk.terminal.payments.getLastResult()
if (lastResult.state == LastPaymentResultState.FOUND) {
val transactionResult = lastResult.result
}

This call returns the most recent final payment result held by the Terminal service. The two possible states are FOUND and NOT_FOUND.

Recovery Limitations​

The current recovery API has the following limitations. Integrators must build their till-side logic with these in mind.

  1. getLastResult() takes no parameters. It returns the last result the SDK holds, not the result for a specific merchantReference or transactionId. The till must verify that the returned transactionId matches the row it is trying to recover.
  2. LastPaymentResultState only distinguishes FOUND from NOT_FOUND. There is no explicit "in flight, not yet final" state. A NOT_FOUND response is ambiguous: it can mean "transaction never started", "transaction is still in flight", or "result has been evicted".
  3. There is no till-to-SDK acknowledgment. The SDK has no knowledge of whether the till has persisted and booked the returned result. The SDK cannot detect duplicate recovery attempts on the till side.
  4. Lookup by merchantReference is not supported. If the till crashed in the window between calling createTransaction and receiving back the TransactionContext, the till does not yet know the transactionId. The till must rely on getLastResult() and match by merchantReference carried in the returned TransactionResult.
  5. The SDK retention policy for the last result is not currently specified.

Given the limitations above, the safest recovery pattern is:

on app start:
pendingRows = db.query(state != BOOKED)
if pendingRows is empty:
return


lastResult = sdk.terminal.payments.getLastResult()


for row in pendingRows:
if lastResult.state == FOUND
and lastResult.result.merchantReference == row.merchantReference:
row.state = RESULT_RECEIVED
row.transactionReferenceNumber = lastResult.result.transactionReferenceNumber
row.outcome = lastResult.result.outcome
db.commit(row)
performBooking(row)
row.state = BOOKED
db.commit(row)
else:
row.state = UNRESOLVED
db.commit(row)
escalateToCashier(row)




UNRESOLVED rows must be surfaced to the cashier or back-office for manual resolution against the wallee portal. The till must not silently treat them as failed or as successful.

Cancellation In Flight​

cancelTransactionExecution(transactionId) requests that the Terminal service stop an in-progress execution. The user or terminal cancellation outcome is returned as a TransactionResult. TerminalCall.cancel() only cancels the local caller handle; it does not stop the transaction on the terminal.


If the till restarts during an in-flight transaction, it cannot call cancelTransactionExecution because the in-process call handle is gone. Recovery via getLastResult() is the only path.

Error Model​

TypeSourceMeaning
TerminalExceptionTerminal callsInfrastructure or technical failure
TransactionResult with non-approved outcomeTerminal callsBusiness outcome including decline, user cancel, terminal cancel
AtiSdkExceptionSDK setupConnection state or lower-level setup failure

Declines and cancellations are not exceptions. They are valid TransactionResult values and must be persisted as such by the till.

Connection Lifecycle​

The till should keep the SDK in an application-scoped holder. Call sdk.connect() once the app starts. Call sdk.disconnect() only when the integration no longer needs the Terminal service. Reconnects after process death are handled by calling connect() again on app start, before recovery runs.

\