The Terminal C2C SDK is a convenience wrapper around the Terminal C2C API. For providers other than BRI and NTT, you can call the Terminal C2C API directly without the SDK. BRI and NTT terminals require either the Gateway App or the C2C SDK as a pre-install.
The Terminal C2C SDK for iOS enables you to integrate secure payment processing directly into your iOS applications. Connect to physical payment terminals through the local Gateway service using the TerminalC2C singleton object that handles HTTP communication with the Terminal Gateway.
This SDK uses a singleton pattern with the TerminalC2C object to communicate with the Terminal Gateway service via HTTP requests. The SDK provides both async/await and callback-based APIs for payment processing.
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 and Terminal ID (TID).
For Share Commerce (SHC) and Cashup terminals, make sure the Gateway app is installed on the device. If it is missing, install it from the Gateway download page or contact inpersonpayments@xendit.co for assistance.
BRI Indonesia
NTT Thailand
Cashup Indonesia (Coming Soon)
SHC Malaysia
Multiple Providers
Copy
// Add BRI provider for Indonesian terminalsTerminalC2C.shared.addProvider(provider: TerminalBRI.shared)
BRI provider supports timeout configuration for card and QR transactions, and provides enhanced transaction retry capabilities.
Copy
// Add NTT provider for Thai terminalsTerminalC2C.shared.addProvider(provider: TerminalNTT.shared)
NTT provider supports multi-function terminals with card payments and QR code transactions.
Copy
// Add Cashup provider for Indonesian terminalsTerminalC2C.shared.addProvider(provider: TerminalCashup.shared)
Cashup provider offers simplified integration with essential payment methods.
Copy
// Add SHC provider for Malaysian terminalsTerminalC2C.shared.addProvider(provider: TerminalSHC.shared)
SHC provider supports Share commerce terminals with card and QR payment capabilities for the Malaysia market.
This setup allows your app to work with different terminal types based on your merchant’s configuration.
Provider frameworks are optional. You can use the generic TerminalDevice.companion.create() method without adding provider-specific frameworks. Provider frameworks enable access to specialized helper methods like TerminalDevice.companion.bri(), TerminalDevice.companion.ntt(), and TerminalDevice.companion.cashup().
import TerminalC2C// Configure the default terminal devicelet terminalDevice = TerminalDevice.companion.create( id: "TERMINAL_ID", ipAddress: "192.168.1.100", provider: "BRI" // or "CASHUP", "NTT")TerminalC2C.shared.setTerminalDevice(terminalDevice)
Use provider-specific helper methods (available without additional dependencies):
Copy
import TerminalC2C// For BRI terminalslet briDevice = TerminalDevice.companion.bri( id: "TERMINAL_ID", ipAddress: "192.168.1.100")// For Cashup terminalslet cashupDevice = TerminalDevice.companion.cashup( id: "TERMINAL_ID", ipAddress: "192.168.1.101")// For NTT terminalslet nttDevice = TerminalDevice.companion.ntt( id: "TERMINAL_ID", ipAddress: "192.168.1.102")// For SHC terminalslet shcDevice = TerminalDevice.companion.shc( id: "TERMINAL_ID", ipAddress: "192.168.1.103")TerminalC2C.shared.setTerminalDevice(briDevice) // or cashupDevice, nttDevice
Provider-specific methods are available in the iOS SDK without requiring additional dependencies. You can use TerminalDevice.companion.bri(), TerminalDevice.companion.cashup(), TerminalDevice.companion.shc(), or TerminalDevice.companion.ntt() directly.
For apps handling multiple EDC devices, configure each terminal and specify per transaction:
Copy
import TerminalC2C// Configure multiple terminal deviceslet cashierTerminal = TerminalDevice.companion.create( id: "TID001", ipAddress: "192.168.1.100", provider: "BRI")let driveThruTerminal = TerminalDevice.companion.create( id: "TID002", ipAddress: "192.168.1.101", provider: "BRI")let selfServiceTerminal = TerminalDevice.companion.ntt( id: "TID003", ipAddress: "192.168.1.102")// Use specific terminal per transactionlet payment = Payment( orderID: "ORDER_123", amount: 10000.0, paymentMethod: BRIPaymentMethod.contactless)// Specify terminal device per requestTask { do { let result = try await TerminalC2C.shared.createPayment( payment: payment, device: cashierTerminal, // Use specific terminal isSimulation: false ) print("Payment successful: \(result.status)") } catch { print("Payment failed: \(error.localizedDescription)") }}
This approach allows dynamic terminal selection based on business logic, location, or user interaction without setting a global default.
You can also specify a device per request instead of setting a default device.
BRI terminals support both physical card payments and digital payment methods popular in Indonesia.
When using Cashup terminal configuration:
Payment Method
Description
Enum Case
Card
Standard card transactions
CashupPaymentMethod.card
QRIS
QR code payments
CashupPaymentMethod.qris
Cashup terminals focus on essential payment methods with card and QRIS support.
When using NTT terminal configuration for Thailand market:
Payment Method
Description
Enum Case
Card
Credit/debit card transactions
NTTPaymentMethod.card
MultiIPP
Installment payments
NTTPaymentMethod.multiIpp
Alipay+
Alipay transactions
NTTPaymentMethod.alipayPlus
WeChat Pay
WeChat payments
NTTPaymentMethod.wechatPayCil
TrueMoney
TrueMoney wallet
NTTPaymentMethod.trueMoney
LINE Pay
LINE Pay wallet
NTTPaymentMethod.linePay
ShopeePay
Shopee wallet
NTTPaymentMethod.shopee
ThaiQR
Thai QR standard
NTTPaymentMethod.thaiQrCode
BBL QRCS
Visa/Master QR
NTTPaymentMethod.bblQrcs
Atome
Atome BNPL
NTTPaymentMethod.atome
Sabuy Money
Sabuy wallet
NTTPaymentMethod.sabuyMoney
Max Me
Max Me wallet
NTTPaymentMethod.maxMe
NTT terminals provide comprehensive payment method support for the Thai market, including local e-wallets and QR payment standards.
When using SHC terminal configuration for the Malaysia market:
Payment Method
Description
Enum Case
Card
Credit/debit card transactions
SHCPaymentMethod.card
SHC terminals focus on core card acceptance for Malaysia deployments.
Use provider-specific payment method enums (for example BRIPaymentMethod.contactless) to avoid string mismatches. Each enum case maps to the underlying string identifier expected by the terminal.
Enable simulation mode to exercise payment flows without a physical terminal.
Copy
import TerminalC2C// Optional: enable simulation globallyTerminalC2C.shared.isSimulation = true// Or pass the flag per request when creating a paymentTask { do { let result = try await TerminalC2C.shared.createPayment( payment: payment, isSimulation: true ) print("Simulated payment: \(result.status)") } catch { print("Simulation failed: \(error.localizedDescription)") }}
Reset the simulation flag (TerminalC2C.shared.isSimulation = false) and omit the isSimulation parameter when moving to physical terminals. Combine simulation mode with the special test amounts (400508 decline, 400509 unavailable, 400711 cancel) to validate error handling before go-live.
import TerminalC2C// Create histories data with optional filterslet histories = Histories( commands: [CommandType.pay, CommandType.cancel], // Optional: filter by command types statuses: [Status.success], // Optional: filter by statuses from: "2024-01-01T00:00:00Z", // Optional: ISO 8601 format start date to: "2024-01-31T23:59:59Z" // Optional: ISO 8601 format end date)Task { do { let history = try await TerminalC2C.shared.getHistory(histories: histories) print("Retrieved history: \(history)") for transaction in history.transactions { print("Transaction: \(transaction.status)") } } catch let error as TerminalException { print("Failed to retrieve history: \(error.message)") }}
For compatibility with older code or specific use cases, callback-based methods are also available. Each *Async call returns an ActionJob you can cancel and invokes the handler with (TerminalResult?, KotlinThrowable?):
import TerminalC2Clet payment = Payment( orderID: "ORDER_123", amount: 10000.0, paymentMethod: BRIPaymentMethod.contactless)let job = TerminalC2C.shared.createPaymentAsync(payment: payment) { value, error in if let result = value { print("Payment successful: \(result.status)") } else if let error = error { if let terminalError = error as? TerminalException { print("Terminal error: \(terminalError.message)") } else { print("Error: \(error.localizedDescription)") } }}// Cancel the coroutine if the user backs out of the flowjob.cancel()
import TerminalC2Clet cancel = Cancel( paymentMethod: BRIPaymentMethod.contactless, terminalReference: "123456")let cancelJob = TerminalC2C.shared.cancelPaymentAsync(cancel: cancel) { value, error in if let result = value { print("Cancellation successful: \(result.status)") } else if let error = error { print("Cancellation failed: \(error.localizedDescription)") }}// Cancel when the request is no longer neededcancelJob.cancel()
import TerminalC2Clet receipt = Receipt( paymentMethod: BRIPaymentMethod.contactless, terminalReference: "123456")let receiptJob = TerminalC2C.shared.printReceiptAsync(receipt: receipt) { value, error in if let result = value { print("Receipt print command sent: \(result.status)") } else if let error = error { print("Receipt print failed: \(error.localizedDescription)") }}// Optionally cancel if the user exits the receipt screenreceiptJob.cancel()
import TerminalC2Clet settlement = Settlement( paymentMethod: BRIPaymentMethod.contactless)let settlementJob = TerminalC2C.shared.performSettlementAsync(settlement: settlement) { value, error in if let result = value { print("Settlement successful: \(result.status)") } else if let error = error { print("Settlement failed: \(error.localizedDescription)") }}// Cancel if the settlement action is no longer requiredsettlementJob.cancel()
import TerminalC2Clet histories = Histories( commands: [CommandType.pay, CommandType.cancel], statuses: [Status.success], from: "2024-01-01T00:00:00Z", to: "2024-01-31T23:59:59Z")let historyJob = TerminalC2C.shared.getHistoryAsync(histories: histories) { value, error in if let historyResult = value { print("Retrieved history: \(historyResult)") for transaction in historyResult.transactions { print("Transaction: \(transaction.status)") } } else if let error = error { print("Failed to retrieve history: \(error.localizedDescription)") }}// Cancel the history request if you no longer need updateshistoryJob.cancel()
Problem: SDK cannot reach the Terminal Gateway service.Solution:
Verify Terminal Gateway service is running locally
Check the port configuration (default: 8189)
Ensure the terminal device IP address is correct
Verify network connectivity between your app and gateway
Copy
// Check terminal device IP (using generic method)let device = TerminalDevice.companion.create( id: "TERMINAL_ID", ipAddress: "192.168.1.100", // Verify this IP provider: "BRI" // or "CASHUP", "NTT")// Or using provider-specific method// let device = TerminalDevice.companion.bri(// id: "TERMINAL_ID",// ipAddress: "192.168.1.100"// )// let device = TerminalDevice.companion.cashup(// id: "TERMINAL_ID",// ipAddress: "192.168.1.100"// )// let device = TerminalDevice.companion.ntt(// id: "TERMINAL_ID",// ipAddress: "192.168.1.100"// )TerminalC2C.shared.setTerminalDevice(device)
No Terminal Device Provided
Problem: Error indicating no terminal device is configured.Solution: Set a default terminal device before making requests:
Copy
// Set default deviceTerminalC2C.shared.setTerminalDevice(terminalDevice)// Then make requestslet payment = Payment( orderID: "ORDER_123", amount: 10000.0, paymentMethod: BRIPaymentMethod.contactless)Task { let result = try await TerminalC2C.shared.createPayment(payment: payment)}
Authentication Failed
Problem: Requests fail with authentication errors.Solutions:
Verify TerminalApp is initialized with correct client key
Check terminal ID matches the configured device
Ensure client key has Terminal C2C permissions
Copy
// Re-initialize with correct client keyTerminalApp.shared.initialize( clientKey: CLIENT_KEY, mode: TerminalMode.live // use TerminalMode.integration for test environments)TerminalC2C.shared.initialize( clientKey: CLIENT_KEY,)
Network Errors
Problem: Network-level errors when communicating with gateway.Solutions:
Check network connectivity
Verify firewall settings allow connections to gateway port
Ensure Terminal Gateway service is accessible
Check terminal device IP address is reachable
Use network debugging tools to verify connectivity between your app and the Terminal Gateway service.
Async/Await Compatibility
Problem: Issues with async/await syntax or compatibility.Solutions:
Ensure you’re using iOS 15+ or macOS 12+ for native async/await support
Use callback-based methods if targeting older iOS versions
Wrap async calls in Task blocks
Copy
import TerminalC2Clet payment = Payment( orderID: "ORDER_123", amount: 10000.0, paymentMethod: BRIPaymentMethod.contactless)// For iOS 15+Task { let result = try await TerminalC2C.shared.createPayment(payment: payment)}// For older iOS versions, use callbackslet legacyJob = TerminalC2C.shared.createPaymentAsync(payment: payment) { value, error in // Handle value or error}legacyJob.cancel()
Open the BRI FMS app on the terminal device and locate the Terminal ID in the device information section.
2
Find IP Address
Open the ECRLink app on the terminal and check the network settings for the IP address.
1
Find Terminal ID (TID)
Look for the Terminal ID on the physical sticker attached to the device.
2
Find IP Address
Open the NTT LinkPOS app on the terminal and navigate to network settings to find the IP address.
1
Find Terminal ID (TID)
Open the Cashlez app and navigate to the User Account tab to locate the Terminal ID.
2
Find IP Address
Open the Terminal Gateway app and check the device list for the terminal IP address.
1
Find Terminal ID (TID)
Open the Xendit app on the device and review the terminal details screen to locate the Terminal ID.
2
Find IP Address
Open the Terminal Gateway app and check the device list for the terminal IP address.
1
Find Terminal ID (TID)
Open the payment app on the Atom terminal, then go to Settings → Device Info to view the Terminal ID.
2
Find IP Address
Open the Terminal Gateway app and check the device list for the terminal IP address.
Screenshots and UI layouts may vary by firmware or app version. Refer to the latest vendor documentation if the interface differs from these instructions.