Skip to main content

Online Payments

The Online Payments API lets you take card and wallet payments for e-commerce checkouts. Use it to initiate a payment and redirect the customer to a hosted payment page, track the payment to completion, list past payments, and issue refunds. It also supports payer details, card tokenisation, expiry, and delayed capture/release.

Before you begin

Every endpoint on this page is a gRPC call secured with TLS. Authenticate each call by attaching your API key to the request metadata using the X-API-Key header.

Connect to the host closest to your store's region. See the Payments API Overview for the full list of regional hostnames (Asia-Pacific and Europe), authentication, shared conventions, and how to install the client SDK.

RegionDevelopment and TestLive
Asia-Pacificgrpc-staging-ap.kodypay.comgrpc-ap.kodypay.com
Europegrpc-staging-eu.kodypay.comgrpc-eu.kodypay.com

Use the regional staging host that matches your store in the examples below. Replace the following placeholders with your own values:

PlaceholderDescription
HOSTNAMEYour regional gRPC host, e.g. grpc-staging-eu.kodypay.com (staging) or grpc-eu.kodypay.com (live)
API_KEYYour Kody API key
STORE_IDYour Kody store identifier

Endpoints

#EndpointgRPC callDescription
1Initiate PaymentInitiatePaymentCreate a payment and return a hosted payment URL.
2Initiate Payment StreamInitiatePaymentStreamCreate a payment and stream status until it completes.
3Payment DetailsPaymentDetailsFetch the current status and details of a payment.
4Payment Details StreamPaymentDetailsStreamStream a payment's details until it completes.
5Get PaymentsGetPaymentsList payments with pagination and filters.
6Refund PaymentRefundRefund all or part of a payment.

All calls are methods on KodyEcomPaymentsService.

Payment flow

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;
}

enum PaymentStatus {
PENDING = 0;
SUCCESS = 1;
FAILED = 2;
CANCELLED = 3;
EXPIRED = 4;
}

// A refund's own lifecycle, which is not the payment's: the payment stays successful throughout.
message Refund {
enum Status {
UNSPECIFIED = 0; // Never sent deliberately; a legal fallback rather than an illegal one
PENDING = 1; // Not yet sent to the acquirer
REQUESTED = 2; // The acquirer accepted the refund request. Not yet money back
SUCCEEDED = 3; // The refund completed
FAILED = 4; // The refund did not happen and will not
CANCELLED = 5; // The refund was cancelled before it completed
}
}
PaymentStatus does not change when a refund is taken

A fully refunded payment still reports SUCCESS. A refund is a separate object with its own lifecycle, reported on PaymentDetails.refunds[].status using Refund.Status above.

Refund.Status is available from protocol v1.8.7.

1. Initiate Payment

Method: KodyEcomPaymentsService.InitiatePayment

Initiates an online payment and returns a payment_url for the customer to complete the transaction. After payment, the customer is redirected to the specified return_url.

Request

rpc InitiatePayment(PaymentInitiationRequest) returns (PaymentInitiationResponse);

message PaymentInitiationRequest {
string store_id = 1; // Your Kody store id
string payment_reference = 2; // Your unique reference for this payment request.
uint64 amount_minor_units = 3; // Amount in minor units. For example, 2000 means GBP 20.00.
string currency = 4; // ISO 4217 three letter currency code (e.g., GBP, HKD), must be the same as store currency
string order_id = 5; // Your identifier for the order. (May be reused if the same order has multiple payments)
optional string order_metadata = 6; // Additional order data (e.g., JSON with checkout items)
string return_url = 7; // URL to redirect after the payment is authorised
optional string payer_statement = 8; // Text for the payer's bank statement. Max 22 characters, otherwise banks might truncate the string. If not set, uses the store's terminal receipt printing name. Allowed characters: a-z, A-Z, 0-9, spaces, and special characters . , ' _ - ? + * /
optional string payer_email_address = 9; // Payer's email address (recommended for fraud and 3D Secure 2 checks)
optional string payer_ip_address = 10; // Payer's IP address (for risk analysis)
optional string payer_locale = 11; // Locale (e.g., en and zh) to display the payment pages in the desired language
optional bool tokenise_card = 12; // Flag to tokenise the card; defaults to false
optional ExpirySettings expiry = 13; // Settings for payment expiry

message ExpirySettings {
bool show_timer = 1; // Display a countdown timer on the payment page (default false)
uint64 expiring_seconds = 2; // Timeout duration in seconds (default: 1800 seconds)
}
}

