Skip to main content

Pre-Authorisations

Pre-Authorisations let you place an initial hold on a card. You can later capture all or part of the authorised amount, or release it. Use this flow when the final charge isn’t known upfront - common in hospitality and car rentals.

Note: Every request requires an X-API-Key header with your API key.


Common Enums


enum PaymentMethods {
VISA = 0;
MASTERCARD = 1;
AMEX = 2;
BAN_CONTACT = 3;
CHINA_UNION_PAY = 4;
MAESTRO = 5;
DINERS = 6;
DISCOVER = 7;
JCB = 8;
ALIPAY = 9;
WECHAT = 10;
UNKNOWN_METHOD = -1;
}

enum AuthStatus {
AUTH_STATUS_UNSPECIFIED = 0;
PENDING_AUTHORISATION = 1;
AUTHORISED = 2;
DECLINED = 3;
CANCELLED = 4;
PENDING_CAPTURE = 5;
CAPTURED = 6;
CAPTURE_FAILED = 7;
PENDING_RELEASE = 8;
RELEASED = 9;
RELEASE_FAILED = 10;
PENDING_TOP_UP = 11;
TOP_UP_FAILED = 12;
EXPIRED = 13;
FAILED = 14;
}

enum RecurringProcessingModel {
MODEL_UNSPECIFIED = 0;
CARD_ON_FILE = 1;
SUBSCRIPTION = 2;
UNSCHEDULED_CARD_ON_FILE = 3;
}

enum CardTokenStatus {
PENDING = 0;
FAILED = 1;
READY = 2;
DELETED = 3;
PENDING_DELETE = 4;
UNKNOWN_STATUS = -1;
}


1. Pre-Authorise

Service Call: KodyPreAuthTerminalService.PreAuthorise

Initiates a pre-authorisation on a physical payment terminal. The terminal will prompt the customer to present their card.

Request

rpc PreAuthorise(PreAuthorisationRequest) returns (stream PreAuthorisationResponse);

message PreAuthorisationRequest {
string idempotency_uuid = 1; // Unique key for idempotency
string pre_auth_reference = 2; // Client's unique reference
optional string order_id = 3; // Client's order identifier
string store_id = 4; // Kody Store ID
string terminal_id = 5; // Target payment terminal ID
uint64 amount_minor_units = 6; // Amount in minor units (e.g., pence)
string currency = 7; // ISO 4217 currency code
}

Response

message PreAuthorisationResponse {
string pre_auth_reference = 1; // Echoed from request
string psp_reference = 2; // PSP reference
optional string order_id = 3; // Echoed if provided
string pre_auth_id = 4; // Kody authorisation ID
optional PaymentCard card_details = 5; // Card information
AuthStatus status = 6; // Final status
uint64 authorised_amount_minor_units = 7; // Actual authorised amount
string currency = 8; // Currency
google.protobuf.Timestamp created_at = 9; // Creation timestamp
optional google.protobuf.Timestamp authorised_at = 10; // Authorisation timestamp
optional string auth_code = 11; // PSP auth code
}

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.AuthStatus;
import com.kodypay.grpc.preauth.v1.KodyPreAuthTerminalServiceGrpc;
import com.kodypay.grpc.preauth.v1.PreAuthorisationRequest;
import com.kodypay.grpc.preauth.v1.PreAuthorisationResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExamplePreAuthorise {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your Terminal ID
String terminalId = "TERMINAL ID";
//TODO: Replace with your store operating currency: ISO 4217
String currencyCode = "HKD";
//TODO: Replace with the auth amount in minor units
long amountMinorUnits = 1000;
//TODO: Replace these with your reference ID
String preAuthReference = "REF-" + System.currentTimeMillis();
//TODO: Replace these with your idempotency UUID
String idempotencyUuid = UUID.randomUUID().toString();

preAuthorise(storeId, terminalId, amountMinorUnits, currencyCode, preAuthReference, idempotencyUuid);
}

