> ## Documentation Index
> Fetch the complete documentation index at: https://terminal-docs.xendit.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Android SDK (Kotlin)

> Integrate Xendit's Payment Terminal H2H into your Android applications with our comprehensive Kotlin SDK

# Terminal H2H SDK for Android (Kotlin)

The Xendit's Payment Terminal H2H Android SDK enables you to integrate secure payment processing into your Android applications. Connect to physical payment terminals and process transactions seamlessly using our Kotlin-based SDK.

<Info>
  This SDK works alongside the Terminal API to provide complete in-person payment functionality. You'll use the Terminal API to create payment sessions and this SDK to interact with physical payment devices.
</Info>

## Version information

<AccordionGroup>
  <Accordion title="v1.2.1 — 13-May-2026">
    What's new:

    * **Activity Lifecycle Management**: Introduced `TerminalActivityLifecycleObserver` interface for reusable activity lifecycle tracking, intent queuing, and dispatch logic
    * **Enhanced Provider App Launching**: Refactored Atom and SHC provider app launchers with improved background app launch handling
    * **Launch Failure Notifications**: Added notification mechanism to prompt users when provider app launch fails while app is in background
    * **Callback Interface**: New `TerminalAppLauncherCallback` interface for custom handling of provider app launch failures
    * **Improved Maintainability**: Centralized lifecycle and launcher logic for better extensibility across different application contexts
  </Accordion>

  <Accordion title="v1.2.0 — 8-May-2026">
    What's new:

    * **Core Stability**: Fixed activity tracking bug in TerminalApplication where paused activities were not properly removed from internal tracking list
  </Accordion>

  <Accordion title="v1.1.3 — 26-Apr-2026">
    What's new:

    * Fixed casting issue on C2C request payload to improve compatibility and prevent runtime errors.
  </Accordion>

  <Accordion title="View all versions">
    <Accordion title="v1.1.1 — 23-Apr-2026">
      What's new:

      * Added Cashup provider support for Indonesian terminals
    </Accordion>

    <Accordion title="v1.0.0 — 19-Feb-2026">
      What's new:

      * Renamed class name `TerminalGateway` to `TerminalH2H` for better consistency

      <Warning>
        **Breaking Change**: Class name changed from `TerminalGateway` to `TerminalH2H`. Update your imports and references accordingly.
      </Warning>
    </Accordion>

    <Accordion title="v0.7.0 — 5-Dec-2025">
      What's new:

      * Fixed NTT payment method value maping to ensure correct provider selection
      * Updated BRI payment flow to validate transaction data before executing terminal actions
      * Added timeout configuration for card and QR transactions to auto-cancel and retry stalled requests
      * Added support to handle retry requests via <a href="/api-reference/payments-terminal-api/retry-terminal-payment-session">`/v1/terminal/sessions/{id}/retry`</a> endpoint
    </Accordion>

    <Accordion title="v0.6.0 — 20-Nov-2025">
      What's new:

      * Added support for multiple concurrent device connections, enabling simultaneous transactions across different terminals
      * \[BRI] Fixed status value handling in void and cancel API responses for improved transaction status accuracy
    </Accordion>

    <Accordion title="v0.5.0 — 30-Oct-2025">
      What's new:

      * Introduced command ID handling for settlement operations
      * Enhanced retry logic with configurable attempt counts
      * Improved error handling and logging throughout the gateway service
    </Accordion>

    <Accordion title="v0.4.1 — 7-Oct-2025">
      Bug fixes and improvements:

      * Fix data mapping for terminal responses
      * \[BRI] Enhanced status verification after transaction timeout for improved reliability
    </Accordion>

    <Accordion title="v0.4.0 — 13-May-2025">
      XenTerminal is the companion SDK to In-Person Payment Sessions API. This version provides core functionality for connecting to payment terminals and processing transactions.
    </Accordion>
  </Accordion>
</AccordionGroup>

<Note>
  The SDK follows semantic versioning. Breaking changes will bump the major version number.
</Note>

## Installation

Follow these steps to add the Terminal H2H Android SDK to your project.

