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.

For an overview of how payments are processed and how card data is kept secure, see How payments flow.

You create a session on a terminal by calling terminal.createSession(request, callback). The SDK returns a TerminalSession snapshot immediately (in PENDING), and the callback is invoked once the session reaches a terminal state (SUCCESS or FAILURE).

Requirements

The SDK targets Java 17 or later.

Terminal

The Terminal class allows the library to interface with payment terminal implementations. Each terminal instance handles the protocol required by the connected terminal.

Each Terminal instance manages a single active terminal connection at a time. Create multiple Terminal instances to connect to multiple terminals simultaneously. Only one Terminal can hold an active connection to a given device.

Terminal instances are intended to be long-lived. Create a single Terminal when your application starts, and treat it as the application’s representation of the terminal connected to the current device. Keep that instance available until shutdown, and dispose of it only when the application exits. This gives the terminal time to initialize, connect to the backend, and be ready before a session begins. Avoid creating a new Terminal for each session.

Creating a Terminal

Create a terminal with a ConnectionConfig, then call connect() to establish the connection:

ConnectionConfig config = ConnectionConfigBuilder.serial(TerminalType.ANDROID, "COM3").build();
Terminal terminal = new Terminal(config);

TerminalStatus status = terminal.connect();

connect() returns the terminal's status once connected.

Use ConnectionConfigBuilder to construct a ConnectionConfig. Use ConnectionConfigBuilder.serial ffor USB or serial connections, and ConnectionConfigBuilder.tcp for terminals reachable over your local network, you must set the token argument. The token can be fetched on the Terminal.

// Serial connection (Android)
ConnectionConfig serialAndroid = ConnectionConfigBuilder.serial(TerminalType.ANDROID, "COM3").build();

// TCP/IP connection to a Stitch Android terminal (requires token)
ConnectionConfig androidTcp = ConnectionConfigBuilder.tcp(TerminalType.ANDROID, "192.168.1.101", "tkn_abc123")
.build();
Android token

token is required when connecting to a Stitch Android terminal over TCP.

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());

See TerminalStatusValue for all status values.

Terminal Session

A Terminal Session defines the intent to process a charge, refund, or disbursement in person using a physical payment terminal.

Session lifecycle

A session reaches a terminal state of either SUCCESS or FAILURE. On SUCCESS, the session's outcome contains the resulting charge, refund, or disbursement. On FAILURE, the cause is available in failureReason.

If the SDK is unable to observe the session reaching a terminal state, for example because the connection to the terminal is lost mid session, the callback's onError() is invoked with a TerminalSessionError and the last known session snapshot, and no further callbacks follow. The session's status remains PENDING, the last state the SDK could confirm. The session may still complete on the terminal. Resolve the outcome using Stitch.getSession() or the Stitch HTTP API. Do not treat onError() as a failure or retry with the same intent.

Starting a session

Build a request and call terminal.createSession(request, callback). The call returns immediately with an immutable TerminalSession snapshot (in PENDING status). The callback is invoked once the session reaches a terminal state (SUCCESS or FAILURE).

// Build the intent (what to charge)
TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(new BigDecimal("25.00"), "ZAR")
.build();

// Build the request
TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.externalReference("order-456")
.metadata(Map.of("orderId", "12345"))
.build();

// Callback receives the final session state
TerminalSessionCallback callback = new TerminalSessionCallback() {
@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());
}

@Override
public void onError(TerminalSessionError error, TerminalSession session) {
// Outcome undetermined. Resolve via Stitch.getSession(session.id())
// or the Stitch HTTP API. Do not retry with the same intent.
System.out.println("Error: " + error.getMessage()
+ ". Resolve session " + session.id());
}
};

// Start the session (returns immediately with PENDING snapshot)
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
onSuccess(TerminalSession session)Session completed successfully. The session has an outcome
onFailure(TerminalSession session)Session failed. The session has a failureReason
onError(TerminalSessionError error, TerminalSession session)The SDK can no longer observe the session, for example after connection loss. The session's status remains PENDING, and the outcome is undetermined and must be resolved with Stitch.getSession(). See Session lifecycle and TerminalSessionError