Request fields

FieldTypeRequiredDescription
store_idstringYesYour Kody store ID.
payment_referencestringYesYour unique reference for this payment request.
amount_minor_unitsuint64YesAmount in minor units (e.g. 2000 = GBP 20.00).
currencystringYesISO 4217 currency code; must match the store currency.
order_idstringYesYour order identifier (may be reused across payments).
order_metadatastringNoAdditional order data (e.g. JSON of checkout items).
return_urlstringYesURL to redirect to after the payment is authorised.
payer_statementstringNoText for the payer's bank statement. Max 22 characters — longer values risk truncation by the bank. If not set, uses the store's terminal receipt printing name. Allowed characters: a-z, A-Z, 0-9, spaces, and . , ' _ - ? + * /.
payer_email_addressstringNoPayer email; recommended for fraud and 3DS2 checks.
payer_ip_addressstringNoPayer IP address, used for risk analysis.
payer_localestringNoLocale for the payment pages (e.g. en, zh).
tokenise_cardboolNoTokenise the card for reuse. Defaults to false.
expiryExpirySettingsNoPayment expiry / countdown timer settings.

Expiry settings (expiry)

FieldTypeRequiredDescription
show_timerboolNoDisplay a countdown timer on the payment page. Defaults to false.
expiring_secondsuint64NoTimeout duration in seconds. Defaults to 1800.

Response

message PaymentInitiationResponse {
oneof result {
Response response = 1;
Error error = 2;
}

message Response {
string payment_id = 1; // The unique identifier created by Kody
string payment_url = 2; // URL for the customer to complete the payment
}

message Error {
Type type = 1;
string message = 2;

enum Type {
UNKNOWN = 0;
DUPLICATE_ATTEMPT = 1;
INVALID_REQUEST = 2;
}
}
}

Examples

Show code examples (Java · Python · .NET · PHP)
package com.kody;

import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.PaymentInitiationRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.util.UUID;

public class InitiateEcomPaymentExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";

public static void main(String[] args) {
String storeId = "STORE_ID";
initiateEcomPayment(storeId);
}

public static void initiateEcomPayment(String storeId) {
// Create a managed channel and attach the API key metadata.
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var paymentClient = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));

String orderId = "order_" + UUID.randomUUID();
String paymentReference = "pay_" + UUID.randomUUID();

PaymentInitiationRequest request = PaymentInitiationRequest.newBuilder()
.setStoreId(storeId)
.setPaymentReference(paymentReference)
.setAmountMinorUnits(1000) // e.g., 1000 for £10.00
.setCurrency("GBP")
.setOrderId(orderId)
.setReturnUrl("https://your-return-url.com")
// Optionally, set additional fields:
.setPayerEmailAddress("customer@example.com")
.build();

var response = paymentClient.initiatePayment(request);
if (response.hasResponse()) {
System.out.println("Payment URL: " + response.getResponse().getPaymentUrl());
System.out.println("Payment ID: " + response.getResponse().getPaymentId());
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}

2. Initiate Payment Stream

Method: KodyEcomPaymentsService.InitiatePaymentStream

Initiates an online payment and streams status updates until the transaction completes. The first (interim) message carries the hosted payment page link at payment_data.payment_wallet.payment_link_id — redirect the customer there to pay. The stream reuses PaymentDetailsResponse, so there is no top-level payment_url field. After payment, the customer is redirected to the specified return_url.

Request

rpc InitiatePaymentStream(PaymentInitiationRequest) returns (stream PaymentDetailsResponse);

message PaymentInitiationRequest {
string store_id = 1; // Your Kody store id
string payment_reference = 2; // Your unique reference for this payment request.
uint64 amount_minor_units = 3; // Amount in minor units. For example, 2000 means GBP 20.00.
string currency = 4; // ISO 4217 three letter currency code (e.g., GBP, HKD)
string order_id = 5; // Your identifier for the order. (May be reused if the same order has multiple payments)
optional string order_metadata = 6; // Additional order data (e.g., JSON with checkout items)
string return_url = 7; // URL to redirect after the payment is authorised
optional string payer_statement = 8; // Text for the payer's bank statement. Max 22 characters, otherwise banks might truncate the string. If not set, uses the store's terminal receipt printing name. Allowed characters: a-z, A-Z, 0-9, spaces, and special characters . , ' _ - ? + * /
optional string payer_email_address = 9; // Payer's email address (recommended for fraud and 3D Secure 2 checks)
optional string payer_ip_address = 10; // Payer's IP address (for risk analysis)
optional string payer_locale = 11; // Locale (e.g., en, zh) to display the payment pages in the desired language
optional bool tokenise_card = 12; // Flag to tokenise the card; defaults to false
optional ExpirySettings expiry = 13; // Settings for payment expiry

message ExpirySettings {
bool show_timer = 1; // Display a countdown timer on the payment page (default false)
uint64 expiring_seconds = 2; // Timeout duration in seconds (default: 1800 seconds)
}
}