<Steps>
  <Step title="Configure Gradle settings">
    Add `mavenCentral()` to your Gradle configuration so Gradle can resolve the SDK artifacts.

    <Tabs>
      <Tab title="Modern Approach (Recommended)">
        Add Maven Central to your Gradle settings file:

        <CodeGroup>
          ```kotlin settings.gradle(.kts) theme={null}
          dependencyResolutionManagement {
            repositories {
              // ... other repositories
              mavenCentral()
            }
          }
          ```

          ```groovy settings.gradle theme={null}
          dependencyResolutionManagement {
            repositories {
              // ... other repositories
              mavenCentral()
            }
          }
          ```
        </CodeGroup>
      </Tab>

      <Tab title="Old Android Project">
        Add Maven Central to your root build.gradle file:

        <CodeGroup>
          ```kotlin build.gradle.kts (root) theme={null}
          allprojects {
            repositories {
              // ... other repositories
              mavenCentral()
            }
          }
          ```

          ```groovy build.gradle (root) theme={null}
          allprojects {
            repositories {
              // ... other repositories
              mavenCentral()
            }
          }
          ```
        </CodeGroup>
      </Tab>
    </Tabs>

    <Check>
      Verify `mavenCentral()` is available in the repository list used by your app module.
    </Check>
  </Step>

  <Step title="Add dependencies">
    Add the required dependencies to your `build.gradle.kts` or `build.gradle` file:

    <CodeGroup>
      ```kotlin build.gradle.kts theme={null}
      dependencies {
        // Terminal H2H SDK
        implementation("co.xendit.terminal:h2h-android:<latest_version>")
        
        // Core Terminal SDK (required)
        implementation("co.xendit.terminal:core-android:<latest_version>")
        
        // Optional: Provider-specific dependencies for helper methods
        // Indonesia - BRI terminals
        implementation("co.xendit.terminal:id-bri-android:<latest_version>")
        
        // Thailand - NTT terminals
        implementation("co.xendit.terminal:th-ntt-android:<latest_version>")
      }
      ```

      ```groovy build.gradle theme={null}
      dependencies {
        // Terminal H2H SDK
        implementation 'co.xendit.terminal:h2h-android:<latest_version>'
        
        // Core Terminal SDK (required)
        implementation 'co.xendit.terminal:core-android:<latest_version>'
        
        // Optional: Provider-specific dependencies for helper methods
        // Indonesia - BRI terminals
        implementation 'co.xendit.terminal:id-bri-android:<latest_version>'
        
        // Thailand - NTT terminals
        implementation 'co.xendit.terminal:th-ntt-android:<latest_version>'
      }
      ```
    </CodeGroup>

    <Note>
      Provider-specific dependencies are optional and enable convenient helper methods for creating `TerminalDevice` instances. Include only the dependencies for providers you plan to use.
    </Note>

    <Check>
      Sync your project to download the dependencies.
    </Check>
  </Step>
</Steps>

## Getting Started

Before you begin, ensure you have the following:

* **Client key**: Request an In-Person Payment `CLIENT_KEY` from the Xendit team
* **Terminal device information**: Obtain the <a href="#finding-terminal-information">Terminal ID</a> and <a href="#finding-terminal-information">IP address</a> for each device
* **Android project**: Set up with minimum SDK version 21 (Android 5.0)

<Warning>
  Keep your client key secure and never commit it to version control. Use environment variables or secure storage for production applications.
</Warning>

### Step 1: Initialize the SDK

Initialize the Terminal H2H SDK in your Application class:

```kotlin MyApp.kt theme={null}
class MyApp : Application() {
  override fun onCreate() {
    super.onCreate()

    TerminalApp.initialize(
      context = this,
      clientKey = TEST_CLIENT_KEY,
      mode = TerminalMode.INTEGRATION
    )
  }
}
```

<Note>
  Use `TerminalMode.INTEGRATION` for development and testing. Switch to `TerminalMode.LIVE` for production with a live client key.
</Note>

### Step 2: Add Terminal Providers

Add the specific terminal providers you need for your integration:

<Note>
  For Share Commerce (SHC), Cashup, and Atom terminals, you do not need to add a Terminal H2H provider. Those devices run terminal logic through the Gateway app installed on the hardware. If the app is missing, install it from the <a href="/sdk/gateway/app-configuration#download-the-app">Gateway download page</a> or contact <a href="mailto:inpersonpayments@xendit.co">[inpersonpayments@xendit.co](mailto:inpersonpayments@xendit.co)</a> for assistance.
</Note>

<Tabs>
  <Tab title="BRI Provider (Indonesia)">
    ```kotlin theme={null}
    // Add BRI provider for Indonesian terminals
    TerminalH2H.addProvider(TerminalBRI)
    ```

    **Required dependency:**

    ```kotlin theme={null}
    implementation("co.xendit.terminal:id-bri-android:<latest_version>")
    ```

    <Info>
      BRI provider supports timeout configuration for card and QR transactions, and provides enhanced transaction retry capabilities.
    </Info>
  </Tab>

  <Tab title="NTT Provider (Thailand)">
    ```kotlin theme={null}
    // Add NTT provider for Thai terminals
    TerminalH2H.addProvider(TerminalNTT)
    ```

    **Required dependency:**

    ```kotlin theme={null}
    implementation("co.xendit.terminal:th-ntt-android:<latest_version>")
    ```

    <Info>
      NTT provider supports multi-function terminals with card payments and QR code transactions.
    </Info>
  </Tab>

  <Tab title="Cashup (Indonesia)">
    ```kotlin theme={null}
    // Add Cashup provider for Indonesian terminals
    TerminalH2H.addProvider(TerminalCashup)
    ```

    **Required dependency:**

    ```kotlin theme={null}
    implementation("co.xendit.terminal:id-cashup-android:<latest_version>")
    ```

    <Note>
      Cashup terminals run terminal logic through the Gateway app on the device. Ensure the Gateway app is installed and your terminal is registered before processing payments.
      Only add the Cashup provider if you plan to install your app directly in the terminal device.
    </Note>
  </Tab>

  <Tab title="Multiple Providers">
    ```kotlin theme={null}
    // Add multiple providers for supporting different terminal types
    TerminalH2H.addProvider(TerminalBRI)
    TerminalH2H.addProvider(TerminalNTT)
    ```

    **Required dependencies:**

    ```kotlin theme={null}
    implementation("co.xendit.terminal:id-bri-android:<latest_version>")
    implementation("co.xendit.terminal:th-ntt-android:<latest_version>")
    ```

    <Warning>
      Only add providers for terminal types you actually plan to use. Each provider adds to your app's size and initialization time.
    </Warning>
  </Tab>