Data Types

TerminalSessionCreateRequest

The request object for creating a terminal session.

TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.externalReference("order-456")
.metadata(Map.of("orderId", "12345"))
.build();

Builder Methods

MethodRequiredDescription
intent(TerminalSessionIntent intent)RequiredDefines the purpose of the session (charge, refund, or disbursement)
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: take a charge from the customer's card, refund a previous charge back to the same card, or pay out funds to a card as a disbursement.

FactoryDescription
TerminalSessionIntent.charge()Take a payment from the customer's card
TerminalSessionIntent.refund()Refund a previous charge back to the original card. Linked to a charge
TerminalSessionIntent.disbursement()Pay funds to a card as a stand-alone payout

Charge intent (once-off payment)

TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(new BigDecimal("30.00"), "ZAR")
.build();

Charge with cashback

TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(new BigDecimal("30.00"), "ZAR")
.cashback(new BigDecimal("5.00"), "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
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

Refund intent (linked refund)

A refund returns funds from a previous charge back to the same card. The original session ID must be supplied as the terminalSession.

TerminalSessionIntent intent = TerminalSessionIntent.refund()
.amount(new BigDecimal("30.00"), "ZAR")
.terminalSession("ts_abc123")
.reason(RefundReason.REQUESTED_BY_USER)
.build();

RefundIntent

MethodRequiredDescription
amount(BigDecimal amount, String currency)RequiredThe refund amount in the currency's major unit. May be less than or equal to the original charge amount. Currency in ISO 4217 format
terminalSession(String id)RequiredThe ID of the original terminal session being refunded (e.g. "ts_abc123")
reason(RefundReason reason)RequiredThe reason for the refund

RefundReason

ValueDescription
FRAUDThe original charge was fraudulent
REQUESTED_BY_USERThe customer requested the refund
DUPLICATE_CHARGEThe original charge was a duplicate

Disbursement intent (stand-alone payout)

A disbursement pays funds to a card without being linked to a previous session. The customer presents their card on the terminal to receive the payout.

TerminalSessionIntent intent = TerminalSessionIntent.disbursement()
.amount(new BigDecimal("50.00"), "ZAR")
.build();

DisbursementIntent

MethodRequiredDescription
amount(BigDecimal amount, String currency)RequiredThe disbursement amount in the currency's major unit. Currency in ISO 4217 format

SessionStatus

A session has two terminal states, SUCCESS and FAILURE. PENDING covers both an active session and a session whose outcome the SDK could not observe.

ValueTerminal?Description
SUCCESSYesSession completed successfully. See SessionOutcome
FAILUREYesSession failed. See FailureReason
PENDINGNoSession is active, or the outcome could not be observed. See Session lifecycle

FailureReason

ValueDescription
EXPIREDSession timed out before completion
CANCELLED_BY_APISession was cancelled via the online API
CANCELLED_BY_TERMINALTerminal cancelled the transaction, eg timeout, user cancel
BUSY_TERMINALTerminal was busy with another session
DECLINEDPayment was declined

SessionOutcome

The outcome when a session completes successfully. Contains the resulting charge, refund, or disbursement depending on the original TerminalSessionIntent. The accessor matching the intent type is populated; the others are null.

SessionOutcome outcome = session.outcome();

Charge charge = outcome.charge(); // may be null
Refund refund = outcome.refund(); // may be null
Disbursement disbursement = outcome.disbursement(); // may be null
AccessorReturnsDescription
charge()ChargeThe charge created (null if the session was not a charge)
refund()RefundThe refund created (null if the session was not a refund)
disbursement()DisbursementThe disbursement created (null if the session was not a disbursement)

Charge

The charge object from a successful charge session.

AccessorReturnsNullableDescription
id()StringNoCharge ID (e.g. "ch_abc123")
amount()BigDecimalNoThe amount charged in the currency's major unit
currency()StringNoThe charge currency in ISO 4217 format (e.g. "ZAR")
cashback()CashbackYesThe cashback dispensed to the customer. null if no cashback
status()ChargeStatusNoThe current status of the charge
failure()FailureYesFailure details when status() is FAILURE; otherwise null
createdAt()InstantNoWhen the charge was created
updatedAt()InstantNoWhen the charge was last modified
type()ChargeTypeNoThe type of charge (e.g. IN_PERSON_CARD)
card()CardYesCard data (scheme, masked PAN, etc.)
retrievalReferenceNumber()StringYesThe retrieval reference number (RRN) assigned by the card network for this transaction. null if not assigned

Failure

Failure details for a charge, refund, or disbursement. Present when the operation's status() is FAILURE.

public record Failure(String reason, ResultCode resultCode) {}
AccessorReturnsNullableDescription
reason()StringNoThe reason for the failure, available options: authorization_failed
authorization_declined
resultCode()ResultCodeYesThe result code from the card network. Only present when reason() is authorization_declined; otherwise null

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
currency()StringThe cashback currency in ISO 4217 format (e.g. "ZAR")

ChargeStatus

ValueDescription
SUCCESSThe charge was processed successfully
FAILUREThe charge did not complete successfully

ChargeType

ValueDescription
IN_PERSON_CARDAn in-person card charge

Refund

The refund object from a successful refund session. A refund returns funds linked to an original charge.

AccessorReturnsNullableDescription
id()StringNoRefund ID (e.g. "rf_abc123")
terminalSession()StringNoThe terminal session ID of the original charge being refunded (e.g. "ts_abc123")
amount()BigDecimalNoThe amount refunded in the currency's major unit
currency()StringNoThe refund currency in ISO 4217 format (e.g. "ZAR")
status()RefundStatusNoThe current status of the refund
failure()FailureYesFailure details when status() is FAILURE
createdAt()InstantNoWhen the refund was created
updatedAt()InstantNoWhen the refund was last modified
card()CardYesCard data (scheme, masked PAN, etc.). null if unavailable
retrievalReferenceNumber()StringYesThe retrieval reference number (RRN) assigned by the card network for this transaction. null if not assigned

RefundStatus

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

Disbursement

The disbursement object from a successful disbursement session. A disbursement pays funds to a card without a linked source charge. The customer presents their card on the terminal to receive the payout.

AccessorReturnsNullableDescription
id()StringNoDisbursement ID (e.g. "dsb_abc123")
amount()BigDecimalNoThe amount disbursed in the currency's major unit
currency()StringNoThe disbursement currency in ISO 4217 format (e.g. "ZAR")
status()DisbursementStatusNoThe current status of the disbursement
failure()FailureYesFailure details when status() is FAILURE; otherwise null
createdAt()InstantNoWhen the disbursement was created
updatedAt()InstantNoWhen the disbursement was last modified
card()CardYesCard data (scheme, masked PAN, etc.) for the card the funds were paid to
retrievalReferenceNumber()StringYesThe retrieval reference number (RRN) assigned by the card network for this transaction. null if not assigned

DisbursementStatus

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

Card

AccessorReturnsNullableDescription
bin()StringNoThe first 8 digits of the card number
last4()StringNoThe last 4 digits of the card number
expiry()CardExpiryNoThe card expiry date
network()CardNetworkYesThe card network
fundingType()FundingTypeYesThe funding type of the card
issuer()CardIssuerYesThe card issuer details

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")

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 (e.g. "COM3")
ConnectionConfigBuilder.tcp(TerminalType type, String host, String token)TCP/IP connection to a network-attached terminal

SerialConfig

AccessorReturnsNullableDescription
type()TerminalTypeNoThe terminal type (ANDROID)
path()StringNoDevice path (e.g. "COM3")

TcpConfig

AccessorReturnsNullableDescription
type()TerminalTypeNoThe terminal type (ANDROID)
host()StringNoHostname or IP address of the terminal
token()StringYesAuthentication token for the terminal. Required for Stitch Android terminals

TerminalStatus

Returned by terminal.getStatus() and terminal.connect().

public record TerminalStatus(String serial, TerminalStatusValue status) {}
AccessorReturnsDescription
getSerial()StringTerminal serial number
getStatus()TerminalStatusValueThe terminal's current status. See TerminalStatusValue

TerminalStatusValue

ValueDescription
READYTerminal is ready for sessions
BUSYTerminal is currently processing a session
OFFLINETerminal is offline
LOGGED_OUTTerminal requires login before transacting

Error Handling

SDK methods throw StitchTerminalException for protocol, connection, and argument errors. Session-level failures (e.g. DECLINED, BUSY_TERMINAL) are delivered to the callback as a TerminalSession with status() == FAILURE. Errors that occur while a session is in progress, leaving its outcome unobservable, are delivered to the callback via onError() as a TerminalSessionError.

StitchTerminalException

MethodReturnsDescription
getErrorCode()ErrorCodeThe error code indicating the type of failure
getMessage()StringA human-readable error message
try {
TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(new BigDecimal("15.00"), "ZAR")
.build();

TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.build();

TerminalSessionCallback callback = new TerminalSessionCallback() {
@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());
}

@Override
public void onError(TerminalSessionError error, TerminalSession session) {
System.out.println("Error: " + error.getMessage()
+ ". Resolve session " + session.id());
}
};