Request fields

FieldTypeRequiredDescription
store_idstringYesYour Kody store ID.
payment_referencestringYesYour unique reference for this payment request.
amount_minor_unitsuint64YesAmount in minor units (e.g. 2000 = GBP 20.00).
currencystringYesISO 4217 currency code.
order_idstringYesYour order identifier (may be reused across payments).
order_metadatastringNoAdditional order data (e.g. JSON of checkout items).
return_urlstringYesURL to redirect to after the payment is authorised.
payer_statementstringNoText for the payer's bank statement. Max 22 characters — longer values risk truncation by the bank. If not set, uses the store's terminal receipt printing name. Allowed characters: a-z, A-Z, 0-9, spaces, and . , ' _ - ? + * /.
payer_email_addressstringNoPayer email; recommended for fraud and 3DS2 checks.
payer_ip_addressstringNoPayer IP address, used for risk analysis.
payer_localestringNoLocale for the payment pages (e.g. en, zh).
tokenise_cardboolNoTokenise the card for reuse. Defaults to false.
expiryExpirySettingsNoPayment expiry / countdown timer settings.

Response

message PaymentDetailsResponse {
oneof result {
PaymentDetails response = 1;
Error error = 2;
}

message PaymentDetails {
string payment_id = 1; // Kody-generated payment identifier
PaymentStatus status = 5;
google.protobuf.Timestamp date_created = 7;

// Detailed payment data (if available)
optional PaymentData payment_data = 11;
// Sale-related data (if applicable)
optional SaleData sale_data = 12;

// Refund fields — available from protocol v1.8.7
repeated RefundDetails refunds = 13; // Every refund taken against this payment, oldest first
uint64 amount_refunded_minor_units = 14; // Refunded so far, counting only refunds that succeeded
bool fully_refunded = 15; // True only when the whole payment has been refunded
}

// One refund taken against the payment. Available from protocol v1.8.7.
message RefundDetails {
string payment_transaction_id = 1; // This refund's own id. Use it to identify this refund
optional string refund_psp_reference = 2; // Absent until the acquirer has accepted the refund
string refund_amount = 3; // BigDecimal/2.dp (e.g. "10.00")
google.protobuf.Timestamp event_date = 4;
Refund.Status status = 5; // What this refund is doing — see Common enums
}

message Error {
Type type = 1;
string message = 2;

enum Type {
UNKNOWN = 0;
NOT_FOUND = 1;
INVALID_REQUEST = 2;
}
}
}

// PaymentData contains detailed information about the payment method and authorisation
message PaymentData {
string psp_reference = 1; // Payment service provider reference
optional PaymentMethods payment_method = 2; // Payment method (VISA, MASTERCARD, etc.). Explicit presence: VISA is the zero value, so check presence before reading.
string payment_method_variant = 3; // Variant of the payment method
PaymentAuthStatus auth_status = 4; // Authorisation status
google.protobuf.Timestamp auth_status_date = 5; // Date/time of the auth status change

enum PaymentAuthStatus {
PENDING = 0;
AUTHORISED = 1;
FAILED = 2;
CAPTURED = 3;
RELEASED = 4;
EXPIRED = 5;
}

oneof payment_method_details {
PaymentCard payment_card = 6; // Card payment details
PaymentWallet payment_wallet = 7; // Digital wallet payment details
}

message PaymentCard {
string card_last_4_digits = 1; // Last 4 digits of the card
string auth_code = 2; // Authorisation code
string payment_token = 3; // Card token (if tokenisation was requested)
}

message PaymentWallet {
optional string card_last_4_digits = 1; // Last 4 digits (if available)
string payment_link_id = 2; // Wallet payment link identifier
}
}