</Tabs>

<Check>
  After adding providers, they will be available for use when registering terminal devices in the next step.
</Check>

### Step 3: Register Terminal Devices

Create `TerminalDevice` entries for each terminal and register them with the gateway:

```kotlin theme={null}
val devices = listOf(
  TerminalDevice(id = "BRI12345", ipAddress = "192.168.1.10", active = true),
  TerminalDevice(id = "BRI67890", ipAddress = "192.168.1.11", active = true)
)

TerminalH2H.registerDevices(
  devices = devices,
  restart = true // restart to apply the refreshed device list immediately
)
```

<Tip>
  For steps to locate each terminal's <a href="#finding-terminal-information">Terminal ID</a> and <a href="#finding-terminal-information">IP address</a>, review the [Finding Terminal Information guide](#finding-terminal-information).
</Tip>

<Info>
  Set <code>active = true</code> for terminals that should be available immediately. You can keep an entry with <code>active = false</code> to stage devices without establishing a connection.
</Info>

<Check>
  After successful registration, your terminal devices will be available for payment processing.
</Check>

### Step 4: Calling Terminal API

With Terminal H2H SDK configured, you can now process payments using the Terminal API. The SDK handles communication with physical terminals through the local gateway service.

```bash theme={null}
# Example: Create a payment session
curl -X POST 'https://terminal-dev.xendit.co/v1/terminal/sessions' \
  -u 'API_KEY:' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-key: unique-session-123' \
  -d '{
    "terminal_id": "TERMINAL_ID",
    "amount": 10000,
    "currency": "IDR",
    "external_id": "payment-123",
    "payment_method": "card"
  }'
```

<Info>
  For complete API documentation including request/response formats, error handling, and advanced features, see the [Terminal API (H2H)](/api-reference/terminal-api/introduction) documentation.
</Info>

<Tip>
  The Terminal H2H SDK automatically manages the connection to your registered terminal devices and handles communication protocols for different providers.
</Tip>

<Check>
  You're now ready to process payments using the Terminal H2H SDK integrated with Terminal API.
</Check>

## Configuring App for Terminal Device