// Example of a pre-authorisation with idempotency and reference IDs
private static void preAuthorise(String storeId, String terminalId, long amountMinorUnits, String currency, String preAuthReference, String idempotencyUuid) {
var preAuthClient = createKodyPreAuthTerminalClient();

PreAuthorisationRequest preAuthRequest = PreAuthorisationRequest.newBuilder()
.setStoreId(storeId)
.setTerminalId(terminalId)
.setAmountMinorUnits(amountMinorUnits)
.setCurrency(currency)
.setPreAuthReference(preAuthReference)
.setIdempotencyUuid(idempotencyUuid)
.build();

Iterator<PreAuthorisationResponse> responseIterator = preAuthClient.preAuthorise(preAuthRequest);
while (responseIterator.hasNext()) {
PreAuthorisationResponse preAuthResponse = responseIterator.next();
processPreAuthResponse(preAuthResponse);
}
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process pre-authorisation response
private static void processPreAuthResponse(PreAuthorisationResponse response) {
System.out.println("PreAuth ID: " + response.getPreAuthId() + ", PreAuth Status: " + response.getStatus());

switch (response.getStatus()) {
case AUTHORISED:
System.out.println("Pre-Auth success: " + response.getPreAuthId());
System.out.println("PspReference: " + response.getPspReference());
System.out.println("Auth Code: " + response.getAuthCode());
break;
case PENDING_AUTHORISATION:
System.out.println("Pre-Auth pending: " + response.getPreAuthId());
break;
case FAILED, EXPIRED, CANCELLED, DECLINED:
System.out.println("Pre-Auth failed: " + response.getPreAuthId());
break;
case AUTH_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("Pre-Auth status unknown: " + response.getPreAuthId());
break;
}
}
}

2. Top-Up Authorisation

Service Call: KodyPreAuthTerminalService.TopUpAuthorisation

Increases the authorised amount on an existing pre-authorisation.

Request

rpc TopUpAuthorisation(TopUpAuthorisationRequest) returns (stream TopUpAuthorisationResponse);

message TopUpAuthorisationRequest {
string idempotency_uuid = 1; // Unique key for idempotency
string top_up_reference = 2; // Client's reference for this top-up
optional string order_id = 3; // Original order ID
string store_id = 4; // Kody Store ID
string pre_auth_id = 5; // ID from original PreAuthorise
uint64 top_up_amount_minor_units = 6; // Additional amount to authorise
string currency = 7; // ISO 4217 currency code
}

Response

message TopUpAuthorisationResponse {
string top_up_reference = 1; // Echoed from request
string psp_reference = 2; // PSP reference
optional string order_id = 3; // Echoed if provided
optional uint64 total_authorised_amount_minor_units = 4; // New total authorised amount
optional google.protobuf.Timestamp topped_up_at = 5; // Top-up timestamp
AuthStatus status = 6; // Status of top-up
string topup_id = 7; // Unique identifier for this top-up
}

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.*;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExampleTopupAuthorisation {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your PreAuth ID returned from the pre-authorisation
String preAuthId = "PRE AUTH ID";
//TODO: Replace with your store operating currency: ISO 4217
String currencyCode = "HKD";
//TODO: Replace with the topup amount in minor units, topup amount is the auth amount after topup
long topUpAmountMinorUnits = 1000;
//TODO: Replace these with your reference ID
String topUpReference = "REF-" + System.currentTimeMillis();
//TODO: Replace these with your idempotency UUID
String idempotencyUuid = UUID.randomUUID().toString();

topUpAuthorisation(storeId, preAuthId, topUpAmountMinorUnits, currencyCode, topUpReference, idempotencyUuid);
}

// Example of a topup-authorisation with idempotency and reference IDs
private static void topUpAuthorisation(String storeId, String preAuthId, long topUpAmountMinorUnits, String currency, String topUpReference, String idempotencyUuid) {
var preAuthClient = createKodyPreAuthTerminalClient();

TopUpAuthorisationRequest topUpAuthRequest = TopUpAuthorisationRequest.newBuilder()
.setStoreId(storeId)
.setPreAuthId(preAuthId)
.setTopUpAmountMinorUnits(topUpAmountMinorUnits)
.setCurrency(currency)
.setTopUpReference(topUpReference)
.setIdempotencyUuid(idempotencyUuid)
.build();

Iterator<TopUpAuthorisationResponse> responseIterator = preAuthClient.topUpAuthorisation(topUpAuthRequest);
while (responseIterator.hasNext()) {
TopUpAuthorisationResponse topUpAuthResponse = responseIterator.next();
processTopUpAuthResponse(topUpAuthResponse);
}
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process topup-authorisation response
private static void processTopUpAuthResponse(TopUpAuthorisationResponse response) {
System.out.println("TopUp ID: " + response.getTopupId() + ", TopUp Status: " + response.getStatus());

switch (response.getStatus()) {
case AUTHORISED:
System.out.println("TopUp success: " + response.getTopupId());
System.out.println("PspReference: " + response.getPspReference());
break;
case PENDING_TOP_UP:
System.out.println("TopUp pending: " + response.getTopupId());
break;
case TOP_UP_FAILED:
System.out.println("TopUp failed: " + response.getTopupId());
break;
case AUTH_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("TopUp status unknown: " + response.getTopupId());
break;
}
}
}

3. Capture Authorisation

Service Call: KodyPreAuthTerminalService.CaptureAuthorisation

Captures the specified amount from the existing pre-authorisation. Any remaining authorised amount is automatically released back to the cardholder.

Request