// SaleData contains information about the sale/order
message SaleData {
uint64 amount_minor_units = 1; // Amount in minor units (e.g., 2000 for £20.00)
string currency = 2; // ISO 4217 currency code
string order_id = 3; // Your order identifier
string payment_reference = 4; // Your payment reference
optional string order_metadata = 5; // Additional order data
}

Examples

Show code examples (Java · Python · .NET · PHP)
package com.kody;

import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.PaymentInitiationRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.util.UUID;

public class InitiatePaymentStreamExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";

public static void main(String[] args) {
String storeId = "STORE_ID";
initiateEcomPaymentStream(storeId);
}

public static void initiateEcomPaymentStream(String storeId) {
// Create a managed channel and attach the API key metadata.
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var paymentClient = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));

String orderId = "order_" + UUID.randomUUID();
String paymentReference = "pay_" + UUID.randomUUID();

PaymentInitiationRequest request = PaymentInitiationRequest.newBuilder()
.setStoreId(storeId)
.setPaymentReference(paymentReference)
.setAmountMinorUnits(1000) // e.g., 1000 for £10.00
.setCurrency("GBP")
.setOrderId(orderId)
.setReturnUrl("https://your-return-url.com")
// Optionally, set additional fields:
.setPayerEmailAddress("customer@example.com")
.build();

// Since InitiatePaymentStream returns a stream, get the first response from the iterator
var response = paymentClient.initiatePaymentStream(request).next();

if (response.hasResponse()) {
var details = response.getResponse();
System.out.println("Payment ID: " + details.getPaymentId());
System.out.println("Status: " + details.getStatus());
// The stream reuses PaymentDetailsResponse; the hosted payment page
// link is returned via payment_data.payment_wallet.payment_link_id.
if (details.hasPaymentData() && details.getPaymentData().hasPaymentWallet()) {
System.out.println("Payment page: " + details.getPaymentData().getPaymentWallet().getPaymentLinkId());
}
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}

3. Payment Details

Method: KodyEcomPaymentsService.PaymentDetails

Retrieves details of a specific payment using either the payment_id or the payment_reference.

Best practice

Poll every 2–5 seconds to avoid excessive load while still getting timely status updates.

Request

rpc PaymentDetails(PaymentDetailsRequest) returns (PaymentDetailsResponse);

message PaymentDetailsRequest {
string store_id = 1; // Your Kody store id
oneof payment_identifier {
string payment_id = 2; // Kody-generated payment identifier
string payment_reference = 3; // Your unique payment reference from initiation
}
}

Request fields

FieldTypeRequiredDescription
store_idstringYesYour Kody store ID.
payment_idstringOne ofKody-generated payment identifier.
payment_referencestringOne ofYour payment reference from initiation.

Response

message PaymentDetailsResponse {
oneof result {
PaymentDetails response = 1;
Error error = 2;
}

message PaymentDetails {
string payment_id = 1; // Kody-generated payment identifier
PaymentStatus status = 5;
google.protobuf.Timestamp date_created = 7;

// Detailed payment data (if available)
optional PaymentData payment_data = 11;
// Sale-related data (if applicable)
optional SaleData sale_data = 12;

// Refund fields — available from protocol v1.8.7
repeated RefundDetails refunds = 13; // Every refund taken against this payment, oldest first
uint64 amount_refunded_minor_units = 14; // Refunded so far, counting only refunds that succeeded
bool fully_refunded = 15; // True only when the whole payment has been refunded
}

// One refund taken against the payment. Available from protocol v1.8.7.
message RefundDetails {
string payment_transaction_id = 1; // This refund's own id. Use it to identify this refund
optional string refund_psp_reference = 2; // Absent until the acquirer has accepted the refund
string refund_amount = 3; // BigDecimal/2.dp (e.g. "10.00")
google.protobuf.Timestamp event_date = 4;
Refund.Status status = 5; // What this refund is doing — see Common enums
}

message Error {
Type type = 1;
string message = 2;

enum Type {
UNKNOWN = 0;
NOT_FOUND = 1;
INVALID_REQUEST = 2;
}
}
}