<Tabs>
  <Tab title="Cashup Indonesia">
    When running your application on a Terminal device, additional configuration is required to enable the terminal to communicate with the Cashup payment system. Follow these steps:

    <Steps>
      <Step title="Configure AndroidManifest.xml">
        Add the Cashup result activity to your `AndroidManifest.xml` file:

        ```xml AndroidManifest.xml theme={null}
        <manifest xmlns:android="http://schemas.android.com/apk/res/android">
          <queries>
            <intent>
              <action android:name="android.intent.action.VIEW" />
              <data android:scheme="cashlez" />
            </intent>
            <package android:name="com.cz.app" />
          </queries>
          
          <application>
            <!-- Your other activities -->
            
            <!-- Cashup Result Activity -->
            <activity
              android:exported="true"
              android:name="co.xendit.terminal.cashup.activity.ResultActivity" />
          </application>
        </manifest>
        ```

        <Warning>
          The `android:exported="true"` attribute is required to allow the Cashup system to return results to your application.
        </Warning>
      </Step>

      <Step title="Configure Application Class">
        Choose one of the following approaches to configure your Application class:

        <Tabs>
          <Tab title="Extend TerminalApplication">
            Make your `Application` class extend `TerminalApplication` and configure the Cashup provider launcher:

            ```kotlin Application.kt theme={null}
            import android.app.Application
            import co.xendit.terminal.core.application.TerminalApplication
            import co.xendit.terminal.cashup.TerminalCashup
            import co.xendit.terminal.cashup.launcher.CashupAppLauncher

            class MyApplication : TerminalApplication() {
              override fun onCreate() {
                super.onCreate()
                
                // Configure Cashup provider app launcher
                TerminalCashup.providerAppLauncher = CashupAppLauncher(this)
              }
            }
            ```

            <Info>
              Don't forget to register your custom `Application` class in the `AndroidManifest.xml`:

              ```xml AndroidManifest.xml theme={null}
              <application
                android:name=".MyApplication"
                ...>
              </application>
              ```
            </Info>
          </Tab>

          <Tab title="Use TerminalAppLauncherCallback">
            Create a custom `Application` class and register the `TerminalAppLauncherCallback`:

            ```kotlin Application.kt theme={null}
            import android.app.Application
            import co.xendit.terminal.core.application.TerminalAppLauncherCallback
            import co.xendit.terminal.cashup.TerminalCashup
            import co.xendit.terminal.cashup.launcher.CashupAppLauncher

            class MyApplication : Application() {
              override fun onCreate() {
                super.onCreate()
                
                // Register the callback
                TerminalAppLauncherCallback.register(this)
                
                // Configure Cashup provider app launcher
                TerminalCashup.providerAppLauncher = CashupAppLauncher(this)
              }
            }
            ```

            <Info>
              Don't forget to register your custom `Application` class in the `AndroidManifest.xml`:

              ```xml AndroidManifest.xml theme={null}
              <application
                android:name=".MyApplication"
                ...>
              </application>
              ```
            </Info>
          </Tab>
        </Tabs>

        <Check>
          Your app is now configured to run on Cashup Terminal devices and can communicate with the Cashup payment system.
        </Check>
      </Step>

      <Step title="Configure Receipt Printing">
        Control whether the terminal prints a receipt after a transaction:

        ```kotlin theme={null}
        import co.xendit.terminal.cashup.utils.setCashupPrintReceiptEnabled

        // Enable receipt printing
        TerminalH2H.setCashupPrintReceiptEnabled(true)

        // Disable receipt printing
        TerminalH2H.setCashupPrintReceiptEnabled(false)
        ```

        <Info>
          This setting allows you to control the default receipt printing behavior for Cashup terminals.
        </Info>
      </Step>
    </Steps>
  </Tab>

  <Tab title="SHC Malaysia">
    When running your application on a Terminal device, additional configuration is required to enable the terminal to communicate with the SHC payment system. Follow these steps:

    <Steps>
      <Step title="Configure AndroidManifest.xml">
        Add the SHC result activity to your `AndroidManifest.xml` file:

        ```xml AndroidManifest.xml theme={null}
        <manifest xmlns:android="http://schemas.android.com/apk/res/android">
          <application>
            <!-- Your other activities -->
            
            <!-- SHC Result Activity -->
            <activity
              android:exported="true"
              android:name="co.xendit.terminal.shc.activity.ResultActivity" />
          </application>
        </manifest>
        ```

        <Warning>
          The `android:exported="true"` attribute is required to allow the SHC system to return results to your application.
        </Warning>
      </Step>

      <Step title="Configure Application Class">
        Choose one of the following approaches to configure your Application class:

        <Tabs>
          <Tab title="Extend TerminalApplication">
            Make your `Application` class extend `TerminalApplication` and configure the SHC provider launcher:

            ```kotlin Application.kt theme={null}
            import android.app.Application
            import co.xendit.terminal.core.application.TerminalApplication
            import co.xendit.terminal.shc.TerminalSHC
            import co.xendit.terminal.shc.launcher.SHCAppLauncher

            class MyApplication : TerminalApplication() {
              override fun onCreate() {
                super.onCreate()
                
                // Configure SHC provider app launcher
                TerminalSHC.providerAppLauncher = SHCAppLauncher(this)
              }
            }
            ```

            <Info>
              Don't forget to register your custom `Application` class in the `AndroidManifest.xml`:

              ```xml AndroidManifest.xml theme={null}
              <application
                android:name=".MyApplication"
                ...>
              </application>
              ```
            </Info>
          </Tab>

          <Tab title="Use TerminalAppLauncherCallback">
            Create a custom `Application` class and register the `TerminalAppLauncherCallback`:

            ```kotlin Application.kt theme={null}
            import android.app.Application
            import co.xendit.terminal.core.application.TerminalAppLauncherCallback
            import co.xendit.terminal.shc.TerminalSHC
            import co.xendit.terminal.shc.launcher.SHCAppLauncher

            class MyApplication : Application() {
              override fun onCreate() {
                super.onCreate()
                
                // Register the callback
                TerminalAppLauncherCallback.register(this)
                
                // Configure SHC provider app launcher
                TerminalSHC.providerAppLauncher = SHCAppLauncher(this)
              }
            }
            ```

            <Info>
              Don't forget to register your custom `Application` class in the `AndroidManifest.xml`:

              ```xml AndroidManifest.xml theme={null}
              <application
                android:name=".MyApplication"
                ...>
              </application>
              ```
            </Info>
          </Tab>
        </Tabs>

        <Check>
          Your app is now configured to run on SHC Terminal devices and can communicate with the SHC payment system.
        </Check>
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Configuration and Management

### Set Operation Timeout

Configure the timeout for terminal operations:

```kotlin theme={null}
// Set timeout to 5 minutes (default)
TerminalH2H.setOperationTimeout(minutes = 5)

// Set custom timeout for longer operations
TerminalH2H.setOperationTimeout(minutes = 10)

// Set provider-specific timeouts
TerminalH2H.setOperationTimeout(type = TimeoutType.CARD, minutes = 10)
TerminalH2H.setOperationTimeout(type = TimeoutType.QR, minutes = 10)
```

<Note>
  The default timeout is 5 minutes. Use the type-specific methods to override card vs QR timeouts when needed.
</Note>

<Warning>
  This timeout configuration is only available for the BRI provider. Other terminal providers use their own internal timeout settings.
</Warning>

### Restart Terminal Connection

Manage and restart terminal service connections:

<Tabs>
  <Tab title="Restart All Connections">
    Call the method without parameters to restart all connections and tasks:

    ```kotlin theme={null}
    // This restarts all connections
    TerminalH2H.restart()
    ```
  </Tab>

  <Tab title="Restart Single Connection">
    Provide the <a href="#finding-terminal-information">terminal ID</a> to restart a specific terminal connection:

    ```kotlin theme={null}
    // This restarts the connection to a specific Terminal
    TerminalH2H.restart(terminalID = TERMINAL_ID)
    ```
  </Tab>
</Tabs>

### Monitor Connection State

Observe terminal connection states in real-time:

<Tabs>
  <Tab title="Using Coroutine Scope">
    Use the `observeConnection` method with a coroutine scope:

    ```kotlin theme={null}
    TerminalH2H.observeConnection(
      CoroutineScope(Dispatchers.IO),
      terminalDevice
    ) { state ->
      when (state) {
        ConnectionState.CONNECTED -> {
          // Terminal is ready for transactions
          Log.i("Terminal", "Connected to ${terminalDevice.id}")
        }
        ConnectionState.DISCONNECTED -> {
          // Terminal is offline
          Log.w("Terminal", "Disconnected from ${terminalDevice.id}")
        }
        ConnectionState.CONNECTING_FAILED -> {
          // Connection attempt failed
          Log.e("Terminal", "Failed to connect to ${terminalDevice.id}")
        }
        ConnectionState.UNREACHABLE -> {
          // Gateway cannot reach the device; SDK keeps retrying
          Log.w("Terminal", "Device unreachable ${terminalDevice.id}")
        }
        // ... handle other states
      }
    }
    ```
  </Tab>

  <Tab title="Using Flow">
    Use the connection state Flow directly:

    ```kotlin theme={null}
    val connectionStateFlow = terminalDevice.connectionState
    connectionStateFlow.collect { state ->
      // Handle connection state changes
      when (state) {
        ConnectionState.CONNECTED -> {
          // Terminal is ready for transactions
          Log.i("Terminal", "Connected to ${terminalDevice.id}")
        }
        ConnectionState.DISCONNECTED -> {
          // Terminal is offline
          Log.w("Terminal", "Disconnected from ${terminalDevice.id}")
        }
        ConnectionState.UNREACHABLE -> {
          // Gateway temporarily marks the device unreachable
          Log.w("Terminal", "Unreachable device ${terminalDevice.id}")
        }
        // ... handle other states
      }
    }
    ```
  </Tab>
</Tabs>

### Connection States

| State              | Description                                                          |
| ------------------ | -------------------------------------------------------------------- |
| CONNECTED          | Device successfully connected to terminal and ready for transactions |
| DISCONNECTED       | Device disconnected from terminal or network unavailable             |
| UNKNOWN            | Initial state before any connection attempt                          |
| CONNECTING         | Currently establishing connection to terminal                        |
| CONNECTING\_FAILED | Connection attempt failed due to network or authentication issues    |
| UNREACHABLE        | Gateway ping failed; SDK retries until the terminal responds         |
| UNSUPPORTED        | Terminal type or connection method not supported                     |

### Monitor Error States

Observe and handle errors across all terminal devices:

```kotlin theme={null}
// Global error monitoring
TerminalH2H.observeError(
  CoroutineScope(Dispatchers.IO)
) { device, error ->
  Log.e("TerminalH2H", "Error on ${device?.id}: ${error?.message}")
  
  when (error.code) {
    ErrorCode.TERMINAL_BUSY -> {
      // Terminal is processing another transaction
      // Wait and retry later
    }
    ErrorCode.FAILED_TO_CONNECT -> {
      // Network connectivity issue
      // Check network connection and terminal <a href="#finding-terminal-information">IP</a>
    }
    ErrorCode.AUTHENTICATION_FAILED -> {
      // Invalid credentials or <a href="#finding-terminal-information">terminal ID</a>
      // Verify client key and terminal configuration
    }
    // ... handle other error codes
  }
}

// Device-specific error monitoring
val errorFlow = TerminalH2H.errorState(terminalDevice)
val deviceErrorFlow = terminalDevice.error
```

## Error Handling

### Error Data Structure

All errors returned by the Terminal H2H SDK follow this structure:

```kotlin theme={null}
data class TerminalError(
  val code: ErrorCode,
  val message: String
)
```