rpc CaptureAuthorisation(CaptureAuthorisationRequest) returns (CaptureAuthorisationResponse);

message CaptureAuthorisationRequest {
string idempotency_uuid = 1; // Unique key for idempotency
string capture_reference = 2; // Client's reference for capture
optional string order_id = 3; // Original order ID
string store_id = 4; // Kody Store ID
string pre_auth_id = 5; // ID from original PreAuthorise
uint64 capture_amount_minor_units = 6; // Final amount to capture
string currency = 7; // ISO 4217 currency code
optional string terminal_id = 8; // Required for batch payments
}

Response

message CaptureAuthorisationResponse {
string capture_reference = 1; // Echoed from request
string psp_reference = 2; // PSP reference
optional string order_id = 3; // Echoed if provided
google.protobuf.Timestamp captured_at = 4; // Capture timestamp
string capture_id = 5; // Unique identifier for this capture
AuthStatus status = 6; // Status of capture
}

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.KodyPreAuthTerminalServiceGrpc;
import com.kodypay.grpc.preauth.v1.CaptureAuthorisationRequest;
import com.kodypay.grpc.preauth.v1.CaptureAuthorisationResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExampleCaptureAuthorisation {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your Terminal ID, required for Batch Payments
String terminalId = "TERMINAL ID";
//TODO: Replace this with your PreAuth ID returned from the pre-authorisation
String preAuthId = "PRE AUTH ID";
//TODO: Replace with your store operating currency: ISO 4217
String currencyCode = "HKD";
//TODO: Replace with the auth amount in minor units, the remaining amount will be automatically released
long captureAmountMinorUnits = 1000;
//TODO: Replace these with your reference ID
String captureReference = "REF-" + System.currentTimeMillis();
//TODO: Replace these with your idempotency UUID
String idempotencyUuid = UUID.randomUUID().toString();

captureAuthorisation(storeId, terminalId, preAuthId, captureAmountMinorUnits, currencyCode, captureReference, idempotencyUuid);
}

// Example of a capture authorisation with idempotency and reference IDs
private static void captureAuthorisation(String storeId, String terminalId, String preAuthId, long captureAmountMinorUnits, String currency, String captureReference, String idempotencyUuid) {
var preAuthClient = createKodyPreAuthTerminalClient();

CaptureAuthorisationRequest captureAuthRequest = CaptureAuthorisationRequest.newBuilder()
.setStoreId(storeId)
.setTerminalId(terminalId)
.setPreAuthId(preAuthId)
.setCaptureAmountMinorUnits(captureAmountMinorUnits)
.setCurrency(currency)
.setCaptureReference(captureReference)
.setIdempotencyUuid(idempotencyUuid)
.build();

CaptureAuthorisationResponse captureAuthResponse = preAuthClient.captureAuthorisation(captureAuthRequest);
processCaptureAuthResponse(captureAuthResponse);
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process capture authorisation response
private static void processCaptureAuthResponse(CaptureAuthorisationResponse response) {
// Capture is processed asynchronously, and the final status can be queried through the GetPreAuthorisation interface.
System.out.println("Capture Ref: " + response.getCaptureReference() + ", Status: " + response.getStatus());

switch (response.getStatus()) {
case CAPTURED:
System.out.println("Capture success: " + response.getCaptureReference());
System.out.println("Capture Id: " + response.getCaptureId());
System.out.println("PspReference: " + response.getPspReference());
System.out.println("CapturedAt: " + response.getCapturedAt());
break;
case PENDING_CAPTURE:
System.out.println("Capture pending: " + response.getCaptureReference());
System.out.println("Capture Id: " + response.getCaptureId());
break;
case CAPTURE_FAILED:
System.out.println("Capture failed: " + response.getCaptureReference());
break;
case AUTH_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("Capture status unknown: " + response.getCaptureReference());
break;
}
}
}


4. Release Authorisation

Service Call: KodyPreAuthTerminalService.ReleaseAuthorisation

Releases the hold on funds from an existing pre-authorisation without capturing.

Request

rpc ReleaseAuthorisation(ReleaseAuthorisationRequest) returns (ReleaseAuthorisationResponse);

message ReleaseAuthorisationRequest {
string idempotency_uuid = 1; // Unique key for idempotency
string release_reference = 2; // Client's reference for release
optional string order_id = 3; // Original order ID
string store_id = 4; // Kody Store ID
string pre_auth_id = 5; // ID from original PreAuthorise
}

Response