// PaymentData contains detailed information about the payment method and authorisation
message PaymentData {
string psp_reference = 1; // Payment service provider reference
optional PaymentMethods payment_method = 2; // Payment method (VISA, MASTERCARD, etc.). Explicit presence: VISA is the zero value, so check presence before reading.
string payment_method_variant = 3; // Variant of the payment method
PaymentAuthStatus auth_status = 4; // Authorisation status
google.protobuf.Timestamp auth_status_date = 5; // Date/time of the auth status change

enum PaymentAuthStatus {
PENDING = 0;
AUTHORISED = 1;
FAILED = 2;
CAPTURED = 3;
RELEASED = 4;
EXPIRED = 5;
}

oneof payment_method_details {
PaymentCard payment_card = 6; // Card payment details
PaymentWallet payment_wallet = 7; // Digital wallet payment details
}

message PaymentCard {
string card_last_4_digits = 1; // Last 4 digits of the card
string auth_code = 2; // Authorisation code
string payment_token = 3; // Card token (if tokenisation was requested)
}

message PaymentWallet {
optional string card_last_4_digits = 1; // Last 4 digits (if available)
string payment_link_id = 2; // Wallet payment link identifier
}
}

// SaleData contains information about the sale/order
message SaleData {
uint64 amount_minor_units = 1; // Amount in minor units (e.g., 2000 for £20.00)
string currency = 2; // ISO 4217 currency code
string order_id = 3; // Your order identifier
string payment_reference = 4; // Your payment reference
optional string order_metadata = 5; // Additional order data
}

Reading the refund fields

Refunds are asynchronous: the acquirer confirms the outcome after the refund request has returned. These fields are how you observe that outcome — poll this endpoint until each refund reaches a terminal status.

To find outRead
Whether any money has been returnedamount_refunded_minor_units
Whether the payment is fully refundedfully_refunded
What happened to an individual refundrefunds[].status

amount_refunded_minor_units counts only refunds that succeeded. A refund that is still pending, or that failed, has moved no money and is not included — so the total never overstates what the payer has received back. Both aggregates are derived from the rows in refunds, so they cannot disagree with the list.

Branch on status. Do not infer the outcome from whether refund_psp_reference is set: that tells you the acquirer accepted the request, not that the money has moved, and it is absent until then.

Refund fields — available from protocol v1.8.7

refunds, amount_refunded_minor_units, fully_refunded, the RefundDetails message and Refund.Status were all added in protocol v1.8.7. See Protocol versions and SDK releases for what that means for your language.

Only Payment Details reports these fields

PaymentDetails is on the PaymentDetails message, which several endpoints return — but only Payment Details and Payment Details Stream populate the refund fields. Get Payments and Get Token Payment Details return the same message with the refund fields left unset.

Do not read the refund fields from those endpoints — a fully refunded payment will look like one that was never refunded. Use Get Payments to find payments, then call Payment Details for each one whose refund state you need.

On protocol v1.8.7 the two aggregates have no presence, so an endpoint that does not report them returns 0 and false and you cannot tell that apart from a payment with nothing refunded. They are optional from the next protocol version, so a newer client can check presence — but refunds is a repeated field and has no presence either way, so the rule above still stands.

Examples

Show code examples (Java · Python · .NET · PHP)
package com.kody;

import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.PaymentDetailsRequest;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse.PaymentDetails;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse.PaymentData;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse.SaleData;
import com.kodypay.grpc.ecom.v1.PaymentStatus;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse.PaymentData.PaymentCard;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse.PaymentData.PaymentWallet;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse.PaymentData.PaymentMethodDetailsCase;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

public class GetPaymentDetailsExample {
public static PaymentDetails getPaymentDetails(String storeId, String paymentId) throws InterruptedException {
ManagedChannel channel = ManagedChannelBuilder.forAddress("HOSTNAME", 443)
.useTransportSecurity()
.build();

Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), "API_KEY");

KodyEcomPaymentsServiceGrpc.KodyEcomPaymentsServiceBlockingStub client =
KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));

PaymentDetailsRequest request = PaymentDetailsRequest.newBuilder()
.setStoreId(storeId)
.setPaymentId(paymentId)
.build();

PaymentDetails details;
PaymentStatus status;

do {
PaymentDetailsResponse response = client.paymentDetails(request);
if (!response.hasResponse()) {
throw new RuntimeException("Error: " + response.getError().getMessage());
}

details = response.getResponse();
status = details.getStatus();
System.out.println("Current Status: " + status);

if (status == PaymentStatus.PENDING) {
Thread.sleep(2000);
}
} while (status == PaymentStatus.PENDING);

channel.shutdownNow();
return details;
}

public static void main(String[] args) throws InterruptedException {
String storeId = "STORE_ID";
String paymentId = "PAYMENT_ID";
PaymentDetails details = getPaymentDetails(storeId, paymentId);
printPaymentDetails(details);
}