final TerminalSession handle = terminal.createSession(request, callback);

} catch (StitchTerminalException e) {
String message = switch (e.getErrorCode()) {
case TERMINAL_UNAVAILABLE -> "Terminal is offline or not responding";
case TERMINAL_BUSY -> "Terminal is busy";
case AUTHENTICATION -> "Check terminal credentials";
case INVALID_REQUEST -> "Check the session request";
default -> "Error: " + e.getMessage();
};
System.out.println(message);
}

ErrorCode

ValueDescription
CONFIGThe SDK or terminal connection is configured incorrectly
AUTHENTICATIONThe terminal token is missing or invalid
TERMINAL_UNAVAILABLEThe terminal is offline, disconnected, or not responding
TERMINAL_BUSYAnother session is already active on this terminal
TERMINAL_LOGGED_OUTThe terminal must be logged in before it can transact
INVALID_REQUESTThe request is missing required fields or contains invalid values
NETWORKThe SDK could not reach Stitch or the required network service
SERVERStitch returned a server-side error
UNSUPPORTEDThe requested operation is not supported by this terminal or connection type
TIMEOUTThe SDK operation timed out before it could complete
INTERNALThe SDK encountered an unexpected internal error. Retry if appropriate, then contact Stitch support with the exception message and logs

TerminalSessionError