message ReleaseAuthorisationResponse {
string release_reference = 1; // Echoed from request
string psp_reference = 2; // PSP reference
optional string order_id = 3; // Echoed if provided
google.protobuf.Timestamp released_at = 4; // Release timestamp
AuthStatus status = 5; // Status of release
}

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.KodyPreAuthTerminalServiceGrpc;
import com.kodypay.grpc.preauth.v1.ReleaseAuthorisationRequest;
import com.kodypay.grpc.preauth.v1.ReleaseAuthorisationResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExampleReleaseAuthorisation {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your PreAuth ID returned from the pre-authorisation
String preAuthId = "PRE AUTH ID";
//TODO: Replace these with your reference ID
String releaseReference = "REF-" + System.currentTimeMillis();
//TODO: Replace these with your idempotency UUID
String idempotencyUuid = UUID.randomUUID().toString();

releaseAuthorisation(storeId, preAuthId, releaseReference, idempotencyUuid);
}

// Example of a release authorisation with idempotency and reference IDs
private static void releaseAuthorisation(String storeId, String preAuthId, String releaseReference, String idempotencyUuid) {
var preAuthClient = createKodyPreAuthTerminalClient();

ReleaseAuthorisationRequest releaseAuthRequest = ReleaseAuthorisationRequest.newBuilder()
.setStoreId(storeId)
.setPreAuthId(preAuthId)
.setReleaseReference(releaseReference)
.setIdempotencyUuid(idempotencyUuid)
.build();

ReleaseAuthorisationResponse releaseAuthResponse = preAuthClient.releaseAuthorisation(releaseAuthRequest);
processReleaseAuthResponse(releaseAuthResponse);
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process release authorisation response
private static void processReleaseAuthResponse(ReleaseAuthorisationResponse response) {
// Release is processed asynchronously, and the final status can be queried through the GetPreAuthorisation interface.
System.out.println("Release Ref: " + response.getReleaseReference() + ", Status: " + response.getStatus());

switch (response.getStatus()) {
case RELEASED:
System.out.println("Release success: " + response.getReleaseReference());
System.out.println("PspReference: " + response.getPspReference());
System.out.println("PspReference: " + response.getPspReference());
System.out.println("ReleasedAt: " + response.getReleasedAt());
break;
case PENDING_RELEASE:
System.out.println("Release pending: " + response.getReleaseReference());
break;
case RELEASE_FAILED:
System.out.println("Release failed: " + response.getReleaseReference());
break;
case AUTH_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("Release status unknown: " + response.getReleaseReference());
break;
}
}
}

5. Get Pre-Authorisation

Service Call: KodyPreAuthTerminalService.GetPreAuthorisation

Retrieves the current state and details of an existing pre-authorisation.

Request

rpc GetPreAuthorisation(GetPreAuthorisationRequest) returns (GetPreAuthorisationResponse);

message GetPreAuthorisationRequest {
string store_id = 1; // Kody Store ID
string pre_auth_id = 2; // ID from original PreAuthorise
}

Response

message GetPreAuthorisationResponse {
string pre_auth_reference = 1; // Client's original reference
string pre_auth_id = 2; // Kody authorisation ID
optional string psp_reference = 3; // PSP reference
AuthStatus status = 4; // Current status
uint64 total_authorised_amount_minor_units = 5; // Total authorised amount
optional uint64 captured_amount_minor_units = 6; // Amount captured
string store_currency = 7; // Currency code
PaymentCard card_details = 8; // Card information
google.protobuf.Timestamp created_at = 9; // Creation timestamp
optional google.protobuf.Timestamp last_topped_up_at = 10; // Last top-up timestamp
optional google.protobuf.Timestamp captured_at = 11; // Capture timestamp
optional google.protobuf.Timestamp released_at = 12; // Release timestamp
optional google.protobuf.Timestamp authorised_at = 13; // Authorisation timestamp
repeated Adjustment adjustments = 14; // List of adjustments
repeated Capture captures = 15; // List of captures
}

message Adjustment {
string adjustment_id = 1; // Unique identifier for this adjustment.
optional string adjustment_reference = 2; // Client's reference for the adjustment, if provided.
AdjustmentStatus status = 3; // Current status of the adjustment.
uint64 amount_minor_units = 4; // Amount of the adjustment in minor currency units.
google.protobuf.Timestamp created_at = 5; // Timestamp when the adjustment was created.
google.protobuf.Timestamp updated_at = 6; // Timestamp when the adjustment was last updated.
optional string psp_reference = 7; // PSP reference for this adjustment, if available.
}

enum AdjustmentStatus {
ADJUSTMENT_STATUS_UNSPECIFIED = 0;
ADJUSTMENT_PENDING = 1;
ADJUSTMENT_SUCCESS = 2;
ADJUSTMENT_ERROR = 3;
}

