Skip to main content

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

Windows

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();
VIPA serial auto-detection

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());
StatusDescription
READYTerminal is ready for sessions
BUSYTerminal is currently processing a session
OFFLINETerminal is offline
LOGGED_OUTTerminal 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());
}
}
note

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

StatusDescription
pendingSession is active; the payer is completing the flow on the terminal.
successSession completed successfully. The outcome contains the resulting charge and/or mandate.
failureSession 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.

MethodDescription
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

MethodRequiredDescription
intent(TerminalSessionIntent intent)RequiredDefines the purpose of the session (e.g. charge, mandate)
nonce(String nonce)RequiredA 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)OptionalA custom identifier to reference this session in your own system. Multiple sessions can share the same external reference
metadata(Map<String, String> metadata)OptionalKey-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

MethodRequiredDescription
amount(BigDecimal amount, String currency)RequiredThe 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)OptionalCashback to be dispensed to the customer in addition to the charge amount. Amount in major unit, currency in ISO 4217 format

SessionStatus

ValueDescription
PENDINGSession is active; payer is completing the flow on the terminal
SUCCESSSession completed successfully
FAILURESession failed

FailureReason

ValueDescription
EXPIREDSession timed out before completion
CANCELLED_BY_APISession was cancelled via Stitch.cancelSession()
CANCELLED_BY_TERMINALTerminal cancelled transaction, eg timeout
BUSY_TERMINALTerminal was busy with another session
OFFLINE_TERMINALTerminal is offline
DECLINEDPayment 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
AccessorReturnsDescription
charge()ChargeThe charge created (null if no charge)

Charge

The charge object from a successful session (immutable record).

AccessorReturnsDescription
id()StringCharge ID (e.g. "ch_abc123")
amount()BigDecimalThe amount charged in the currency's major unit (e.g., 350.50 for R350.50)
currency()StringThe charge currency in ISO 4217 format (e.g. "ZAR")
cashback()CashbackThe cashback dispensed to the customer (null if no cashback)
status()ChargeStatusThe current status of the charge
failure()ChargeFailureFailure details when status is FAILURE (null otherwise)
createdAt()InstantWhen the charge was created
updatedAt()InstantWhen the charge was last modified
type()ChargeTypeThe type of charge (e.g. IN_PERSON_CARD)
card()CardDataCard data (scheme, masked PAN, etc.)
networkTransactionIdentifier()StringThe network transaction identifier.
retrievalReferenceNumber()StringThe 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.

AccessorReturnsDescription
reason()StringThe reason for the failure, available options: authorization_failed
authorization_declined
resultCode()ResultCodeThe 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.

AccessorReturnsDescription
value()StringThe numeric result code value (e.g. "05")
descriptor()StringA machine-readable descriptor for the result code (e.g. "do_not_honour")
detail()StringA human-readable explanation of the result code

Cashback

The cashback dispensed to the customer as part of the charge.

AccessorReturnsDescription
amount()BigDecimalThe cashback amount in the currency's major unit (e.g., 50.00 for R50.00)
currency()StringThe cashback currency in ISO 4217 format (e.g. "ZAR")

ChargeStatus

ValueDescription
PROCESSINGThe charge is being processed
SUCCESSThe charge was processed successfully
FAILUREThe charge did not complete successfully

ChargeType

ValueDescription
IN_PERSON_CARDAn in-person card charge

CardData

Card data from the charge.

AccessorReturnsDescription
id()StringThe unique identifier for this card (e.g. "card_1234567890")
bin()StringThe first 8 digits of the card number
last4()StringThe last 4 digits of the card number
expiry()CardExpiryThe card expiry date
createdAt()InstantWhen the card was created
updatedAt()InstantWhen the card was last modified
network()CardNetworkThe card network (may be null)
fundingType()FundingTypeThe funding type of the card (may be null)
issuer()CardIssuerThe card issuer details (may be null)

CardExpiry

AccessorReturnsDescription
month()StringThe expiry month in MM format (01-12)
year()StringThe expiry year in YY format (e.g. "25" for 2025)

CardNetwork

ValueDescription
VISAVisa
MASTERCARDMastercard
AMEXAmerican Express
DINERSDiners Club

FundingType