Passed to the callback's onError() when the SDK can no longer observe an ongoing session, for example because the connection to the terminal was lost. The accompanying TerminalSession is the last known snapshot, still in PENDING status. See Failure during an ongoing session.

MethodReturnsDescription
getErrorCode()ErrorCodeThe error code indicating why the session could no longer be observed (e.g. TERMINAL_UNAVAILABLE, NETWORK, TIMEOUT)
getMessage()StringA human-readable error message

Connection failures

The connection to the terminal is established by connect() and maintained for the lifetime of the Terminal instance. If the SDK is unable to connect to the terminal (for example, if the terminal is offline or the token is invalid), connect() throws a StitchTerminalException.

If the connection to the terminal is lost after connect() has succeeded, the next SDK method call will throw the relevant exception. This will typically occur when creating a new terminal session or when polling getStatus().

ScenarioError CodeDescription
Terminal offline / not connectedTERMINAL_UNAVAILABLETerminal is not connected or not responding
Terminal busyTERMINAL_BUSYAnother session is already active on this terminal
Terminal not logged inTERMINAL_LOGGED_OUTThe terminal must be logged in on the device itself before transacting
Invalid request (missing fields, bad values)INVALID_REQUESTMissing required fields or invalid values (e.g. null amount)
Network errorNETWORKCannot reach Stitch servers

See ErrorCode for the full list of values and their descriptions.

Failure during an ongoing session