message Capture {
string capture_id = 1;
uint64 amount_minor_units = 2;
CaptureStatus status = 3;
google.protobuf.Timestamp created_at = 4;
optional string psp_reference = 5;
optional string error = 6;

enum CaptureStatus {
CAPTURE_STATUS_UNSPECIFIED = 0;
CAPTURE_PENDING = 1;
CAPTURE_SUCCESS = 2;
CAPTURE_FAILED = 3;
}
}

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.AuthStatus;
import com.kodypay.grpc.preauth.v1.GetPreAuthorisationRequest;
import com.kodypay.grpc.preauth.v1.GetPreAuthorisationResponse;
import com.kodypay.grpc.preauth.v1.KodyPreAuthTerminalServiceGrpc;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.concurrent.TimeUnit;

public class ExampleGetPreAuthorisation {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your PreAuth ID returned from the pre-authorisation
String preAuthId = "PRE AUTH ID";

getPreAuthorisation(storeId, preAuthId);
}

// Example of a get pre-authorisation with idempotency and reference IDs
private static void getPreAuthorisation(String storeId, String preAuthId) {
var preAuthClient = createKodyPreAuthTerminalClient();

GetPreAuthorisationRequest preAuthRequest = GetPreAuthorisationRequest.newBuilder()
.setStoreId(storeId)
.setPreAuthId(preAuthId)
.build();

GetPreAuthorisationResponse getPreAuthResponse = preAuthClient.getPreAuthorisation(preAuthRequest);
processGetPreAuthResponse(getPreAuthResponse);
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process get pre-authorisation response
private static void processGetPreAuthResponse(GetPreAuthorisationResponse response) {
System.out.println("PreAuth ID: " + response.getPreAuthId() + ", PreAuth Status: " + response.getStatus());

switch (response.getStatus()) {
case AUTHORISED:
System.out.println("Pre-Auth success: " + response.getPreAuthId());
System.out.println("PspReference: " + response.getPspReference());
break;
case PENDING_AUTHORISATION:
System.out.println("Pre-Auth pending: " + response.getPreAuthId());
break;
case FAILED, EXPIRED, CANCELLED, DECLINED:
System.out.println("Pre-Auth failed: " + response.getPreAuthId());
break;
case PENDING_RELEASE:
System.out.println("Pre-Auth pending release: " + response.getPreAuthId());
break;
case RELEASED:
System.out.println("Pre-Auth released: " + response.getPreAuthId());
break;
case PENDING_CAPTURE:
System.out.println("Pre-Auth pending capture: " + response.getPreAuthId());
break;
case CAPTURED:
System.out.println("Pre-Auth captured: " + response.getPreAuthId());
break;
case AUTH_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("Pre-Auth status unknown: " + response.getPreAuthId());
break;
}
for (var adjustment : response.getAdjustmentsList()) {
System.out.println("Adjustment ID: " + adjustment.getAdjustmentId() + ", Adjustment Amount: " + adjustment.getAmountMinorUnits() + ", Adjustment Status: " + adjustment.getStatus());
switch (adjustment.getStatus()) {
case ADJUSTMENT_SUCCESS -> System.out.println("Adjustment success: " + adjustment.getAdjustmentId());
case ADJUSTMENT_ERROR -> System.out.println("Adjustment failed: " + adjustment.getAdjustmentId());
case ADJUSTMENT_PENDING -> System.out.println("Adjustment pending: " + adjustment.getAdjustmentId());
case ADJUSTMENT_STATUS_UNSPECIFIED, UNRECOGNIZED -> System.out.println("Adjustment status unknown: " + adjustment.getAdjustmentId());
}
}
for (var capture : response.getCapturesList()) {
System.out.println("Capture ID: " + capture.getCaptureId() + ", Capture Amount: " + capture.getAmountMinorUnits() + ", Capture Status: " + capture.getStatus());
switch (capture.getStatus()) {
case CAPTURE_SUCCESS -> System.out.println("Capture success: " + capture.getCaptureId());
case CAPTURE_FAILED -> System.out.println("Capture failed: " + capture.getCaptureId());
case CAPTURE_PENDING -> System.out.println("Capture pending: " + capture.getCaptureId());
case CAPTURE_STATUS_UNSPECIFIED, UNRECOGNIZED -> System.out.println("Capture status unknown: " + capture.getCaptureId());
}
}
}
}

6. Refund Capture

Service Call: KodyPreAuthTerminalService.RefundCapture

Issues a full or partial refund against a previously captured authorisation.

Request

rpc RefundCapture(RefundCaptureRequest) returns (RefundCaptureResponse);

message RefundCaptureRequest {
string idempotency_uuid = 1; // Unique key for idempotency
string refund_reference = 2; // Client's reference for refund
optional string order_id = 3; // Original order ID
string store_id = 4; // Kody Store ID
string pre_auth_id = 5; // ID from original PreAuthorise
string capture_id = 6; // ID from CaptureAuthorisation
uint64 refund_amount_minor_units = 7; // Amount to refund
string currency = 8; // ISO 4217 currency code
}

