Skip to main content

Terminal Gateway SDK for iOS (Swift)

The Terminal Gateway SDK for iOS enables you to integrate secure payment processing directly into your iOS applications. Connect to physical payment terminals and process transactions seamlessly using our comprehensive Swift SDK.
This SDK works alongside the Terminal API to provide a complete in-person payment solution. You’ll use the Terminal API to create payment sessions and the Terminal Gateway SDK to interact with physical payment devices.

Version information

What’s new:
  • Fixed NTT payment method value mapping 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 retry logic via Terminal API for more resilient recovery after timeouts
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
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
Bug fixes and improvements:
  • Fix data mapping for terminal responses
  • [BRI] Enhanced status verification after transaction timeout for improved reliability
XenTerminal is the companion SDK to In-Person Payment Sessions API. This version provides core functionality for connecting to payment terminals and processing transactions.
The SDK follows semantic versioning. Breaking changes will bump the major version number.

Download

Download the Terminal Gateway iOS SDK:

Installation

Install the Terminal Gateway SDK by adding it as a framework in Xcode:
1

Add Sentry dependency

Add the Sentry pod to your Podfile:
Podfile
target 'YourApp' do
  pod 'Sentry', :git => 'https://github.com/getsentry/sentry-cocoa.git', :tag => '8.45.0'
end
Run pod install to install the dependency.
2

Extract framework

Extract the TerminalGateway.xcframework zip file in your project directory.
Extracting TerminalGateway.xcframework zip file in Finder
3

Add framework to project

In Xcode, select your target and navigate to the “General” tab.Find “Frameworks, Libraries, and Embedded Content” and click the ”+” button.
Selecting target and navigating to General tab in Xcode
Finding Frameworks, Libraries, and Embedded Content section in Xcode
4

Import framework

Click “Add Other…” and select “Add Files…”Search for TerminalGateway.xcframework, select it, and click “Open”.
Importing TerminalGateway.xcframework in Xcode using Add Files dialog
5

Configure build settings

Go to “Build Settings” and make the following changes:
  • Search for “ENABLE_USER_SCRIPT_SANDBOXING” and set it to “NO”
  • Search for “Other Linker Flags” and add “-lsqlite3”
Setting ENABLE_USER_SCRIPT_SANDBOXING to NO in Xcode Build Settings
Adding -lsqlite3 to Other Linker Flags in Xcode Build Settings

Getting Started

Before starting, you’ll need an In-Person Payment CLIENT_KEY from the Xendit In-Person Payment team. You will also need the terminal IP address.
Terminal Gateway requires physical payment devices to function. Contact the Xendit team to obtain compatible terminal devices and configuration details.

Step 1: Initialize SDK

Call the XenTerminal.initialize() method within the Application class of your app:
AppDelegate.swift
TerminalApp.shared.initialize(
   clientKey: TEST_CLIENT_KEY,
   mode: TerminalMode.integration
)

TerminalGateway.shared.addProvider(provider: TerminalBRI.shared)
// For NTT
// TerminalGateway.shared.addProvider(provider: TerminalNTT.shared)
The SDK supports two modes:
  • Integration Mode
  • Live Mode
Use TerminalMode.integration for development and testing with physical terminals. Transactions are sent to Test Mode on the Xendit Dashboard.

Step 2: Register Terminal Device

Register multiple devices by calling the registerDevices method:
TerminalGateway.shared.registerDevices(devices: [ 
  TerminalDevice(id: Terminal_ID_1, ipAddress: Terminal_IP_1),    
  TerminalDevice(id: Terminal_ID_2, ipAddress: Terminal_IP_2), 
  TerminalDevice(id: Terminal_ID_3, ipAddress: Terminal_IP_3) 
  // Add more TerminalDevice instances as needed 
])
For detailed instructions on finding Terminal ID and IP address for different terminal providers, see our Finding Terminal Information guide.

Optional Methods

Set Operation Timeout

Configure the maximum allowed time for operations to complete before the service cancels and retries the transaction:
TerminalGateway.shared.setOperationTimeout(minutes: 5) // default value is 5 minutes

// Set provider-specific timeouts
TerminalGateway.shared.setOperationTimeout(type: TimeoutType.card, minutes: 10)
TerminalGateway.shared.setOperationTimeout(type: TimeoutType.qr, minutes: 10)

Restart Terminal Connection

Manage and restart terminal service connections:
  • Restart All Connections
  • Restart Single Connection