public static void printPaymentDetails(PaymentDetails details) {
if (details.hasPaymentData()) {
PaymentData paymentData = details.getPaymentData();
System.out.println("PSP Reference: " + paymentData.getPspReference());
System.out.println("Payment Method: " + paymentData.getPaymentMethod());
System.out.println("Auth Status: " + paymentData.getAuthStatus());

PaymentMethodDetailsCase methodCase = paymentData.getPaymentMethodDetailsCase();
switch (methodCase) {
case PAYMENT_CARD -> {
PaymentCard card = paymentData.getPaymentCard();
System.out.println("Card Last 4: " + card.getCardLast4Digits());
System.out.println("Auth Code: " + card.getAuthCode());
System.out.println("Payment Token: " + card.getPaymentToken());
}
case PAYMENT_WALLET -> {
PaymentWallet wallet = paymentData.getPaymentWallet();
if (wallet.hasCardLast4Digits()) {
System.out.println("Wallet Card Last 4: " + wallet.getCardLast4Digits());
}
System.out.println("Payment Link ID: " + wallet.getPaymentLinkId());
}
default -> System.out.println("Unknown payment method details.");
}
}

if (details.hasSaleData()) {
SaleData sale = details.getSaleData();
System.out.println("Amount: " + sale.getAmountMinorUnits());
System.out.println("Currency: " + sale.getCurrency());
System.out.println("Order ID: " + sale.getOrderId());
System.out.println("Payment Reference: " + sale.getPaymentReference());
if (sale.hasOrderMetadata()) {
System.out.println("Order Metadata: " + sale.getOrderMetadata());
}
}
}
}

4. Payment Details Stream

Method: KodyEcomPaymentsService.PaymentDetailsStream

Retrieves details of a specific payment using either the payment_id or the payment_reference. Streams updates until the transaction reaches a terminal state, then returns the final result.

Request

rpc PaymentDetailsStream(PaymentDetailsRequest) returns (stream  PaymentDetailsResponse);

message PaymentDetailsRequest {
string store_id = 1; // Your Kody store id
oneof payment_identifier {
string payment_id = 2; // Kody-generated payment identifier
string payment_reference = 3; // Your unique payment reference from initiation
}
}

Request fields

FieldTypeRequiredDescription
store_idstringYesYour Kody store ID.
payment_idstringOne ofKody-generated payment identifier.
payment_referencestringOne ofYour payment reference from initiation.

Response

message PaymentDetailsResponse {
oneof result {
PaymentDetails response = 1;
Error error = 2;
}

message PaymentDetails {
string payment_id = 1; // Kody-generated payment identifier
PaymentStatus status = 5;
google.protobuf.Timestamp date_created = 7;

// Detailed payment data (if available)
optional PaymentData payment_data = 11;
// Sale-related data (if applicable)
optional SaleData sale_data = 12;

// Refund fields — available from protocol v1.8.7
repeated RefundDetails refunds = 13; // Every refund taken against this payment, oldest first
uint64 amount_refunded_minor_units = 14; // Refunded so far, counting only refunds that succeeded
bool fully_refunded = 15; // True only when the whole payment has been refunded
}

// One refund taken against the payment. Available from protocol v1.8.7.
message RefundDetails {
string payment_transaction_id = 1; // This refund's own id. Use it to identify this refund
optional string refund_psp_reference = 2; // Absent until the acquirer has accepted the refund
string refund_amount = 3; // BigDecimal/2.dp (e.g. "10.00")
google.protobuf.Timestamp event_date = 4;
Refund.Status status = 5; // What this refund is doing — see Common enums
}

// PaymentData contains detailed information about the payment method and authorisation
message PaymentData {
string psp_reference = 1; // Payment service provider reference
optional PaymentMethods payment_method = 2; // Payment method (VISA, MASTERCARD, etc.). Explicit presence: VISA is the zero value, so check presence before reading.
string payment_method_variant = 3; // Variant of the payment method
PaymentAuthStatus auth_status = 4; // Authorisation status
google.protobuf.Timestamp auth_status_date = 5; // Date/time of the auth status change

enum PaymentAuthStatus {
PENDING = 0;
AUTHORISED = 1;
FAILED = 2;
CAPTURED = 3;
RELEASED = 4;
EXPIRED = 5;
}

oneof payment_method_details {
PaymentCard payment_card = 6; // Card payment details
PaymentWallet payment_wallet = 7; // Digital wallet payment details
}

message PaymentCard {
string card_last_4_digits = 1; // Last 4 digits of the card
string auth_code = 2; // Authorisation code
string payment_token = 3; // Card token (if tokenisation was requested)
}

message PaymentWallet {
optional string card_last_4_digits = 1; // Last 4 digits (if available)
string payment_link_id = 2; // Wallet payment link identifier
}
}

// SaleData contains information about the sale/order
message SaleData {
uint64 amount_minor_units = 1; // Amount in minor units (e.g., 2000 for £20.00)
string currency = 2; // ISO 4217 currency code
string order_id = 3; // Your order identifier
string payment_reference = 4; // Your payment reference
optional string order_metadata = 5; // Additional order data
}

message Error {
Type type = 1;
string message = 2;

enum Type {
UNKNOWN = 0;
NOT_FOUND = 1;
INVALID_REQUEST = 2;
}
}
}