Response

message RefundCaptureResponse {
string refund_reference = 1; // Echoed from request
optional string psp_reference = 2; // PSP reference
optional string order_id = 3; // Echoed if provided
google.protobuf.Timestamp refunded_at = 4; // Refund timestamp
RefundStatus status = 5; // Status of refund
optional string failure_reason = 6; // Reason if failed
}

enum RefundStatus {
REFUND_STATUS_UNSPECIFIED = 0;
REFUND_PENDING = 1;
REFUND_REQUESTED = 2;
REFUND_FAILED = 3;
}

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.*;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExampleRefundCapture {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your Terminal ID, required for Batch Payments
String terminalId = "TERMINAL ID";
//TODO: Replace this with your PreAuth ID returned from the pre-authorisation
String preAuthId = "PRE AUTH ID";
//TODO: Replace this with your Capture ID returned from the capture authorisation
String captureId = "CAPTURE ID";
//TODO: Replace with your store operating currency: ISO 4217
String currencyCode = "HKD";
//TODO: Replace with the auth amount in minor units, cannot exceed the captured amount
long refundAmountMinorUnits = 1000;
//TODO: Replace these with your reference ID
String refundReference = "REF-" + System.currentTimeMillis();
//TODO: Replace these with your idempotency UUID
String idempotencyUuid = UUID.randomUUID().toString();

refundCapture(storeId, preAuthId, captureId, refundAmountMinorUnits, currencyCode, refundReference, idempotencyUuid);
}

// Example of a capture authorisation with idempotency and reference IDs
private static void refundCapture(String storeId, String preAuthId, String captureId, long refundAmountMinorUnits, String currency, String refundReference, String idempotencyUuid) {
var preAuthClient = createKodyPreAuthTerminalClient();

RefundCaptureRequest refundCaptureRequest = RefundCaptureRequest.newBuilder()
.setStoreId(storeId)
.setPreAuthId(preAuthId)
.setCaptureId(captureId)
.setRefundAmountMinorUnits(refundAmountMinorUnits)
.setCurrency(currency)
.setRefundReference(refundReference)
.setIdempotencyUuid(idempotencyUuid)
.build();

RefundCaptureResponse refundCaptureResponse = preAuthClient.refundCapture(refundCaptureRequest);
processRefundCaptureResponse(refundCaptureResponse);
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process refund capture response
private static void processRefundCaptureResponse(RefundCaptureResponse response) {
// There is no clear success status for refunds.
// Generally, if REFUND_REQUESTED/REFUND_PENDING is returned, the refund request can be considered successful.
// The actual success is subject to whether the user has received the refund.
System.out.println("Refund Ref: " + response.getRefundReference() + ", Refund Status: " + response.getStatus());

switch (response.getStatus()) {
case REFUND_REQUESTED, REFUND_PENDING:
System.out.println("Refund requested/pending: " + response.getRefundedAt());
System.out.println("PspReference: " + response.getPspReference());
System.out.println("RefundedAt: " + response.getRefundedAt());
break;
case REFUND_FAILED:
System.out.println("Refund failed: " + response.getFailureReason());
break;
case REFUND_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("Refund status unknown: " + response.getRefundReference());
break;
}
}
}

7. Create Card Token

Service Call: KodyTerminalTokenService.CreateCardToken

Creates a card token by prompting the customer to present their card at a physical payment terminal. The token can be stored and used for future payments without requiring the physical card.

Request

rpc CreateCardToken(CreateCardTokenRequest) returns (stream TokenDetailsResponse);

message CreateCardTokenRequest {
string store_id = 1; // Your Kody store ID
string terminal_id = 2; // Terminal ID where tokenisation is performed
string idempotency_uuid = 3; // Unique key for idempotency
string token_reference = 4; // Your unique reference for this token
string payer_reference = 5; // Payer identifier (e.g., user ID)
optional RecurringProcessingModel recurring_processing_model = 6; // Recurring model
}

Response

message TokenDetailsResponse {
string token_id = 1; // Kody's unique token identifier
string token_reference = 2; // Echoed from request
optional string payment_token = 3; // PSP token for future payments
string payer_reference = 4; // Echoed from request
optional RecurringProcessingModel recurring_processing_model = 5; // Echoed if provided
CardTokenStatus status = 6; // Token status
optional google.protobuf.Timestamp created_at = 7; // Creation timestamp
optional CardInfo card_info = 8; // Card details

message CardInfo {
PaymentMethods payment_method = 1; // Card brand (e.g., VISA, MC)
string payment_method_variant = 2; // Variant (e.g., visadebit)
string card_last_4_digits = 3; // Last 4 digits
string card_expiry_date = 4; // Expiry in MM/YY format
string pos_entry_mode = 5; // POS entry mode
}
}