ValueDescription
CREDITCredit card
DEBITDebit card
PREPAIDPrepaid card

CardIssuer

AccessorReturnsDescription
name()StringThe name of the financial institution that issued the card
country()StringThe 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.

MethodDescription
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

ParameterTypeDescription
typeTerminalTypeThe terminal type (VIPA or ANDROID)
pathStringDevice path (e.g. "/dev/ttyUSB0", "COM3"), or null to auto-detect (VIPA only)

TcpConfig

ParameterTypeDescription
typeTerminalTypeThe terminal type (VIPA or ANDROID)
hostStringHostname or IP address of the terminal
portintTCP port number (e.g. 16107)

OtaConfig

ParameterTypeDescription
terminalIdStringThe terminal ID for the cloud-connected terminal

TerminalStatus

Returned by terminal.getStatus().

MethodReturnsDescription
getSerial()StringTerminal serial number
getStatus()StringTerminal status: READY, BUSY, OFFLINE, or LOGGED_OUT

Error Handling

All SDK methods throw StitchTerminalException on failure.

StitchTerminalException

MethodReturnsDescription
getErrorCode()ErrorCodeThe error code indicating the type of failure
getMessage()StringA 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

CodeNameDescription
0x0001ERR_TIMEOUTOperation timed out
0x0002ERR_CANCELOperation cancelled
0x0003ERR_BUSYDevice busy
0x0004ERR_FAILEDOperation failed
0x0005ERR_NOT_FOUNDResource not found
0x0100ERR_INTERNALInternal error
0x0101ERR_APISDK / API error
0x0102ERR_CONFIGConfiguration error
0x0103ERR_INVALID_ARGInvalid argument
0x0104ERR_BUFFERBuffer overflow
0x0105ERR_MALLOCMemory allocation failed
0x0106ERR_WRONG_STATEWrong state for this operation
0x0107ERR_NOT_SUPPORTEDOperation not supported
0x0200ERR_NETWORKNetwork error
0x0201ERR_SERVERServer error
0x0202ERR_UNAUTHORIZEDUnauthorized
0x0203ERR_COMMCommunication 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:

ScenarioError CodeDescription
Terminal offline / not connectedERR_COMMTerminal is not connected or not responding
Terminal busyERR_BUSYAnother session is already active on this terminal
Terminal not logged inERR_WRONG_STATETerminal requires login before transacting
Incorrect PINERR_UNAUTHORIZEDWhen login() for VIPA terminals
Duplicate nonceERR_APIA session with this nonce already exists
Invalid request (missing fields, bad values)ERR_INVALID_ARGMissing required fields or invalid values (e.g. null amount)
Network error (OTA / VIPA)ERR_NETWORKCannot 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().

Ambiguous outcomes on connection loss

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.

MethodReturnsDescription
Stitch.getSession(String id)TerminalSessionFetch a fresh snapshot of a session by ID
Stitch.cancelSession(String id)voidCancel a session by ID. If pending, onFailure() is called with CANCELLED_BY_API. No effect if already completed
Stitch.cancelSession(TerminalSession session)voidCancel a session (convenience overload)

Terminal

MethodReturnsDescription
new Terminal(ConnectionConfig)TerminalCreate a terminal instance with the given connection config
terminal.createSession(TerminalSessionCreateRequest request, TerminalSessionCallback callback)TerminalSessionStart a session on this terminal; returns initial snapshot (PENDING). Events are delivered to the callback
terminal.getStatus()TerminalStatusGet the terminal status
terminal.login(String)voidLog 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);
}
AccessorReturnsDescription
id()StringThe unique session ID (e.g. "ts_abc123")
status()SessionStatusSession status: PENDING, SUCCESS, or FAILURE
failureReason()FailureReasonWhy the session failed. Can be null
outcome()SessionOutcomeThe outcome (charge/mandate). Only present when status is SUCCESS
failedAttempts()List<Charge>List of failed charge attempts during this session
intent()TerminalSessionIntentThe original intent
nonce()StringThe unique identifier for idempotency
externalReference()StringThe custom identifier. Can be null
metadata()Map<String, String>Key-value pairs for additional information
createdAt()InstantWhen the session was created
updatedAt()InstantWhen the session was last updated