Examples

Show code examples (Java · Python · .NET · PHP)
package com.kody;

import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.PaymentDetailsRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

public class GetPaymentDetailsExample {
public static void getPaymentDetails(String storeId, String paymentId) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("HOSTNAME", 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), "API_KEY");
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));

PaymentDetailsRequest request = PaymentDetailsRequest.newBuilder()
.setStoreId(storeId)
.setPaymentId(paymentId)
.build();

//Since PaymentDetailsStream returns a stream, get the first response from the iterator
var response = client.paymentDetailsStream(request).next();

if (response.hasResponse()) {
System.out.println("Status: " + response.getResponse().getStatus());
System.out.println("Payment ID: " + response.getResponse().getPaymentId());
// Additional fields (e.g. payment_data) can be inspected as needed
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}

public static void main(String[] args) {
getPaymentDetails("STORE_ID", "PAYMENT_ID");
}
}

5. Get Payments

Refund fields are not populated here

The PaymentDetails entries this endpoint returns carry the refund fields declared on the message, but this endpoint does not fill them in: refunds comes back empty, and the two aggregates come back unset whatever the payment's real refund state is. Call Payment Details for a payment whose refunds you need.

Method: KodyEcomPaymentsService.GetPayments

Retrieves a paginated list of payments with optional filters.

Request

message GetPaymentsRequest {
string store_id = 1;
PageCursor page_cursor = 2;
Filter filter = 3;

message PageCursor {
int64 page = 1;
int64 page_size = 2;
}

message Filter {
optional string order_id = 1;
optional google.protobuf.Timestamp created_before = 2;
}
}

Request fields

FieldTypeRequiredDescription
store_idstringYesYour Kody store ID.
page_cursorPageCursorYesPagination cursor: page and page_size.
filterFilterNoOptional filters: order_id, created_before.

Response

message GetPaymentsResponse {
oneof result {
Response response = 1;
Error error = 2;
}

message Response {
int64 total = 2;
repeated PaymentDetailsResponse.PaymentDetails payments = 3;
}

message Error {
Type type = 1;
string message = 2;

enum Type {
UNKNOWN = 0;
NOT_FOUND = 1;
INVALID_ARGUMENT = 2;
}
}
}

Examples

Show code examples (Java · Python · .NET · PHP)
package com.kody;

import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.GetPaymentsRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

public class GetPaymentsExample {
public static void getPayments(String storeId) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("HOSTNAME", 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), "API_KEY");
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));

var request = GetPaymentsRequest.newBuilder()
.setStoreId(storeId)
.setPageCursor(GetPaymentsRequest.PageCursor.newBuilder().setPage(0).setPageSize(10))
.build();

var response = client.getPayments(request);
if (response.hasResponse()) {
System.out.println("Total payments: " + response.getResponse().getTotal());
for (var payment : response.getResponse().getPaymentsList()) {
System.out.println("Payment ID: " + payment.getPaymentId());
System.out.println("Status: " + payment.getStatus());
System.out.println("Created: " + payment.getDateCreated());
}
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}

public static void main(String[] args) {
getPayments("STORE_ID");
}
}

6. Refund Payment

Method: KodyEcomPaymentsService.Refund

Issues a refund for a specific payment, either in full or in part. Identify the payment with its payment_id, and provide amount as a decimal string with two decimal places (e.g. "10.00").

psp_reference removed

