Java
Overview
The Stitch Terminal SDK is designed for POS systems and other merchant-facing applications to seamlessly connect to payment terminals that process payments on Stitch.
You create a session on a terminal by calling terminal.createSession(request, callback). The SDK returns a TerminalSession object with an ID, status, and outcome.
Merchant Setup
On Windows POS systems, the USB driver for VIPA terminals must be installed before the SDK can communicate with the device.
Terminal
The Terminal class allows the library to interface with different payment terminal implementations. Each terminal instance handles the specific communication protocol, message format, and behavior required by a particular terminal type or vendor (e.g. a VIPA terminal communicates with Verifone devices).
Each Terminal instance manages a single active terminal connection at a time. Create multiple Terminal instances to connect to multiple terminals simultaneously.
Create terminals and log in (VIPA only) at application startup. This gives terminals enough time to initialize and be ready when you begin a transaction.
Creating a Terminal
Create a terminal instance with a ConnectionConfig:
ConnectionConfig config = ConnectionConfigBuilder.serial(TerminalType.VIPA, "/dev/ttyUSB0").build();
Terminal terminal = new Terminal(config);
Each connection type has its own builder with only the parameters relevant to that type:
// Serial connection (VIPA or Android)
ConnectionConfig serialLinux = ConnectionConfigBuilder.serial(TerminalType.VIPA, "/dev/ttyUSB0").build(); // Linux
ConnectionConfig serialWindows = ConnectionConfigBuilder.serial(TerminalType.ANDROID, "COM3").build(); // Windows
// Serial connection with auto-detection (VIPA only)
ConnectionConfig serialAuto = ConnectionConfigBuilder.serial(TerminalType.VIPA, null).build();
// TCP/IP connection (network-attached terminal)
ConnectionConfig tcp = ConnectionConfigBuilder.tcp(TerminalType.VIPA, "192.168.1.100", 16107).build();
// OTA connection (cloud-connected terminal)
ConnectionConfig ota = ConnectionConfigBuilder.overTheAir("terminal_abc123").build();
For VIPA terminals using serial connections, passing null as the path enables automatic device detection. The SDK will scan for and connect to an available serial device.
Terminal Status
Get the status of a connected terminal.
TerminalStatus status = terminal.getStatus();
System.out.println("Serial: " + status.getSerial());
System.out.println("Status: " + status.getStatus());
| Status | Description |
|---|---|
READY | Terminal is ready for sessions |
BUSY | Terminal is currently processing a session |
OFFLINE | Terminal is offline |
LOGGED_OUT | Terminal requires login before transacting |
Login (VIPA only)
For VIPA terminals, call login() after creating the terminal and before starting sessions. Do this at application startup so the terminal is ready when you need to process transactions. The terminal status should be LOGGED_OUT before calling login.
try {
terminal.login("1234");
System.out.println("Login successful");
} catch (StitchTerminalException e) {
if (e.getErrorCode() == ErrorCode.ERR_UNAUTHORIZED) {
System.out.println("Incorrect PIN");
} else {
System.out.println("Login failed: " + e.getMessage());
}
}
Login is a one-time setup step. Once logged in, the terminal remains authenticated for the lifetime of the application.
For Android terminals, login is not required via the SDK. You must log in directly on the terminal device.
Terminal Session
A Terminal Session defines the intent to process a charge or a disbursement in person using a physical payment terminal.
Session lifecycle
| Status | Description |
|---|---|
pending | Session is active; the payer is completing the flow on the terminal. |
success | Session completed successfully. The outcome contains the resulting charge and/or mandate. |
failure | Session failed and will not be retried. |
Starting a session
Build a request and call terminal.createSession(request, callback) on the terminal. The call returns immediately with an immutable TerminalSession snapshot (in PENDING status).
// Build the intent (what to charge)
TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(2500, "ZAR")
.build();
// Build the request
TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.nonce("unique-nonce-123")
.externalReference("order-456")
.metadata(Map.of("orderId", "12345"))
.build();
// Callback receives a session with updated state
TerminalSessionCallback callback = new TerminalSessionCallback() {
@Override
public void onUpdate(TerminalSession session) {
System.out.println("Session updated: " + session.status());
}
@Override
public void onSuccess(TerminalSession session) {
Charge charge = session.outcome().charge();
System.out.println("Success! Charge ID: " + charge.id());
}
@Override
public void onFailure(TerminalSession session) {
System.out.println("Failed: " + session.failureReason());
}
};
// Start the session (returns immediately with PENDING snapshot)
final TerminalSession handle = terminal.createSession(request, callback);
System.out.println("Session started: " + handle.id());
Callback methods
The TerminalSessionCallback interface methods are called during and after the session.
| Method | Description |
|---|---|
onUpdate(TerminalSession session) | Session state changed (e.g. a charge attempt failed). |
onSuccess(TerminalSession session) | Session completed successfully. The session has an outcome with charge |
onFailure(TerminalSession session) | Session failed. The session has a failureReason |
TerminalSessionCallback callback = new TerminalSessionCallback() {
@Override
public void onUpdate(TerminalSession session) {
List<Charge> failed = session.failedAttempts();
if (!failed.isEmpty()) {
Charge lastAttempt = failed.get(failed.size() - 1);
System.out.println("Charge failed: " + lastAttempt.failure().reason());
}
}
@Override
public void onSuccess(TerminalSession session) {
Charge charge = session.outcome().charge();
System.out.println("Success! Charge ID: " + charge.id());
}
@Override
public void onFailure(TerminalSession session) {
System.out.println("Session failed: " + session.failureReason());
}
};
Data Types
TerminalSessionCreateRequest
The request object for creating a terminal session.
TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.nonce("unique-nonce-123")
.externalReference("order-456")
.metadata(Map.of("orderId", "12345"))
.build();
Builder Methods
| Method | Required | Description |
|---|---|---|
intent(TerminalSessionIntent intent) | Required | Defines the purpose of the session (e.g. charge, mandate) |
nonce(String nonce) | Required | A unique identifier for idempotency. If you attempt to create a session with a nonce that has already been used, an error will be returned |
externalReference(String ref) | Optional | A custom identifier to reference this session in your own system. Multiple sessions can share the same external reference |
metadata(Map<String, String> metadata) | Optional | Key-value pairs to store additional, structured information relevant to your integration |
TerminalSessionIntent
The intent defines what the session should do: create a charge, set up a mandate, or both. Use the static factory methods to build intents.
Charge intent (once-off payment)
TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(3000, "ZAR")
.build();
Charge with cashback
TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(3000, "ZAR")
.cashback(500, "ZAR")
.build();
ChargeIntent
| Method | Required | Description |
|---|---|---|
amount(BigDecimal amount, String currency) | Required | The charge amount in the currency's major unit (e.g., 30.00 for R30.00). Currency in ISO 4217 format (only "ZAR" supported) |
cashback(BigDecimal amount, String currency) | Optional | Cashback to be dispensed to the customer in addition to the charge amount. Amount in major unit, currency in ISO 4217 format |
SessionStatus
| Value | Description |
|---|---|
PENDING | Session is active; payer is completing the flow on the terminal |
SUCCESS | Session completed successfully |
FAILURE | Session failed |
FailureReason
| Value | Description |
|---|---|
EXPIRED | Session timed out before completion |
CANCELLED_BY_API | Session was cancelled via Stitch.cancelSession() |
CANCELLED_BY_TERMINAL | Terminal cancelled transaction, eg timeout |
BUSY_TERMINAL | Terminal was busy with another session |
OFFLINE_TERMINAL | Terminal is offline |
DECLINED | Payment was declined |
SessionOutcome
The outcome when a session completes successfully. Contains the resulting charge.
SessionOutcome outcome = session.outcome(); // from TerminalSession record
Charge charge = outcome.charge(); // may be null
| Accessor | Returns | Description |
|---|---|---|
charge() | Charge | The charge created (null if no charge) |
Charge
The charge object from a successful session (immutable record).
| Accessor | Returns | Description |
|---|---|---|
id() | String | Charge ID (e.g. "ch_abc123") |
amount() | BigDecimal | The amount charged in the currency's major unit (e.g., 350.50 for R350.50) |
currency() | String | The charge currency in ISO 4217 format (e.g. "ZAR") |
cashback() | Cashback | The cashback dispensed to the customer (null if no cashback) |
status() | ChargeStatus | The current status of the charge |
failure() | ChargeFailure | Failure details when status is FAILURE (null otherwise) |
createdAt() | Instant | When the charge was created |
updatedAt() | Instant | When the charge was last modified |
type() | ChargeType | The type of charge (e.g. IN_PERSON_CARD) |
card() | CardData | Card data (scheme, masked PAN, etc.) |
networkTransactionIdentifier() | String | The network transaction identifier. |
retrievalReferenceNumber() | String | The retrieval reference number (RRN) assigned by the card network for this transaction |
ChargeFailure
Failure details for a charge. Present when the charge status is FAILURE.
| Accessor | Returns | Description |
|---|---|---|
reason() | String | The reason for the failure, available options: authorization_failed authorization_declined |
resultCode() | ResultCode | The result code from the card network. Only present when reason is authorization_declined |
ResultCode
The result code from the card network explaining why authorization failed.
| Accessor | Returns | Description |
|---|---|---|
value() | String | The numeric result code value (e.g. "05") |
descriptor() | String | A machine-readable descriptor for the result code (e.g. "do_not_honour") |
detail() | String | A human-readable explanation of the result code |
Cashback
The cashback dispensed to the customer as part of the charge.
| Accessor | Returns | Description |
|---|---|---|
amount() | BigDecimal | The cashback amount in the currency's major unit (e.g., 50.00 for R50.00) |
currency() | String | The cashback currency in ISO 4217 format (e.g. "ZAR") |
ChargeStatus
| Value | Description |
|---|---|
PROCESSING | The charge is being processed |
SUCCESS | The charge was processed successfully |
FAILURE | The charge did not complete successfully |
ChargeType
| Value | Description |
|---|---|
IN_PERSON_CARD | An in-person card charge |
CardData
Card data from the charge.
| Accessor | Returns | Description |
|---|---|---|
id() | String | The unique identifier for this card (e.g. "card_1234567890") |
bin() | String | The first 8 digits of the card number |
last4() | String | The last 4 digits of the card number |
expiry() | CardExpiry | The card expiry date |
createdAt() | Instant | When the card was created |
updatedAt() | Instant | When the card was last modified |
network() | CardNetwork | The card network (may be null) |
fundingType() | FundingType | The funding type of the card (may be null) |
issuer() | CardIssuer | The card issuer details (may be null) |
CardExpiry
| Accessor | Returns | Description |
|---|---|---|
month() | String | The expiry month in MM format (01-12) |
year() | String | The expiry year in YY format (e.g. "25" for 2025) |
CardNetwork
| Value | Description |
|---|---|
VISA | Visa |
MASTERCARD | Mastercard |
AMEX | American Express |
DINERS | Diners Club |
FundingType
| Value | Description |
|---|---|
CREDIT | Credit card |
DEBIT | Debit card |
PREPAID | Prepaid card |
CardIssuer
| Accessor | Returns | Description |
|---|---|---|
name() | String | The name of the financial institution that issued the card |
country() | String | The country code (ISO 3166-1 alpha-2) of the card issuer (e.g. "ZA") |
ConnectionConfig
sealed interface ConnectionConfig permits SerialConfig, TcpConfig, OtaConfig {}
ConnectionConfigBuilder
Static factory methods that return a builder. Call .build() to create a ConnectionConfig.
| Method | Description |
|---|---|
ConnectionConfigBuilder.serial(TerminalType type, String path) | Serial connection to a device path. Pass null for path to auto-detect (VIPA only) |
ConnectionConfigBuilder.tcp(TerminalType type, String host, int port) | TCP/IP connection to a network-attached terminal |
ConnectionConfigBuilder.overTheAir(String terminalId) | OTA connection to a cloud-connected terminal |
SerialConfig
| Parameter | Type | Description |
|---|---|---|
type | TerminalType | The terminal type (VIPA or ANDROID) |
path | String | Device path (e.g. "/dev/ttyUSB0", "COM3"), or null to auto-detect (VIPA only) |
TcpConfig
| Parameter | Type | Description |
|---|---|---|
type | TerminalType | The terminal type (VIPA or ANDROID) |
host | String | Hostname or IP address of the terminal |
port | int | TCP port number (e.g. 16107) |
OtaConfig
| Parameter | Type | Description |
|---|---|---|
terminalId | String | The terminal ID for the cloud-connected terminal |
TerminalStatus
Returned by terminal.getStatus().
| Method | Returns | Description |
|---|---|---|
getSerial() | String | Terminal serial number |
getStatus() | String | Terminal status: READY, BUSY, OFFLINE, or LOGGED_OUT |
Error Handling
All SDK methods throw StitchTerminalException on failure.
StitchTerminalException
| Method | Returns | Description |
|---|---|---|
getErrorCode() | ErrorCode | The error code indicating the type of failure |
getMessage() | String | A human-readable error message |
try {
TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(1500, "ZAR")
.build();
TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.nonce("unique-nonce-789")
.build();
TerminalSessionCallback callback = new TerminalSessionCallback() {
@Override
public void onUpdate(TerminalSession session) {}
@Override
public void onSuccess(TerminalSession session) {
Charge charge = session.outcome().charge();
System.out.println("Success! Charge ID: " + charge.id());
}
@Override
public void onFailure(TerminalSession session) {
System.out.println("Session failed: " + session.failureReason());
}
};
final TerminalSession handle = terminal.createSession(request, callback);
} catch (StitchTerminalException e) {
ErrorCode code = e.getErrorCode();
switch (code) {
case TIMEOUT -> System.out.println("Timed out");
case CANCEL -> System.out.println("Cancelled");
default -> System.out.println("Error: " + e.getMessage());
}
}
Error Codes
| Code | Name | Description |
|---|---|---|
0x0001 | ERR_TIMEOUT | Operation timed out |
0x0002 | ERR_CANCEL | Operation cancelled |
0x0003 | ERR_BUSY | Device busy |
0x0004 | ERR_FAILED | Operation failed |
0x0005 | ERR_NOT_FOUND | Resource not found |
0x0100 | ERR_INTERNAL | Internal error |
0x0101 | ERR_API | SDK / API error |
0x0102 | ERR_CONFIG | Configuration error |
0x0103 | ERR_INVALID_ARG | Invalid argument |
0x0104 | ERR_BUFFER | Buffer overflow |
0x0105 | ERR_MALLOC | Memory allocation failed |
0x0106 | ERR_WRONG_STATE | Wrong state for this operation |
0x0107 | ERR_NOT_SUPPORTED | Operation not supported |
0x0200 | ERR_NETWORK | Network error |
0x0201 | ERR_SERVER | Server error |
0x0202 | ERR_UNAUTHORIZED | Unauthorized |
0x0203 | ERR_COMM | Communication error |
Connection failures
Connection failures can occur either before a session is created or during an active session. Failures that occur while calling SDK functions are thrown immediately as exceptions. Failures that occur during a pending session are reported asynchronously via session state updates and callbacks.
The connection to the terminal is established and maintained when a terminal instance is created. If the SDK is unable to connect to the terminal (for example, if the terminal is offline, not connected, or not powered on), terminal instance creation fails and the SDK throws a StitchTerminalException with error code ERR_COMM.
If the connection to the terminal is lost after the terminal instance has been created, the next SDK function call will fail and throw the relevant exception. This will typically occur when creating a new terminal session or when polling getStatus().
List of common failure cases:
| Scenario | Error Code | Description |
|---|---|---|
| Terminal offline / not connected | ERR_COMM | Terminal is not connected or not responding |
| Terminal busy | ERR_BUSY | Another session is already active on this terminal |
| Terminal not logged in | ERR_WRONG_STATE | Terminal requires login before transacting |
| Incorrect PIN | ERR_UNAUTHORIZED | When login() for VIPA terminals |
| Duplicate nonce | ERR_API | A session with this nonce already exists |
| Invalid request (missing fields, bad values) | ERR_INVALID_ARG | Missing required fields or invalid values (e.g. null amount) |
| Network error (OTA / VIPA) | ERR_NETWORK | Cannot reach Stitch servers |
Connection Failures during a pending session
If the connection to a terminal is lost after createSession() returns successfully and the session state is PENDING, the failure is handled asynchronously.
In this case, the session state changes to FAILED, and the SDK invokes the onFailure callback with the failureReason set to OFFLINE_TERMINAL.
If the POS system loses connectivity (for example, the POS device powers down or the SDK process stops), the terminal will eventually mark the session as FAILED with the failure reason EXPIRED. This terminal session can later be retrieved using getSession() with the session ID.
Failed charge attempts during a session
A single session may involve multiple charge attempts. When a charge attempt fails (eg. card declined) but the session can still retry, the SDK calls onUpdate and the failed charge is appended to session.failedAttempts().
When connection is lost during a session for standalone terminals (Android terminals), a charge may have already been authorized by the card network before the terminal went offline. In such cases, the charge will be automatically reversed.
API Reference
Stitch
Static methods to manage terminal sessions.
| Method | Returns | Description |
|---|---|---|
Stitch.getSession(String id) | TerminalSession | Fetch a fresh snapshot of a session by ID |
Stitch.cancelSession(String id) | void | Cancel a session by ID. If pending, onFailure() is called with CANCELLED_BY_API. No effect if already completed |
Stitch.cancelSession(TerminalSession session) | void | Cancel a session (convenience overload) |
Terminal
| Method | Returns | Description |
|---|---|---|
new Terminal(ConnectionConfig) | Terminal | Create a terminal instance with the given connection config |
terminal.createSession(TerminalSessionCreateRequest request, TerminalSessionCallback callback) | TerminalSession | Start a session on this terminal; returns initial snapshot (PENDING). Events are delivered to the callback |
terminal.getStatus() | TerminalStatus | Get the terminal status |
terminal.login(String) | void | Log into terminal with PIN. VIPA only. Throws StitchTerminalException with code ERR_UNAUTHORIZED for incorrect PIN |
TerminalSession
An immutable snapshot of a session, returned by terminal.createSession() or Stitch.getSession().
// TerminalSession is a record
public record TerminalSession(
String id,
TerminalSessionIntent intent,
SessionStatus status,
FailureReason failureReason,
SessionOutcome outcome,
List<Charge> failedAttempts,
String nonce,
String externalReference,
Map<String, String> metadata,
Instant createdAt,
Instant updatedAt
) {}
public interface TerminalSessionCallback {
void onUpdate(TerminalSession session);
void onSuccess(TerminalSession session);
void onFailure(TerminalSession session);
}
| Accessor | Returns | Description |
|---|---|---|
id() | String | The unique session ID (e.g. "ts_abc123") |
status() | SessionStatus | Session status: PENDING, SUCCESS, or FAILURE |
failureReason() | FailureReason | Why the session failed. Can be null |
outcome() | SessionOutcome | The outcome (charge/mandate). Only present when status is SUCCESS |
failedAttempts() | List<Charge> | List of failed charge attempts during this session |
intent() | TerminalSessionIntent | The original intent |
nonce() | String | The unique identifier for idempotency |
externalReference() | String | The custom identifier. Can be null |
metadata() | Map<String, String> | Key-value pairs for additional information |
createdAt() | Instant | When the session was created |
updatedAt() | Instant | When the session was last updated |