### Error Codes Reference

| Error Code              | HTTP Status | Description                                                                                            |
| ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------ |
| INVALID\_CREDENTIAL     | 401         | Provided credentials are invalid or expired                                                            |
| INVALID\_REQUEST        | 400         | Request format or parameters are invalid                                                               |
| INTERNAL\_SERVER\_ERROR | 500         | Server-side error occurred during processing                                                           |
| UNKNOWN\_ERROR          | 0           | Unexpected error that doesn't fit other categories                                                     |
| KEY\_INVALID            | -1          | API key is invalid or not authorized                                                                   |
| SEND\_FAILED            | -2          | Failed to send data to the terminal device                                                             |
| NOT\_CONNECTED          | -3          | No active connection to any terminal device                                                            |
| FAILED\_TO\_CONNECT     | -4          | Unable to establish connection to terminal                                                             |
| UNSUPPORTED             | -5          | Operation or platform not supported                                                                    |
| TERMINAL\_BUSY          | -6          | Terminal is processing another transaction                                                             |
| ENCRYPTION\_FAILED      | -7          | Encryption key invalid or encryption process failed                                                    |
| AUTHENTICATION\_FAILED  | -21         | Terminal authentication failed - check <a href="#finding-terminal-information">TID</a> and credentials |
| DECLINED\_BY\_READER    | N/A         | Card reader rejected the transaction; prompt the customer to retry                                     |
| PAYMENT\_UNPAID         | N/A         | Payment is still pending on the terminal; confirm status before retrying                               |
| DUPLICATED              | N/A         | Duplicate command detected; generate a new order ID before retrying                                    |
| CANCELLED               | N/A         | Action cancelled on the terminal or by the operator                                                    |
| TIMEOUT                 | N/A         | Operation exceeded the configured timeout window                                                       |
| NO\_DATA\_FOUND         | N/A         | Terminal cannot find the referenced transaction data                                                   |
| TID\_NOT\_SET           | N/A         | Terminal ID is missing in the gateway configuration                                                    |
| TID\_NOT\_MATCH         | N/A         | Terminal ID does not match the registered device                                                       |

### Error Handling Best Practices

Implement comprehensive error handling for robust payment processing:

```kotlin theme={null}
fun handleTerminalError(error: TerminalError?) {
  when (error?.code) {
    ErrorCode.AUTHENTICATION_FAILED -> {
      // Verify API key and <a href="#finding-terminal-information">terminal ID</a>
      Log.e("TerminalH2H", "Authentication failed: ${error.message}")
      // Show user-friendly message and retry with correct credentials
    }
    
    ErrorCode.TERMINAL_BUSY -> {
      // Terminal is processing another transaction
      Log.w("TerminalH2H", "Terminal busy: ${error.message}")
      // Wait and retry after a delay
      retryAfterDelay(5000) // 5 seconds
    }
    
    ErrorCode.FAILED_TO_CONNECT, ErrorCode.NOT_CONNECTED -> {
      // Network connectivity issues
      Log.e("TerminalH2H", "Connection failed: ${error.message}")
      // Check network connection and terminal <a href="#finding-terminal-information">IP address</a>
      checkNetworkConnectivity()
    }
    
    ErrorCode.KEY_INVALID, ErrorCode.INVALID_CREDENTIAL -> {
      // Credential issues
      Log.e("TerminalH2H", "Invalid credentials: ${error.message}")
      // Rotate or refresh API credentials
      refreshCredentials()
    }
    
    ErrorCode.ENCRYPTION_FAILED -> {
      // Encryption key issues
      Log.e("TerminalH2H", "Encryption failed: ${error.message}")
      // Re-initialize SDK with correct encryption keys
      reinitializeSDK()
    }

    ErrorCode.TIMEOUT -> {
      // Operation exceeded configured timeout window
      Log.w("TerminalH2H", "Operation timed out: ${error.message}")
      // Retry using exponential backoff or restart the device connection
      retryAfterDelay(5000)
    }

    ErrorCode.CANCELLED -> {
      // Action cancelled on terminal or by operator
      Log.i("TerminalH2H", "Command cancelled: ${error.message}")
      // Surface a friendly message to the cashier and re-initiate if needed
    }

    ErrorCode.DECLINED_BY_READER -> {
      // Card or reader rejected the transaction
      Log.w("TerminalH2H", "Reader declined: ${error.message}")
      // Prompt customer to retry or use a different payment method
    }
    
    else -> {
      // Log and escalate unknown errors
      Log.e("TerminalH2H", "Unknown error: ${error?.message}")
      // Report to monitoring system
      reportError(error)
    }
  }
}
```

<Tip>
  Always implement retry logic for transient errors like `TERMINAL_BUSY` and `FAILED_TO_CONNECT`. Use exponential backoff to avoid overwhelming the terminal.
</Tip>

## Troubleshooting

<Tip>
  For more recovery scenarios across H2H and C2C terminal flows, see the [SDK Troubleshooting Guide](/sdk/troubleshooting).