As of 2026-09-05, refunding by psp_reference is no longer supported. A request that still sets it now carries no identifier and is rejected with INVALID_ARGUMENT rather than refunding anything. Identify the payment with payment_id.

Request

rpc Refund(RefundRequest) returns (stream RefundResponse);

message RefundRequest {
string store_id = 1; // UUID of store
// Refunding by psp_reference is no longer supported: removed 2026-09-05 after 90 days with no
// request using it. A request that still sets it now carries no identifier and is rejected with
// INVALID_ARGUMENT rather than refunding anything. Field 4 and the name stay reserved so neither
// can be reused for something else.
reserved 4;
reserved "psp_reference";
// Kept as a oneof with one member: exactly one identifier, and room for another later without
// changing the shape of the generated code.
oneof id {
string payment_id = 2; // Payment id created by wombat
}
string amount = 3; // amount in BigDecimal/2.dp (0.00)
optional string idempotency_uuid = 5; // Idempotency key to ensure the request is processed only once, generated by client. Without it a retry is indistinguishable from a second partial refund and is processed as one.
}

Request fields

FieldTypeRequiredDescription
store_idstringYesYour store UUID.
payment_idstringYesKody-generated payment ID.
amountstringYesRefund amount as a 2dp decimal string (e.g. "10.00").
idempotency_uuidstringNoClient-generated idempotency key. Without it, a retry is indistinguishable from a second partial refund and is processed as a second refund.

Response

message RefundResponse {
RefundStatus status = 1;
optional string failure_reason = 2; // Populated on failure
string payment_id = 3;
google.protobuf.Timestamp date_created = 4;
string total_paid_amount = 5;
string total_amount_refunded = 6;
string remaining_amount = 7;
string total_amount_requested = 8;
string paymentTransactionId = 9; // This refund's own id — not the payment's

enum RefundStatus {
PENDING = 0;
REQUESTED = 1; // The refund request was accepted. Not yet money back
FAILED = 2;
}
}

Response fields

FieldTypeDescription
statusRefundStatusWhether the refund request was accepted — see the warning below.
payment_idstringThe payment that was refunded.
paymentTransactionIdstringThis refund's own id. Use it to find this refund in PaymentDetails.refunds.
total_paid_amountstringThe payment's total.
total_amount_refundedstringRefunded so far against this payment.
remaining_amountstringStill refundable.
total_amount_requestedstringRequested against this payment.
failure_reasonstringPopulated on failure.
paymentTransactionId — meaning changed in protocol v1.8.6

Before v1.8.6 this field carried the payment's transaction id, which left the refund you had just created impossible to identify. From v1.8.6 it carries the refund's own id. If you stored this value as a payment identifier, revisit that assumption when you upgrade.

idempotency_uuid — available from protocol v1.8.6

Send one. Without it a network retry is indistinguishable from a second, deliberate partial refund and is processed as one. With it, a replay is recognised instead of taking a second refund.

status reports acceptance, not outcome

REQUESTED means Kody accepted the refund request. It does not mean the money has been returned — the acquirer offers no synchronous refund, so the outcome arrives afterwards. To learn what actually happened, read refunds[].status on Payment Details, matching on the paymentTransactionId returned here.

Examples

Show code examples (Java · Python · .NET · PHP)
package com.kody;

import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.RefundRequest;
import com.kodypay.grpc.ecom.v1.RefundResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;

public class RefundPaymentExample {
public static void main(String[] args) {
refundPayment("STORE_ID", "PAYMENT_ID");
}

public static void refundPayment(String storeId, String paymentId) {
ManagedChannel channel = ManagedChannelBuilder.forAddress("HOSTNAME", 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), "API_KEY");
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));

RefundRequest request = RefundRequest.newBuilder()
.setStoreId(storeId)
// Use payment_id (or alternatively setPspReference if available)
.setPaymentId(paymentId)
.setAmount("5.00") // Refund amount as a string (e.g., "5.00")
.build();

var responseIterator = client.refund(request);
while (responseIterator.hasNext()) {
RefundResponse response = responseIterator.next();
System.out.println("Refund Status: " + response.getStatus());
if (response.getStatus() == RefundResponse.RefundStatus.FAILED) {
System.err.println("Failure Reason: " + response.getFailureReason());
}
System.out.println("Total Amount Refunded: " + response.getTotalAmountRefunded());
}
channel.shutdownNow();
}
}

Need help?

For further support or more detailed information, contact the Kody Support team at support@kody.com.