SDK Response Codes & Error Handling
Understand how the Koard Android SDK surfaces errors and transaction outcomes — including the KoardException / KoardError types your app catches, transaction response codes, display messages, and error scenarios.
What you learn
- The two error channels:
KoardException(thrown) vsKoardTransactionResponse(emitted) - The
KoardErrorandKoardErrorTypesealed hierarchy your app inspects for error details - How the SDK wraps all underlying Visa KiC errors into Koard types — you never handle raw KiC exceptions
- The four final transaction statuses: Approve, Decline, Abort, and Failure
- What
statusCodemeans and the numeric codes the underlying Visa KiC kernel sends - Display message IDs shown during the tap-to-pay flow
- Common abort and error scenarios and how to handle them
Error Model Overview
The SDK surfaces errors through two channels depending on context:
| Channel | When | How | What to inspect |
|---|---|---|---|
KoardException (thrown) |
Non-transaction operations: enrollment, SDK init, API calls (capture, refund, reverse, tipAdjust), validation |
try / catch |
exception.error.errorType — a KoardErrorType sealed class |
KoardTransactionResponse (emitted) |
During sale() / preauth() / refund() tap flows |
Callback / Flow | response.actionStatus, response.finalStatus, response.statusCode |
You never handle raw KiC exceptions. The SDK catches every KiCSdkException from the Visa Kernel in the Cloud SDK and maps it to a KoardException with a typed KoardErrorType. Your app only needs to handle Koard types.
KoardException & KoardError
KoardException is the main exception thrown by the SDK for all non-transaction-flow errors. It wraps a KoardError with a human-readable message and a typed error classification:
class KoardException(
cause: Throwable? = null,
val error: KoardError
) : Throwable(error.shortMessage, cause)
data class KoardError(
val shortMessage: String, // Human-readable error description
val errorType: KoardErrorType // Typed error classification (sealed hierarchy)
)
Catching KoardException
try {
sdk.capture(transactionId, amount)
} catch (e: KoardException) {
when (e.error.errorType) {
is KoardErrorType.KoardServiceErrorType.HttpError -> {
val code = (e.error.errorType as KoardErrorType.KoardServiceErrorType.HttpError).errorCode
showError("Server error (HTTP $code): ${e.error.shortMessage}")
}
is KoardErrorType.KoardServiceErrorType.ConnectionError ->
showError("Network error — check your connection")
is KoardErrorType.KoardServiceErrorType.Unauthorized ->
showError("Session expired — please log in again")
is KoardErrorType.VACEnrollmentError ->
showError("Enrollment failed — re-enroll the device")
else ->
showError(e.error.shortMessage)
}
}
KoardErrorType Reference
KoardErrorType is a sealed class hierarchy. Every error the SDK produces maps to one of these types.
Top-Level Error Types
| Error Type | When It Occurs |
|---|---|
GeneralError |
Catch-all for unmapped or unexpected errors |
CertificateError |
TLS or certificate validation failure |
MainThreadError |
SDK method called on the main thread (must use a worker thread) |
NfcTransactionError |
NFC transaction-level failure |
VACEligibilityError |
Device failed Visa Acceptance Cloud eligibility check (e.g., Android < 12) |
DeviceNotProvisionedError |
Device has not been provisioned for Tap to Pay |
VACEnrollmentError |
Enrollment with the Visa Acceptance Cloud failed |
KoardServiceErrorType — API / HTTP Errors
Thrown when SDK methods call the Koard REST API (capture, refund, reverse, tipAdjust, getTransaction, etc.):
| Error Type | Description |
|---|---|
HttpError(errorCode: Int) |
Server returned an HTTP error — inspect errorCode for the status (400, 401, 404, 500, etc.) |
InvalidRequest |
Request validation failed before sending (e.g., negative amount, missing transaction ID) |
NotFound |
Resource not found (404) |
Unauthorized |
Missing or invalid API key / session (401) |
UnexpectedError |
Unexpected server error or empty response body |
ConnectionError |
Network unreachable, DNS failure, or timeout |
DeviceIntegrityError — Security Checks
Thrown when the device fails security validation during enrollment or transaction preparation:
| Error Type | KiC Code | Description |
|---|---|---|
EmulatorDetected |
1000 | Running on an emulator — use a physical device |
RootDetected |
1001 | Device is rooted or has superuser binaries |
TamperDetected |
1002 | Device tamper detection triggered |
DeveloperModeEnabled |
2000 | Developer options must be disabled |
DebugModeEnabled |
2001 | USB debugging must be disabled |
HookDetected |
2003 | Runtime instrumentation detected (Frida, Xposed, etc.) |
GenericIntegrityFailure |
-1 | Generic device integrity attestation failure |
TransactionErrorType — Card & Payment Errors
These appear as the errorType on a KoardException when a transaction-level error is mapped from the KiC thin client. They correspond to EMV-level outcomes:
| Error Type | Description |
|---|---|
TransactionAmountNonPositive |
Amount must be greater than zero |
RefundMissingParentTransactionId |
Refund requires a parent transaction ID |
CancelOrEnter |
Cardholder prompted to cancel or confirm |
CardError |
Unrecoverable card data error |
NotAuthorisedOrDeclined |
Issuer declined the transaction |
PinRequired |
PIN entry is required |
IncorrectPin |
Cardholder entered an incorrect PIN |
ProcessingError |
Generic processing failure |
TryAnotherCard |
Card cannot complete — try a different card |
InsertOrSwipe |
Contactless not supported — use chip or mag-stripe |
TryAnotherChoice |
Try a different payment method |
Cancelled |
Transaction was cancelled |
StrongCvm |
Strong Customer Verification required (SCA) |
PinBypassed |
PIN entry was bypassed |
PinNotProvided |
PIN was requested but not provided |
TransactionNotAllowed |
Transaction type not allowed on this card/terminal |
NotApplicable |
Status not applicable to this transaction type |
UnknownStatus |
Unmapped status from the kernel |
TransactionError |
Generic transaction error |
EnableReader |
NFC reader needs to be enabled |
NetworkError |
Network error during transaction processing |
AuthenticationFailed |
Authentication with the payment backend failed |
CouldNotAttestError |
Device attestation failed during transaction |
AsiError |
Visa auth service interface error |
TcConfigError |
Thin client configuration error |
VACResponseFailedError |
VAC response indicated failure |
VACResponseParseError |
Could not parse VAC response |
TransactionInProgressError |
Another transaction is already in progress |
DeviceDisabledError |
Device has been disabled for transactions |
ErrorLoadingConfig |
Could not load transaction configuration |
VACInternalError |
Internal VAC error |
TransactionApprovedUploadFailed |
Transaction approved but receipt upload failed |
KiC Connector Errors
Thrown when the SDK cannot communicate with the Visa Tap to Pay Ready kernel app:
| Error Type | KiC Code | Description |
|---|---|---|
BindingError |
92 | Failed to bind to the Visa kernel service |
ConnectorSendError |
93 | Sending a message to the kernel failed |
KernelParseError |
94 | Kernel response could not be parsed |
ConnectorParseError |
95 | Connector-side serialization failed |
KernelAppNotInstalled |
96 | Visa kernel app missing — install from Google Play |
PlayProtectOrVerifyAppDisabled |
97 | Google Play Protect must be enabled |
KiC General Errors
| Error Type | KiC Code | Description |
|---|---|---|
NoNetworkOrTimedOut |
10 | Network unavailable or request timed out |
UnsupportedAndroidVersion |
21 | Device OS below Android 12 |
TapToPayReadyAppUpdateRequired |
62 | Visa Tap to Pay Ready app is outdated |
KiC Eligibility Errors
| Error Type | KiC Code | Description |
|---|---|---|
UnsupportedOs |
80 | OS build is unsupported |
HardwareKeystoreNotPresent |
81 | No hardware-backed keystore |
ECEncryptionNotAvailable |
82 | Elliptic-curve crypto unavailable |
AESEncryptionNotAvailable |
83 | AES crypto unavailable |
DESEncryptionNotAvailable |
84 | DES crypto unavailable |
NfcNotAvailable |
85 | NFC hardware missing or disabled |
GooglePlayServicesNotAvailableOrOldVersion |
86 | Google Play Services absent or outdated |
EligibilityCheckFailed |
-1 | Generic eligibility failure |
KiC Initialize Errors
| Error Type | KiC Code | Description |
|---|---|---|
AlreadyEnrolled |
1 | Device already enrolled — no action needed |
DeviceAuthPubKidEmpty |
3 | Missing device-auth public key — re-enroll |
VacDeviceIdEmpty |
4 | VAC device ID not provided |
XRandomValueEmpty |
52 | Random nonce required by enrollment missing |
Failed |
-1 | Generic initialization failure |
KiC Prepare Errors
Pre-transaction secure channel setup failures:
| Error Type | KiC Code | Description |
|---|---|---|
AuthenticationFailed |
7 | VAC authentication failed |
SdkInitNotDone |
11 | init() not completed before use |
SdkEnrollNotDone |
12 | enrollDevice() not completed — re-enroll |
ErrorLoadingConfig |
17 | Could not load config blobs |
AsiError |
18 | Visa auth service interface error |
HardwareKeystoreNotPresent |
20 | Hardware keystore missing during key prep |
AttestationFailed |
24 | Device attestation failed |
DoLoginFailed |
25 | Login exchange with Visa backend failed |
NullLoginAssertion |
26 | Login response missing assertion |
NullLoginCrypto |
27 | Login response missing crypto payload |
NullLoginResponse |
28 | Entire login response was null |
NullLoginResponseBody |
29 | Login HTTP body empty |
NullLoginResponseAuthStatus |
30 | Login response missing auth status |
NullSharedSecret |
31 | Shared secret not derived — re-enroll |
NullSessionKeys |
32 | Session keys missing — re-enroll |
FailedResponseVerification |
33 | MAC/signature mismatch — possible tampering |
FailedMacTagVerification |
34 | MAC tag verification failed |
FailedAuthStatus |
36 | Visa backend rejected authorization |
EmptyAuthStatus |
37 | Auth status element empty |
GetSeedListFailure |
49 | Could not fetch key-rotation seed list |
CertificatePinningError |
50 | TLS pinning failed — possible MITM |
TransactionKeyDerivationFailed |
51 | Could not derive transaction keys — re-enroll |
KeyRotationNeeded |
87 | Kernel requested key rotation (handled automatically) |
KeyRotationNotNeeded |
88 | Key rotation not needed (informational) |
KeyRotationSuccess |
89 | Key rotation completed (informational) |
KeyRotationFailure |
90 | Key rotation failed — re-enroll if transactions fail |
KeyRotationNullResponse |
91 | Kernel did not return rotation status |
Transaction Response Flow
Every KoardTransactionResponse emitted by sdk.sale() or sdk.preauth() includes an action status that tells your app what stage the transaction is in. Use this to drive your UI:
| Action Status | Meaning | What to do |
|---|---|---|
OnProgress |
Transaction is in flight — the reader is active | Update your UI with the current displayMessage and readerStatus |
OnFailure |
A non-recoverable error occurred before completion | Read the statusCode to determine the failure reason and display an appropriate error |
OnComplete |
The transaction has finished — check finalStatus for the outcome |
Route to your receipt, decline, or error screen based on finalStatus |
when (response.actionStatus) {
KoardTransactionActionStatus.OnProgress -> {
showStatus(response.readerStatus.toString(), response.displayMessage)
}
KoardTransactionActionStatus.OnFailure -> {
showError(response.statusCode, response.displayMessage ?: "Transaction failed")
}
KoardTransactionActionStatus.OnComplete -> {
when (response.finalStatus) {
KoardTransactionFinalStatus.Approve -> showReceipt(response.transaction!!)
KoardTransactionFinalStatus.Decline -> showDeclined(response)
KoardTransactionFinalStatus.Abort -> showAborted(response)
KoardTransactionFinalStatus.Failure -> showFailure(response)
}
}
}
Final Transaction Statuses
When actionStatus is OnComplete, the SDK sets finalStatus to one of these values. These are the only terminal outcomes your app needs to handle:
| Final Status | Description | Typical Cause |
|---|---|---|
Approve |
Transaction was authorized by the issuer | Successful payment — display receipt with approval code and transaction details |
Decline |
Transaction was explicitly declined | Issuer denied the authorization, card restricted, insufficient funds, or Strong CVM required (SCA interface switch) |
Abort |
Transaction was terminated before completion | User cancelled, PIN entry cancelled, device security issue, NFC read failure, timeout, or network loss |
Failure |
An internal or system-level error prevented the transaction | SDK/kernel error, device misconfiguration, or unexpected processing failure |
AltService |
Card requested an alternative service | The card network indicated that an alternative acceptance method should be used |
The SDK consolidates the underlying processor response into these statuses so your app does not need to interpret raw processor-level codes. The original acquirer responseCode (ISO 8583 field 39) is still available in the transaction receipt for logging and support purposes.
Acquirer Authorization Statuses
Behind the scenes, the acquirer returns a more granular authStatus in the authorization response. The SDK maps these to the final statuses above, but they are available in the transaction details for advanced use cases:
| Auth Status | Description | Maps to Final Status |
|---|---|---|
Approve |
Issuer approved the transaction | Approve |
Decline |
Issuer declined the transaction (also used for internal acquirer errors) | Decline |
PartialApproval |
Issuer approved a lesser amount than requested | Approve (with reduced authorizedAmount) |
InvalidPIN |
The PIN entered by the cardholder was incorrect | Decline |
UnableToGoOnline |
The terminal could not connect to the acquirer for online authorization | Decline or Abort |
AdditionalInfo |
Acquirer returned supplementary information (e.g., referral) | Varies |
Transaction Response Details
When a transaction completes (regardless of outcome), the KoardTransactionResponse contains the following fields:
| Field | Type | Description |
|---|---|---|
transactionId |
String |
Unique identifier for the transaction |
finalStatus |
KoardTransactionFinalStatus |
Terminal outcome: Approve, Decline, Abort, Failure, or AltService |
actionStatus |
KoardTransactionActionStatus |
Current action phase: OnProgress, OnFailure, or OnComplete |
readerStatus |
KoardReaderStatus |
Reader state: preparing, readyForTap, cardDetected, processing, complete, etc. |
displayMessage |
String? |
Human-readable message from the reader/kernel |
statusCode |
Int? |
Numeric status code from the Visa KiC kernel — see Status Code Reference below |
statusCodeDescription |
String? |
Human-readable description of the status code (auto-generated from the code) |
transaction |
KoardTransaction? |
Full transaction object (populated on completion) |
Status Code Reference
The statusCode field on KoardTransactionResponse is a numeric integer forwarded from the underlying Visa Kernel in the Cloud (KiC) SDK. These codes are passed through on the transaction response for troubleshooting and logging.
These same codes drive the KoardErrorType mapping. When the SDK catches a KiCSdkException with one of these codes, it maps it to the corresponding KoardErrorType documented in the KoardErrorType Reference above. You don't need to handle numeric codes directly — use KoardErrorType pattern matching instead.
For most apps, routing on actionStatus + finalStatus is sufficient. The statusCode is useful for debugging, logging, and handling edge cases like re-enrollment (12) or developer mode (2000).
The status codes fall into several categories based on what layer of the KiC stack generated them:
Connector Status (92–97) — Service Binding Failures
These indicate problems communicating between the Koard SDK and the Visa Tap to Pay Ready app installed on the device.
| Code | Description | What to do |
|---|---|---|
92 |
Failed to bind to the Visa kernel service | Ensure the Visa Tap to Pay Ready app is installed and up to date |
93 |
Sending a message to the kernel service failed | Retry the operation; if persistent, restart both apps |
94 |
Kernel response payload could not be parsed | Update the Visa Tap to Pay Ready app |
95 |
Connector-side serialization/deserialization failed | Update the Koard SDK to the latest version |
96 |
Visa kernel service app missing on device | Install the Visa Tap to Pay Ready app from Google Play |
97 |
Google Play Protect / Verify Apps is disabled | Enable Play Protect in Google Play settings |
General Status (10, 21, 62) — Environment Readiness
| Code | Description | What to do |
|---|---|---|
10 |
Network unavailable or SDK request timed out | Check network connectivity and retry |
21 |
Device OS level not supported by Tap to Pay | Device must run Android 12 (API 31) or later |
62 |
Visa Tap to Pay Ready app is outdated | Update the Tap to Pay Ready app from Google Play |
Eligibility Status (80–86) — Device Capability Checks
Returned when getKiCEligibility() detects a device hardware or software limitation.
| Code | Description | What to do |
|---|---|---|
80 |
OS flavor/build is unsupported | Device uses an incompatible Android build (e.g., custom ROM) |
81 |
Device lacks a hardware-backed keystore | Device does not meet security requirements |
82 |
Elliptic-curve crypto APIs missing or disabled | Device crypto hardware insufficient |
83 |
AES crypto acceleration unavailable | Device crypto hardware insufficient |
84 |
DES crypto unavailable | Device crypto hardware insufficient |
85 |
NFC hardware missing or disabled | Enable NFC in device settings, or device has no NFC |
86 |
Google Play Services absent or out of date | Install or update Google Play Services |
Initialize Status (1, 3, 4, 52) — Enrollment & Bootstrap
| Code | Description | What to do |
|---|---|---|
1 |
Device already enrolled for Tap to Pay | No action needed — the device is already set up |
3 |
Missing device-auth public key identifier | Re-run the enrollment flow |
4 |
Merchant/VAC device ID not provided | Ensure the SDK is configured with a valid merchant profile |
52 |
Random nonce required by enrollment is missing | Re-run the enrollment flow |
Security Status (1000–2003) — Device Integrity
| Code | Description | What to do |
|---|---|---|
1000 |
Emulator detected | Tap to Pay cannot run on emulators — use a physical device |
1001 |
Device rooted or superuser binaries present | Device must not be rooted |
1002 |
Device tamper detection triggered | Device has been modified and is not trusted |
2000 |
Developer options must be disabled | Disable developer mode before running transactions |
2001 |
USB debugging/logging must be disabled | Turn off USB debugging in developer options |
2003 |
Runtime hook/instrumentation detected | Remove any instrumentation frameworks (Frida, Xposed, etc.) |
Prepare Status (7–91) — Pre-Transaction Secure Channel
These codes occur during startUpSdk() or when the SDK prepares for a transaction. They relate to the secure channel between the device and the Visa backend.
| Code | Description | What to do |
|---|---|---|
7 |
VAC authentication call to Visa failed | Check API credentials and network connectivity |
11 |
init() not completed before use |
Complete SDK initialization before starting transactions |
12 |
enrollDevice() not completed |
Device needs enrollment — show the enrollment UI and re-enroll. This also occurs if the Tap to Pay Ready app was reinstalled |
17 |
Could not load enrollment/transaction config blobs | Re-initialize the SDK |
18 |
ASI (Visa auth service interface) returned error | Transient backend issue — retry |
20 |
Hardware keystore missing when preparing keys | Device does not meet security requirements |
24 |
Device attestation failed or invalid | Re-enroll the device; ensure Play Protect is enabled |
25 |
Login exchange with Visa backend failed | Check network; retry |
26 |
Login response missing assertion blob | Transient backend issue — retry |
27 |
Login response missing crypto payload | Transient backend issue — retry |
28 |
Entire login response was null | Transient backend issue — retry |
29 |
Login HTTP body empty | Transient backend issue — retry |
30 |
Login response missing auth status | Transient backend issue — retry |
31 |
Shared secret not derived | Re-enroll the device |
32 |
Session keys missing | Re-enroll the device |
33 |
MAC/signature mismatch in response | Possible tampering — re-enroll the device |
34 |
MAC tag verification failed | Possible tampering — re-enroll the device |
36 |
Visa backend explicitly rejected authorization | Check merchant configuration with Koard support |
37 |
Auth status element empty | Transient backend issue — retry |
49 |
Could not fetch key-rotation seed list | Check network connectivity |
50 |
TLS pinning check failed | Possible man-in-the-middle — check network security |
51 |
Could not derive transaction keys | Re-enroll the device |
Key Rotation Status (87–91)
| Code | Description | What to do |
|---|---|---|
87 |
Kernel requested key rotation | SDK handles this automatically — no action needed |
88 |
Key rotation already satisfied (not needed) | Informational — no action needed |
89 |
Key rotation completed successfully | Informational — no action needed |
90 |
Key rotation failed | Re-enroll the device if transactions fail |
91 |
Kernel did not return key rotation status | Re-enroll the device if transactions fail |
Transaction Status — In-Progress Codes
These codes appear during an active transaction and are reflected in the readerStatus field:
| Code | Reader Status | Description |
|---|---|---|
109 |
readyForTap |
POS state started — reader is waiting for a card tap |
112 |
preparing |
POS state message — reader is preparing for the transaction |
106 |
readCompleted |
Card read completed successfully |
Generic Failure (-1)
A status code of -1 indicates a generic failure. The SDK uses the statusCodeDescription field to provide more context:
| Description Contains | Meaning |
|---|---|
"eligibility" |
Generic eligibility evaluation failure |
"initialise" or "enrol" |
Generic initialization or enrollment failure |
"integrity" or "attestation" |
Device integrity attestation failed |
Status code 12 (enrollment not done) deserves special handling. In the demo app, the SDK treats statusCode == 12 during OnFailure as a recoverable state — it shows a status message rather than an error because re-enrollment can be triggered automatically. If the Tap to Pay Ready app is reinstalled or clears its data, the first transaction attempt will return code 12, and the merchant app should clear its stored enrollment data and re-run the enrollment flow.
Display Message IDs
During the tap-to-pay flow, the SDK emits display messages via displayMessage on each OnProgress event. These correspond to standard EMV message identifiers from the kernel:
| Message | ID | Description |
|---|---|---|
| Approved | 03 |
Authorization obtained — transaction approved |
| Cancel or Enter | 05 |
Prompt to cancel or confirm |
| Card Error | 06 |
Unrecoverable card data error |
| Not Authorized / Declined | 07 |
Transaction was declined by the issuer |
| Please remove card | 10 |
Card not yet removed from the reader field |
| Please try again | 13 |
Recoverable error — retry the tap |
| Welcome | 14 |
Idle state — reader is ready |
| Present card | 15 |
Prompt the cardholder to tap |
| Processing | 16 |
Transaction is being processed |
| Card read OK / Remove card | 17 |
Card was read successfully — may be removed |
| Please insert or swipe card | 18 |
Contactless not supported — try contact/mag-stripe |
| Please present one card only | 19 |
Card collision detected — present only one card |
| Approved. Please Sign | 1A |
Approved; signature required |
| Authorizing. Please Wait | 1B |
Online authorization in progress |
| Insert, swipe, or try another card | 1C |
Contactless failed — use another interface or card |
| Please insert card | 1D |
Chip card should be inserted into the slot |
| (Empty string) | 1E |
Clear the display |
| See Phone for instructions | 20 |
Mobile device CVM required (Touch ID, Face ID, etc.) |
| Present card again | 21 |
Recoverable error — present the card again |
| Practice Mode | 40 |
Successful test/practice transaction |
| Partial Approval | 43 |
Issuer approved a lesser amount |
| Cancelled for Device Security | 46 |
Transaction cancelled due to a device security issue |
| Cancelled | 47 |
Generic transaction cancellation |
| Try another card - No contact interface | 48 |
Card returned GPO error (SW 6984) — transaction aborted |
| Strong CVM | 49 |
SCA issuer response requires interface switch — transaction declined |
Abort and Error Scenarios
The SDK returns an Abort or Failure final status in several well-defined situations. Understanding these helps you build robust error handling:
Transaction Abort Scenarios
| Scenario | What Happens | Message ID |
|---|---|---|
| User cancels PIN entry | User selects "Cancel Transaction" on the PIN keypad | — |
| PIN session timeout | 1 minute of inactivity on the PIN keypad | — |
| PIN keypad interrupted | Another app covers the PIN screen | — |
| Network loss during PIN | Network drops before the PIN event is sent | — |
| Split screen mode | Device enters split screen while on PIN screen — sends CVEntrySecurity cancel |
— |
| Device security issue | Security configuration problem detected | 46 |
| Generic cancellation | User or system cancelled the transaction | 47 |
| Card NFC failure (GPO 6984) | Card cannot complete contactless — abort with MACompletion indicator |
48 |
| Strong CVM / SCA switch | Issuer requires contact interface (not supported) — decline with MACompletion |
49 |
| Developer mode enabled | Developer options are on — reader blocks the transaction | — |
OnFailure Status Codes
When actionStatus is OnFailure, check statusCode for the specific reason:
| Status Code Constant | Description |
|---|---|
TRANSACTION_WINDOW_FOCUS_CHANGED |
The transaction window lost focus (another app came to foreground) |
CAMERA_IS_ACTIVE |
Device camera is active — conflicts with the secure NFC session |
DEVELOPER_MODE_ENABLED |
Developer options are enabled on the device |
NFC_NOT_AVAILABLE |
Device NFC is disabled or unavailable |
DEVICE_NOT_ENROLLED |
Device has not completed enrollment |
SESSION_TIMEOUT |
The transaction session timed out |
Developer Mode: The most common cause of unexpected transaction failures during development. Always disable developer mode before running transactions. Follow the workflow: Enable dev mode → Install app → Disable dev mode → Run transactions.
Completion Indicators
The receipt field emv.tx.tm.CompletionIndicator tells you how the transaction concluded at the kernel level:
| Value | Meaning |
|---|---|
FullCompletion |
Transaction completed normally through the full authorization flow |
MACompletion |
Transaction was terminated by the kernel (Merchant Application completion) — typically an abort or forced decline |
Handling Responses in Practice
The SDK uses two error channels. Here is a complete pattern for handling both:
Channel 1: Transaction Flow (KoardTransactionResponse)
For sale(), preauth(), and tap-based refund() — errors come via the response callback:
private fun handleTransactionEvent(response: KoardTransactionResponse) {
when (response.actionStatus) {
KoardTransactionActionStatus.OnProgress -> {
// Update UI with reader status and display message
updateUI(
status = response.readerStatus.toString(),
message = response.displayMessage ?: "Processing..."
)
}
KoardTransactionActionStatus.OnFailure -> {
// Check for re-enrollment scenario
if (response.statusCode == 12) {
// Tap to Pay Ready app was reinstalled or cleared data
// Clear stored enrollment info and re-run enrollment
triggerReEnrollment()
return
}
// Transaction could not proceed — show the reason
val reason = buildString {
append("Transaction Failed")
response.displayMessage?.let { append("\n\n$it") }
response.statusCodeDescription?.let { append("\n\n$it") }
response.statusCode?.let { append("\n\nStatus Code: $it") }
}
showError(reason)
}
KoardTransactionActionStatus.OnComplete -> {
when (response.finalStatus) {
KoardTransactionFinalStatus.Approve -> {
showReceipt(response.transaction!!)
}
KoardTransactionFinalStatus.Decline -> {
showDeclined(response.displayMessage ?: "Transaction declined")
}
KoardTransactionFinalStatus.Abort -> {
showAborted(response.displayMessage ?: "Transaction aborted")
}
KoardTransactionFinalStatus.Failure -> {
showError(response.displayMessage ?: "Transaction failed")
}
is KoardTransactionFinalStatus.Unknown -> {
showError("Unexpected status: ${response.finalStatus}")
}
}
}
else -> Unit
}
}
Channel 2: API & SDK Operations (KoardException)
For capture(), reverse(), refundTransaction(), tipAdjust(), enrollDevice(), and other non-tap operations — errors are thrown as KoardException:
private suspend fun capturePayment(transactionId: String, amount: Int) {
try {
val result = sdk.capture(transactionId, amount)
showReceipt(result)
} catch (e: KoardException) {
when (val errorType = e.error.errorType) {
// HTTP errors from the Koard API
is KoardErrorType.KoardServiceErrorType.HttpError ->
showError("Server error (HTTP ${errorType.errorCode}): ${e.error.shortMessage}")
is KoardErrorType.KoardServiceErrorType.ConnectionError ->
showError("Network error — check your connection and retry")
is KoardErrorType.KoardServiceErrorType.Unauthorized ->
showError("Session expired — please log in again")
is KoardErrorType.KoardServiceErrorType.NotFound ->
showError("Transaction not found")
is KoardErrorType.KoardServiceErrorType.InvalidRequest ->
showError("Invalid request: ${e.error.shortMessage}")
// Device integrity failures
is KoardErrorType.DeviceIntegrityError.DeveloperModeEnabled ->
showError("Disable developer mode before processing payments")
is KoardErrorType.DeviceIntegrityError ->
showError("Device security check failed: ${e.error.shortMessage}")
// Enrollment issues
is KoardErrorType.VACEnrollmentError ->
promptReEnrollment(e.error.shortMessage)
is KoardErrorType.VACEligibilityError ->
showError("Device not eligible for Tap to Pay: ${e.error.shortMessage}")
// KiC connector/kernel errors
is KoardErrorType.KicConnectorError.KernelAppNotInstalled ->
showError("Install the Visa Tap to Pay Ready app from Google Play")
is KoardErrorType.KicConnectorError ->
showError("Kernel communication error: ${e.error.shortMessage}")
is KoardErrorType.KicPrepareError.SdkEnrollNotDone ->
promptReEnrollment("Device needs re-enrollment")
// Fallback
else -> showError(e.error.shortMessage)
}
}
}
Next Steps
- Review the Running Payments guide for the complete payment flow implementation
- See the Demo App for a working example of response handling in
MainScreenViewModel - Consult the API Response Codes for HTTP-level status codes from the Koard REST API