</Tip>

### Common Issues and Solutions

<AccordionGroup>
  <Accordion title="Unresponsive EDC Machine">
    **Problem**: The EDC (Electronic Data Capture) machine becomes unresponsive or stops processing transactions.

    **Solution**:

    1. Restart the EDC machine by holding the power button and selecting "Restart"
    2. Wait for the device to fully boot up and reconnect
    3. Verify the terminal is back online using the connection monitoring features

    <Warning>
      Always ensure the EDC is properly restarted before attempting new transactions to avoid data corruption.
    </Warning>
  </Accordion>

  <Accordion title="Kiosk Mode Configuration">
    **Problem**: Unexpected behaviors or unauthorized access to terminal functions.

    **Solution**:

    1. Ensure all transactions are initiated only through the SDK
    2. Contact the Xendit EDC team to enable POS-only mode for your <a href="#finding-terminal-information">Terminal IDs</a>
    3. Configure terminal settings to disable manual transaction entry

    <Tip>
      POS-only mode prevents manual transaction entry and ensures all operations go through your application.
    </Tip>
  </Accordion>

  <Accordion title="No Response After Payment Completion">
    **Problem**: EDC fails to send transaction results to the SDK after payment completion.

    **Solution**:

    1. Query the Payment Session using the Terminal API to retrieve the latest status
    2. Check network connectivity between the EDC and your application
    3. Verify the callback URL configuration in your payment session
    4. Implement retry logic for failed status updates

    ```kotlin theme={null}
    // Example: Query payment session status
    val paymentSession = terminalApi.getPaymentSession(sessionId)
    when (paymentSession.status) {
      PaymentStatus.COMPLETED -> {
        // Process successful payment
      }
      PaymentStatus.FAILED -> {
        // Handle failed payment
      }
      PaymentStatus.PENDING -> {
        // Payment still in progress
      }
    }
    ```
  </Accordion>

  <Accordion title="Transaction Stuck or Missing Callback After Receipt Printed">
    **Problem**: Transaction appears stuck or you don't receive a callback after the terminal prints a receipt, indicating the transaction may be incomplete.

    <Note>
      This troubleshooting section applies only to BRI terminals.
    </Note>

    **Solution**:
    Call the Terminal API retry endpoint to request the app check the transaction status and redo the transaction if it's incomplete:

    ```kotlin theme={null}
    // Retry transaction via Terminal API
    val response = terminalApi.retrySession(sessionId)
    // The app will check transaction status and redo if incomplete
    ```

    <Info>
      The retry endpoint <a href="/api-reference/payments-terminal-api/retry-terminal-payment-session">`/v1/terminal/sessions/{id}/retry`</a> instructs the app to verify the current transaction status with the terminal and automatically redo the transaction if it's found to be incomplete.
    </Info>

    <Tip>
      Use this retry mechanism when you see a receipt printed but haven't received a callback, as it may indicate the transaction didn't complete successfully on the terminal side.
    </Tip>
  </Accordion>

  <Accordion title="Connection Issues">
    **Problem**: Unable to establish or maintain connection with terminal devices.

    **Solutions**:

    * **Check network connectivity**: Ensure both devices are on the same network
    * **Verify IP addresses**: Confirm terminal <a href="#finding-terminal-information">IP addresses</a> are correct and accessible
    * **Firewall settings**: Check if firewall is blocking the connection ports
    * **Terminal status**: Ensure the terminal is powered on and in ready state
    * **SDK initialization**: Verify client key and terminal configuration

    <Note>
      Use the connection monitoring features to diagnose specific connection issues.
    </Note>
  </Accordion>
</AccordionGroup>

## Finding Terminal Information