Call the method without parameters to restart all connections and tasks:
// This restarts all connections
TerminalGateway.shared.restart(terminalID: nil)

Observing Connection State

Monitor Terminal connection status using the observeEDCConnection method:
TerminalGateway.shared
  .observeConnection(device: terminalDevice) { state in     
   // Handle connection state changes
}
The SDK handles the following connection states:
CodeDescription
connectedDevice connected to Terminal
disconnectedDevice disconnected to Terminal
unknownWhen no connection has been established yet
connectingDevice requesting connection to Terminal
connectingFailedDevice unable to connect with Terminal
unsupportedConnection not supported

Observing Error State

Monitor Terminal error states using the observeError method:
TerminalGateway.shared.observeError(onResult: { device, error in
    // Handle error state events
})

Error Handling

Error Data Structure

All errors returned by the Terminal Gateway SDK follow this structure:
struct TerminalError {
    let code: ErrorCode
    let message: String
}

Error Codes Reference

Error CodeHTTP StatusDescription
INVALID_CREDENTIAL401Provided credentials are invalid or expired
INVALID_REQUEST400Request format or parameters are invalid
INTERNAL_SERVER_ERROR500Server-side error occurred during processing
UNKNOWN_ERROR0Unexpected error that doesn’t fit other categories
KEY_INVALID-1API key is invalid or not authorized
SEND_FAILED-2Failed to send data to the terminal device
NOT_CONNECTED-3No active connection to any terminal device
FAILED_TO_CONNECT-4Unable to establish connection to terminal
UNSUPPORTED-5Operation or platform not supported
TERMINAL_BUSY-6Terminal is processing another transaction
ENCRYPTION_FAILED-7Encryption key invalid or encryption process failed
AUTHENTICATION_FAILED-21Terminal authentication failed - check TID and credentials

Error Handling Best Practices

Implement comprehensive error handling for robust payment processing:
func handleTerminalError(_ error: TerminalError?) {
    switch error?.code {
    case .authenticationFailed:
        // Verify API key and <a href="#finding-terminal-information">terminal ID</a>
        print("Authentication failed: \(error?.message ?? "")")
        // Show user-friendly message and retry with correct credentials
        
    case .terminalBusy:
        // Terminal is processing another transaction
        print("Terminal busy: \(error?.message ?? "")")
        // Wait and retry after a delay
        retryAfterDelay(5.0) // 5 seconds
        
    case .failedToConnect, .notConnected:
        // Network connectivity issues
        print("Connection failed: \(error?.message ?? "")")
        // Check network connection and terminal <a href="#finding-terminal-information">IP address</a>
        checkNetworkConnectivity()
        
    case .keyInvalid, .invalidCredential:
        // Credential issues
        print("Invalid credentials: \(error?.message ?? "")")
        // Rotate or refresh API credentials
        refreshCredentials()
        
    case .encryptionFailed:
        // Encryption key issues
        print("Encryption failed: \(error?.message ?? "")")
        // Re-initialize SDK with correct encryption keys
        reinitializeSDK()
        
    default:
        // Log and escalate unknown errors
        print("Unknown error: \(error?.message ?? "")")
        // Report to monitoring system
        reportError(error)
    }
}
Always implement retry logic for transient errors like TERMINAL_BUSY and FAILED_TO_CONNECT. Use exponential backoff to avoid overwhelming the terminal.

Troubleshooting

Common Issues and Solutions

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
Always ensure the EDC is properly restarted before attempting new transactions to avoid data corruption.
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 Terminal IDs
  3. Configure terminal settings to disable manual transaction entry
POS-only mode prevents manual transaction entry and ensures all operations go through your application.
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
// Example: Query payment session status
let paymentSession = terminalApi.getPaymentSession(sessionId)
switch paymentSession.status {
case .completed:
    // Process successful payment
case .failed:
    // Handle failed payment
case .pending:
    // Payment still in progress
}
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 IP addresses 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
Use the connection monitoring features to diagnose specific connection issues.

Finding Terminal Information

  • BRI Terminals
  • NTT Terminals
1

Find Terminal ID (TID)

Open the BRI FMS app on the terminal device and locate the Terminal ID in the device information section.
BRI terminal showing Terminal ID in FMS app
2

Find IP Address

Open the ECRLink app on the terminal and check the network settings for the IP address.
BRI terminal showing IP address in ECRLink app
Screenshots and UI layouts may vary by firmware or app version. Refer to the latest vendor documentation if the interface differs from these instructions.