If the connection to a terminal is lost while a session is PENDING, the SDK can no longer observe the session. The callback's onError() is invoked with a TerminalSessionError and the last known snapshot, and no further callbacks follow. The session may still have completed on the terminal.

Pending sessions have undetermined outcomes

When the connection is lost mid session, the terminal may already have authorized (or declined) the transaction. The outcome screen shown on the terminal is final and authoritative; what the cardholder saw on the terminal is what actually happened on the card.

Resolving a Pending session

Use Stitch.getSession(sessionId) to resolve the outcome of a PENDING session. It first queries Stitch directly if available, and then queries any connected terminals to retrieve the outcome.

TerminalSession session = Stitch.getSession("ts_abc123");

For backoffice reconciliation without a Terminal instance available, use the Stitch HTTP API directly.

API Reference

Stitch

Static methods to manage terminal sessions.

MethodReturnsDescription
Stitch.getSession(String id)TerminalSessionFetch the latest state of a session by ID

Terminal

MethodReturnsDescription
new Terminal(ConnectionConfig config)TerminalConstruct a terminal with the given connection config
terminal.connect()TerminalStatusEstablish the connection to the terminal. Returns the terminal status once connected
terminal.createSession(TerminalSessionCreateRequest request, TerminalSessionCallback callback)TerminalSessionStart a session on this terminal. Returns an initial PENDING snapshot immediately. The callback reports the final state
terminal.getStatus()TerminalStatusGet the terminal status
terminal.close()voidDisconnect and release native resources. From AutoCloseable

TerminalSession

An immutable snapshot of a session, returned by terminal.createSession() or Stitch.getSession().

public record TerminalSession(
String id,
TerminalSessionIntent intent,
SessionStatus status,
FailureReason failureReason,
SessionOutcome outcome,
String externalReference,
Map<String, String> metadata,
Instant createdAt,
Instant updatedAt
) {}

public interface TerminalSessionCallback {
void onSuccess(TerminalSession session);
void onFailure(TerminalSession session);
void onError(TerminalSessionError error, TerminalSession session);
}
AccessorReturnsNullableDescription
id()StringNoThe unique session ID (e.g. "ts_abc123")
intent()TerminalSessionIntentNoThe original intent
status()SessionStatusNoSession status: PENDING, SUCCESS, or FAILURE
failureReason()FailureReasonYesWhy the session failed. null unless status() is FAILURE
outcome()SessionOutcomeYesThe outcome (containing a charge, refund, or disbursement). Only present when status() is SUCCESS; otherwise null
externalReference()StringYesThe custom identifier. null if not set
metadata()Map<String, String>NoKey-value pairs for additional information
createdAt()InstantNoWhen the session was created
updatedAt()InstantNoWhen the session was last updated

Example: end-to-end flow

The example below uses a TCP connection to a Stitch Android terminal. For refund or disbursement sessions, build the appropriate intent and read the matching accessor on session.outcome() (refund() or disbursement()).

import java.math.BigDecimal;

ConnectionConfig config = ConnectionConfigBuilder.tcp(TerminalType.ANDROID, "192.168.1.101", "tkn_abc123")
.build();

// Created once, kept until application shutdown; close() on exit
Terminal terminal = new Terminal(config);
terminal.connect();

TerminalSessionIntent intent = TerminalSessionIntent.charge()
.amount(new BigDecimal("25.00"), "ZAR")
.build();

TerminalSessionCreateRequest request = TerminalSessionCreateRequest.builder()
.intent(intent)
.externalReference("order-456")
.build();

terminal.createSession(request, new TerminalSessionCallback() {
@Override
public void onSuccess(TerminalSession session) {
Charge charge = session.outcome().charge();
System.out.println("Approved. Charge " + charge.id()
+ " for " + charge.amount() + " " + charge.currency());
}

@Override
public void onFailure(TerminalSession session) {
System.out.println("Declined: " + session.failureReason());
}

@Override
public void onError(TerminalSessionError error, TerminalSession session) {
System.out.println("Error: " + error.getMessage()
+ ". Resolve session " + session.id());
}
});