<Tabs>
  <Tab title="BRI Terminals">
    <Steps>
      <Step title="Find Terminal ID (TID)">
        Open the BRI FMS app on the terminal device and locate the Terminal ID in the device information section.

        <Frame>
          <img src="https://mintcdn.com/xendit/79NehsWB8-Vpp9n7/images/bri/bri-tid.png?fit=max&auto=format&n=79NehsWB8-Vpp9n7&q=85&s=60410d91d34e864135c485e6df2901a6" alt="BRI terminal showing Terminal ID in FMS app" width="450" height="885" data-path="images/bri/bri-tid.png" />
        </Frame>
      </Step>

      <Step title="Find IP Address">
        Open the ECRLink app on the terminal and check the network settings for the IP address.

        <Frame>
          <img src="https://mintcdn.com/xendit/79NehsWB8-Vpp9n7/images/bri/bri-ip.png?fit=max&auto=format&n=79NehsWB8-Vpp9n7&q=85&s=34eeb360fc42d6c7ab5c633ba7a05371" alt="BRI terminal showing IP address in ECRLink app" width="480" height="878" data-path="images/bri/bri-ip.png" />
        </Frame>
      </Step>
    </Steps>
  </Tab>

  <Tab title="NTT Terminals">
    <Steps>
      <Step title="Find Terminal ID (TID)">
        Look for the Terminal ID on the physical sticker attached to the device.

        <Frame>
          <img src="https://mintcdn.com/xendit/79NehsWB8-Vpp9n7/images/ghl/ghl-tid.jpg?fit=max&auto=format&n=79NehsWB8-Vpp9n7&q=85&s=88d3ff4da303a2d35eecd432ed4fa9c4" alt="NTT terminal showing Terminal ID sticker on device" width="3024" height="4032" data-path="images/ghl/ghl-tid.jpg" />
        </Frame>
      </Step>

      <Step title="Find IP Address">
        Open the NTT LinkPOS app on the terminal and navigate to network settings to find the IP address.

        <Frame>
          <img src="https://mintcdn.com/xendit/79NehsWB8-Vpp9n7/images/ghl/ghl-ip.jpg?fit=max&auto=format&n=79NehsWB8-Vpp9n7&q=85&s=e05ef6b97d190aa59206ef5842d9a4a8" alt="NTT terminal showing IP address in LinkPOS app" width="3024" height="4032" data-path="images/ghl/ghl-ip.jpg" />
        </Frame>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Cashup Terminals">
    <Steps>
      <Step title="Find Terminal ID (TID)">
        Open the Cashlez app and navigate to the User Account tab to locate the Terminal ID.

        <Frame>
          <img src="https://mintcdn.com/xendit/iTqGX9_7zwXBWkal/images/cashup/cashup-tid.png?fit=max&auto=format&n=iTqGX9_7zwXBWkal&q=85&s=d75858971855c6badc35155cf5d417b7" alt="Cashup terminal showing Terminal ID in User Account tab" width="720" height="1600" data-path="images/cashup/cashup-tid.png" />
        </Frame>
      </Step>

      <Step title="Find IP Address">
        Open the Terminal Gateway app and check the device list for the terminal IP address.

        <Frame>
          <img src="https://mintcdn.com/xendit/iTqGX9_7zwXBWkal/images/app/gateway-app-ip-address.png?fit=max&auto=format&n=iTqGX9_7zwXBWkal&q=85&s=06041514d4322321d4522600a8cdd921" alt="Terminal Gateway app showing device IP address" width="717" height="535" data-path="images/app/gateway-app-ip-address.png" />
        </Frame>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Share Commerce Terminals">
    <Steps>
      <Step title="Find Terminal ID (TID)">
        Open the Xendit app on the device and review the terminal details screen to locate the Terminal ID.

        <Frame>
          <img src="https://mintcdn.com/xendit/iTqGX9_7zwXBWkal/images/shc/shc-tid.png?fit=max&auto=format&n=iTqGX9_7zwXBWkal&q=85&s=1944ce5df286baabbab4b40b561f6bac" alt="Share Commerce terminal showing Terminal ID in Xendit app" width="720" height="1280" data-path="images/shc/shc-tid.png" />
        </Frame>
      </Step>

      <Step title="Find IP Address">
        Open the Terminal Gateway app and check the device list for the terminal IP address.

        <Frame>
          <img src="https://mintcdn.com/xendit/iTqGX9_7zwXBWkal/images/app/gateway-app-ip-address.png?fit=max&auto=format&n=iTqGX9_7zwXBWkal&q=85&s=06041514d4322321d4522600a8cdd921" alt="Terminal Gateway app showing device IP address" width="717" height="535" data-path="images/app/gateway-app-ip-address.png" />
        </Frame>
      </Step>
    </Steps>
  </Tab>

  <Tab title="Atom Terminals">
    <Steps>
      <Step title="Find Terminal ID (TID)">
        Open the payment app on the Atom terminal, then go to <strong>Settings</strong> → <strong>Device Info</strong> to view the Terminal ID.

        <Frame>
          <img src="https://mintcdn.com/xendit/iTqGX9_7zwXBWkal/images/atom/atom-tid.png?fit=max&auto=format&n=iTqGX9_7zwXBWkal&q=85&s=1e1df9098f060068525d853b569a714b" alt="Atom terminal showing Terminal ID in device settings" width="720" height="1280" data-path="images/atom/atom-tid.png" />
        </Frame>
      </Step>

      <Step title="Find IP Address">
        Open the Terminal Gateway app and check the device list for the terminal IP address.

        <Frame>
          <img src="https://mintcdn.com/xendit/iTqGX9_7zwXBWkal/images/app/gateway-app-ip-address.png?fit=max&auto=format&n=iTqGX9_7zwXBWkal&q=85&s=06041514d4322321d4522600a8cdd921" alt="Terminal Gateway app showing device IP address" width="717" height="535" data-path="images/app/gateway-app-ip-address.png" />
        </Frame>
      </Step>
    </Steps>
  </Tab>
</Tabs>

<Note>
  Screenshots and UI layouts may vary by firmware or app version. Refer to the latest vendor documentation if the interface differs from these instructions.
</Note>