Examples

package com.kody;

import com.kodypay.grpc.common.CardTokenStatus;
import com.kodypay.grpc.pay.v1.*;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExampleCreateCardToken {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your Terminal ID
String terminalId = "TERMINAL ID";
//TODO: Replace this with your Payer Reference
String payerReference = "Payer-" + System.currentTimeMillis();

createCardToken(storeId, terminalId, payerReference);

//TODO: Optional - Replace these with your reference IDs or leave null
String tokenReference = "REF-" + System.currentTimeMillis();
String idempotencyUuid = UUID.randomUUID().toString();
createCardTokenIdempotently(storeId, terminalId, payerReference, tokenReference, idempotencyUuid);
}

// Example of a card tokenisation
private static void createCardToken(String storeId, String terminalId, String payerReference) {
var tokenClient = createKodyTerminalTokenClient();

CreateCardTokenRequest tokenRequest = CreateCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setTerminalId(terminalId)
.setPayerReference(payerReference)
.build();

Iterator<TokenDetailsResponse> responseIterator = tokenClient.createCardToken(tokenRequest);
while (responseIterator.hasNext()) {
TokenDetailsResponse tokenDetailsResponse = responseIterator.next();
processTokenisationResponse(tokenDetailsResponse);
}
}

// Example of a card tokenisation with idempotency and reference IDs
private static void createCardTokenIdempotently(String storeId, String terminalId, String payerReference, String idempotencyUuid, String tokenReference) {
var tokenClient = createKodyTerminalTokenClient();

CreateCardTokenRequest tokenRequest = CreateCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setTerminalId(terminalId)
.setPayerReference(payerReference)
.setIdempotencyUuid(idempotencyUuid)
.setTokenReference(tokenReference)
.build();

TokenDetailsResponse tokenDetailsResponse = tokenClient.createCardToken(tokenRequest).next();
processTokenisationResponse(tokenDetailsResponse);
}

private static KodyTerminalTokenServiceGrpc.KodyTerminalTokenServiceBlockingStub createKodyTerminalTokenClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyTerminalTokenServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process tokenisation response
private static void processTokenisationResponse(TokenDetailsResponse response) {
System.out.println("Token ID: " + response.getTokenId() + ", Token Status: " + response.getStatus());

switch (response.getStatus()) {
case READY:
System.out.println("Token success: " + response.getTokenId());
System.out.println("Payment Token: " + response.getPaymentToken());
System.out.println("Card Info: " + response.getCardInfo());
break;
case PENDING:
System.out.println("Token pending: " + response.getTokenId());
break;
case FAILED:
System.out.println("Token failed: " + response.getTokenId());
break;
case UNKNOWN_STATUS, UNRECOGNIZED:
System.out.println("TopUp status unknown: " + response.getTokenId());
break;
}
}
}

8. Get Card Token

Service Call: KodyTerminalTokenService.GetCardToken

Retrieves the details of a previously created card token.

Request

rpc GetCardToken(GetCardTokenRequest) returns (TokenDetailsResponse);

message GetCardTokenRequest {
string store_id = 1; // Your Kody store ID
string token_id = 2; // The unique token identifier created by Kody
}

Response

Same as CreateCardToken response (TokenDetailsResponse).

Examples

package com.kody;

import com.kodypay.grpc.common.CardTokenStatus;
import com.kodypay.grpc.pay.v1.GetCardTokenRequest;
import com.kodypay.grpc.pay.v1.KodyTerminalTokenServiceGrpc;
import com.kodypay.grpc.pay.v1.TokenDetailsResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.concurrent.TimeUnit;

public class ExampleGetCardToken {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your Token ID returned from the createCardToken
String tokenId = "TOKEN ID";

getCardToken(storeId, tokenId);
}

// Example of get card token by token ID
private static void getCardToken(String storeId, String tokenId) {
var tokenClient = createKodyTerminalTokenClient();

GetCardTokenRequest tokenRequest = GetCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setTokenId(tokenId)
.build();

TokenDetailsResponse tokenDetailsResponse = tokenClient.getCardToken(tokenRequest);
processTokenisationResponse(tokenDetailsResponse);
}

private static KodyTerminalTokenServiceGrpc.KodyTerminalTokenServiceBlockingStub createKodyTerminalTokenClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyTerminalTokenServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process tokenisation response
private static void processTokenisationResponse(TokenDetailsResponse response) {
System.out.println("Token ID: " + response.getTokenId() + ", Token Status: " + response.getStatus());

switch (response.getStatus()) {
case READY:
System.out.println("Token success: " + response.getTokenId());
System.out.println("Payment Token: " + response.getPaymentToken());
System.out.println("Card Info: " + response.getCardInfo());
break;
case PENDING:
System.out.println("Token pending: " + response.getTokenId());
break;
case FAILED:
System.out.println("Token failed: " + response.getTokenId());
break;
case UNKNOWN_STATUS, UNRECOGNIZED:
System.out.println("TopUp status unknown: " + response.getTokenId());
break;
}
}
}

9. Pre-Authorise With Card Token

Service Call: KodyPreAuthTerminalService.PreAuthoriseWithCardToken

Initiates a pre-authorisation using a previously stored card token (no terminal interaction required).

Request

rpc PreAuthoriseWithCardToken(PreAuthoriseWithCardTokenRequest) returns (stream PreAuthorisationResponse);

message PreAuthoriseWithCardTokenRequest {
string idempotency_uuid = 1; // Unique key for idempotency
string pre_auth_reference = 2; // Client's unique reference
optional string order_id = 3; // Client's order identifier
string store_id = 4; // Kody Store ID
uint64 amount_minor_units = 5; // Amount in minor units
string currency = 6; // ISO 4217 currency code
string payment_token = 7; // Stored card token
string expiry_date = 8; // Card expiry in MM/YY format
}

Response

Same as PreAuthorise response.

Examples

package com.kody;

import com.kodypay.grpc.preauth.v1.*;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

import java.util.Iterator;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class ExamplePreAuthoriseWithCardToken {
//TODO: Replace this with the testing or live environment
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API KEY";

public static void main(String[] args) {
//TODO: Replace this with your Store ID
String storeId = "STORE ID";
//TODO: Replace this with your Payment Token returned from CreateCardToken
String paymentToken = "Payment Token";
//TODO: Replace this with your Card Expiry Date returned from CreateCardToken
String expiryDate = "03/30"; // format in MM/YY
//TODO: Replace with your store operating currency: ISO 4217
String currencyCode = "HKD";
//TODO: Replace with the auth amount in minor units
long amountMinorUnits = 1000;
//TODO: Replace these with your reference ID
String preAuthReference = "REF-" + System.currentTimeMillis();
//TODO: Replace these with your idempotency UUID
String idempotencyUuid = UUID.randomUUID().toString();

preAuthoriseWithCardToken(storeId, paymentToken, expiryDate, amountMinorUnits, currencyCode, preAuthReference, idempotencyUuid);
}

// Example of a pre-authorisation with idempotency and reference IDs
private static void preAuthoriseWithCardToken(String storeId, String paymentToken, String expiryDate, long amountMinorUnits, String currency, String preAuthReference, String idempotencyUuid) {
var preAuthClient = createKodyPreAuthTerminalClient();

PreAuthoriseWithCardTokenRequest preAuthRequest = PreAuthoriseWithCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setPaymentToken(paymentToken)
.setExpiryDate(expiryDate)
.setAmountMinorUnits(amountMinorUnits)
.setCurrency(currency)
.setPreAuthReference(preAuthReference)
.setIdempotencyUuid(idempotencyUuid)
.build();

Iterator<PreAuthorisationResponse> responseIterator = preAuthClient.preAuthoriseWithCardToken(preAuthRequest);
while (responseIterator.hasNext()) {
PreAuthorisationResponse preAuthResponse = responseIterator.next();
processPreAuthResponse(preAuthResponse);
}
}

private static KodyPreAuthTerminalServiceGrpc.KodyPreAuthTerminalServiceBlockingStub createKodyPreAuthTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);

return KodyPreAuthTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}

// Helper method to process pre-authorisation response
private static void processPreAuthResponse(PreAuthorisationResponse response) {
System.out.println("PreAuth ID: " + response.getPreAuthId() + ", PreAuth Status: " + response.getStatus());

switch (response.getStatus()) {
case AUTHORISED:
System.out.println("Pre-Auth success: " + response.getPreAuthId());
System.out.println("PspReference: " + response.getPspReference());
System.out.println("Auth Code: " + response.getAuthCode());
break;
case PENDING_AUTHORISATION:
System.out.println("Pre-Auth pending: " + response.getPreAuthId());
break;
case FAILED, EXPIRED, CANCELLED, DECLINED:
System.out.println("Pre-Auth failed: " + response.getPreAuthId());
break;
case AUTH_STATUS_UNSPECIFIED, UNRECOGNIZED:
System.out.println("Pre-Auth status unknown: " + response.getPreAuthId());
break;
}
}
}

Remember to replace the following placeholders with your actual values:

  • HOSTNAME: Use either grpc-staging.kodypay.com (for development and test) or grpc.kodypay.com (for live).
  • API_KEY: Your API key
  • STORE_ID: Your store identifier
  • TERMINAL_ID: Your terminal identifier

For further support or more detailed information, please contact our support team.