Terminal Payments
The Terminal Payments API lets you take in-person card payments on Kody's physical terminals. Use it to list your terminals, send a payment to a terminal, cancel a payment in progress, retrieve payment details, issue refunds (from the backend or a terminal), void a payment, and close batches.
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.
| Region | Development and Test | Live |
|---|---|---|
| Asia-Pacific | grpc-staging-ap.kodypay.com | grpc-ap.kodypay.com |
| Europe | grpc-staging-eu.kodypay.com | grpc-eu.kodypay.com |
Use the regional staging host that matches your store in the examples below. Replace the following placeholders with your own values:
| Placeholder | Description |
|---|---|
HOSTNAME | Your regional gRPC host, e.g. grpc-staging-eu.kodypay.com (staging) or grpc-eu.kodypay.com (live) |
API_KEY | Your Kody API key |
STORE_ID | Your Kody store identifier |
TERMINAL_ID | Your terminal serial number |
Endpoints
| # | Endpoint | gRPC call | Description |
|---|---|---|---|
| 1 | List of Terminals | Terminals | List your terminals and their online status. |
| 2 | Initiate Terminal Payment | Pay | Send a payment to a terminal (server-streaming). |
| 3 | Cancel Terminal Payment | Cancel | Cancel a payment in progress on a terminal. |
| 4 | Get Terminal Payment Details | PaymentDetails | Retrieve a terminal payment by order ID. |
| 5 | Refund Payment | Refund | Refund via the backend or a terminal (server-streaming). |
| 6 | Void Payment | Void | Void a processed payment before settlement. |
| 7 | Close Batch | CloseBatch | Close payment batches for one or all terminals. |
All calls are methods on KodyPayTerminalService.
Common enums
enum PaymentStatus {
PENDING = 0;
SUCCESS = 1;
FAILED = 2;
CANCELLED = 3;
DECLINED = 4;
REFUND_PENDING = 5;
REFUND_REQUESTED = 6;
}
1. List of Terminals
Method: KodyPayTerminalService.Terminals
Retrieves a list of all terminals associated with your store, along with their online status.
Request
rpc Terminals(TerminalsRequest) returns (TerminalsResponse);
message TerminalsRequest {
string store_id = 1; // UUID of store
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
store_id | string | Yes | UUID of the store. |
Response
message TerminalsResponse {
repeated Terminal terminals = 1;
}
message Terminal {
string terminal_id = 1; // Terminal serial number
bool online = 2; // Online status
}
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.TerminalsRequest;
import com.kodypay.grpc.pay.v1.TerminalsResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
public class ListTerminalsExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
var client = KodyPayTerminalServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
TerminalsRequest request = TerminalsRequest.newBuilder()
.setStoreId("STORE_ID")
.build();
TerminalsResponse response = client.terminals(request);
response.getTerminalsList().forEach(terminal -> {
System.out.println("Terminal ID: " + terminal.getTerminalId());
System.out.println("Online: " + terminal.getOnline());
});
}
}
import grpc
import kody_clientsdk_python.pay.v1.pay_pb2 as kody_model
import kody_clientsdk_python.pay.v1.pay_pb2_grpc as kody_client
def list_terminals():
channel = grpc.secure_channel("HOSTNAME:443", grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", "API_KEY")]
request = kody_model.TerminalsRequest(store_id="STORE_ID")
response = client.Terminals(request, metadata=metadata)
for terminal in response.terminals:
print(f"Terminal ID: {terminal.terminal_id}")
print(f"Online: {terminal.online}")
if __name__ == "__main__":
list_terminals()
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class Program
{
static async Task Main(string[] args)
{
var HOSTNAME = "HOSTNAME";
var API_KEY = "API_KEY";
var channel = GrpcChannel.ForAddress("https://" + HOSTNAME);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata
{
{ "X-API-Key", API_KEY }
};
var request = new TerminalsRequest { StoreId = "STORE_ID" };
var response = await client.TerminalsAsync(request, metadata);
foreach (var terminal in response.Terminals)
{
Console.WriteLine($"Terminal ID: {terminal.TerminalId}");
Console.WriteLine($"Online: {terminal.Online}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\TerminalsRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
$request = new TerminalsRequest();
$request->setStoreId('STORE_ID');
list($response, $status) = $client->Terminals($request, $metadata)->wait();
if ($status->code !== \Grpc\STATUS_OK) {
echo "Error: " . $status->details . PHP_EOL;
} else {
foreach ($response->getTerminals() as $terminal) {
echo "Terminal ID: " . $terminal->getTerminalId() . PHP_EOL;
echo "Online: " . ($terminal->getOnline() ? 'Yes' : 'No') . PHP_EOL;
}
}
2. Initiate Terminal Payment
Method: KodyPayTerminalService.Pay
Sends a payment request to a terminal. The terminal displays the payment screen (and, optionally, tip options) for an in-person transaction.
Request
rpc Pay(PayRequest) returns (stream PayResponse);
message PayRequest {
string store_id = 1; // UUID of store
string amount = 2; // Amount in BigDecimal/2.dp (e.g., "10.00")
string terminal_id = 3; // Terminal serial number
optional bool show_tips = 4; // Flag to display tips on the terminal
optional PaymentMethod payment_method = 5; // Specific payment method; if unset, the terminal will prompt the customer
optional string idempotency_uuid = 6; // UUID idempotency key (generated by client)
optional string payment_reference = 7; // Unique payment reference provided by client
optional string order_id = 8; // Unique order reference provided by client
repeated PayRequest.PaymentMethods accepts_only = 9; // Inclusion list of accepted payment methods. It is optional. If not specified, all payment methods enabled for the store will be accepted.
}
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;
}
message PaymentMethod {
PaymentMethodType payment_method_type = 1; // e.g., CARD, E_WALLET
oneof verification_mode {
string token = 2; // With a token provided, QR scanning is skipped and the terminal goes straight to the payment screen.
bool activate_qr_code_scanner = 3; // Set to true to activate the terminal camera to scan a customer's or Kody's QR Code; false (default) displays a QR code for the customer to scan.
}
}
enum PaymentMethodType {
CARD = 0;
E_WALLET = 1;
UNKNOWN = 2; // Response only — indicates an unrecognised payment method
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
store_id | string | Yes | UUID of the store. |
amount | string | Yes | Amount as a 2dp decimal string (e.g. "10.00"). |
terminal_id | string | Yes | Terminal serial number. |
show_tips | bool | No | Display tip options on the terminal. |
payment_method | PaymentMethod | No | Specific method; if unset, the terminal prompts the customer. |
idempotency_uuid | string | No | Client-generated idempotency key. |
payment_reference | string | No | Your unique payment reference. |
order_id | string | No | Your unique order reference. |
accepts_only | PaymentMethods[] | No | Allow-list of accepted methods; defaults to all enabled. |
Response
message PayResponse {
PaymentStatus status = 1;
optional string failure_reason = 2; // Populated on failure
string payment_id = 4;
google.protobuf.Timestamp date_created = 5;
optional PaymentData payment_data = 11;
optional string payment_reference = 12;
optional string order_id = 13;
repeated PayRequest.PaymentMethods accepts_only = 14;
optional bool is_payment_declined = 15; // true if the payment was declined rather than cancelled
repeated RefundDetails refunds = 16;
message PaymentData {
google.protobuf.Timestamp date_paid = 1;
string total_amount = 2;
string sale_amount = 3;
string tips_amount = 4;
string receipt_json = 5; // Receipt details in JSON format
string psp_reference = 6;
PaymentMethodType payment_method_type = 7;
string payment_method = 8; // Payment method. e.g., visa, mc, amex, etc.
optional PaymentCard payment_card = 9; // Deprecated.
optional string payment_method_variant = 10; // Payment method variant. e.g., mc, mcsuperpremiumcredit, visa, visadebit, visacredit, etc.
optional string original_currency = 12; // The currency that applies to total_amount, sale_amount, and tips_amount. Defined by the store’s configured settlement currency.
optional string paid_amount = 13; // The amount paid by the customer. This will differ from total_amount if the customer pays in their home currency.
optional string paid_currency = 14; // The currency in which the customer paid. This will differ from original_currency if the customer pays in their home currency.
optional string exchange_rate = 15; // The exchange rate between original_currency and paid_currency, calculated as total_amount / paid_amount. Precision: up to 6 decimal places.
optional string batch_id = 16; // The identifier of the payment batch on the terminal in which this payment was created. Applicable only to batch payments.
message PaymentCard {
string card_last_4_digits = 1;
string card_expiry_date = 2;
string pos_entry_mode = 3;
string payment_token = 4; // Deprecated — use the Tokenised Payments API instead.
string auth_code = 5;
optional string card_bin = 6; // The card’s Bank Identification Number (BIN), used to identify the issuing bank. Corresponds to the first 6 digits of the card number.
optional string card_issuing_bank = 7; // Issuing bank name, e.g., STANDARD CHARTERED BANK (HONG KONG) LTD
optional string funding_source = 8; // The funding source of the customer’s card, e.g., CREDIT, DEBIT, or PREPAID.
}
}
message RefundDetails {
string payment_id = 1;
optional string refund_psp_reference = 2; // PSP reference; may not be available yet if the refund is still pending
string payment_transaction_id = 3;
string refund_amount = 4;
google.protobuf.Timestamp event_date = 5;
optional string terminal_id = 6;
}
}
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.PayRequest;
import com.kodypay.grpc.pay.v1.PayResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
public class InitiateTerminalPaymentExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
// Replace with your store and terminal IDs
String storeId = "STORE_ID";
String terminalId = "TERMINAL_ID";
BigDecimal amount = new BigDecimal("10.00");
initiatePayment(storeId, terminalId, amount);
}
private static void initiatePayment(String storeId, String terminalId, BigDecimal amount) {
var client = createTerminalClient();
PayRequest request = PayRequest.newBuilder()
.setStoreId(storeId)
.setAmount(amount.toString())
.setTerminalId(terminalId)
.setShowTips(true)
// Optionally set payment_method, idempotency_uuid, payment_reference, order_id, etc.
.build();
// Since Pay returns a stream, get the first response from the iterator
PayResponse response = client.pay(request).next();
System.out.println("Payment ID: " + response.getPaymentId());
System.out.println("Status: " + response.getStatus());
// Additional fields (e.g. payment_data) can be inspected as needed
}
private static KodyPayTerminalServiceGrpc.KodyPayTerminalServiceBlockingStub createTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
return KodyPayTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}
}
from datetime import datetime
import grpc
import kody_clientsdk_python.pay.v1.pay_pb2 as kody_model
import kody_clientsdk_python.pay.v1.pay_pb2_grpc as kody_client
def initiate_terminal_payment():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
terminal_id = "TERMINAL_ID"
amount = "10.00"
# Optional: define a PaymentMethod, for example:
payment_method = kody_model.PaymentMethod(
payment_method_type=kody_model.PaymentMethodType.E_WALLET,
activate_qr_code_scanner=True
)
request = kody_model.PayRequest(
store_id=store_id,
amount=amount,
terminal_id=terminal_id,
show_tips=True,
payment_method=payment_method
)
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", api_key)]
response_iterator = client.Pay(request, metadata=metadata)
# Process the first response from the stream
for response in response_iterator:
print(f"Payment ID: {response.payment_id}")
print(f"Status: {kody_model.PaymentStatus.Name(response.status)}")
if response.failure_reason:
print(f"Failure Reason: {response.failure_reason}")
break
if __name__ == "__main__":
initiate_terminal_payment()
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class Program
{
static async Task Main(string[] args)
{
var HOSTNAME = "HOSTNAME";
var API_KEY = "API_KEY";
var channel = GrpcChannel.ForAddress("https://" + HOSTNAME);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", API_KEY } };
var request = new PayRequest
{
StoreId = "STORE_ID",
Amount = "10.00",
TerminalId = "TERMINAL_ID",
ShowTips = true
// Optionally set PaymentMethod, IdempotencyUuid, PaymentReference, OrderId, etc.
};
using var call = client.Pay(request, metadata);
if (await call.ResponseStream.MoveNext())
{
var response = call.ResponseStream.Current;
Console.WriteLine($"Payment ID: {response.PaymentId}");
Console.WriteLine($"Status: {response.Status}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\PayRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
$request = new PayRequest();
$request->setStoreId('STORE_ID');
$request->setAmount("10.00");
$request->setTerminalId('TERMINAL_ID');
$request->setShowTips(true);
// Optionally set payment_method, idempotency_uuid, payment_reference, order_id, etc.
$call = $client->Pay($request, $metadata);
foreach ($call->responses() as $response) {
echo "Payment ID: " . $response->getPaymentId() . PHP_EOL;
echo "Status: " . $response->getStatus() . PHP_EOL;
break; // Process only the first response
}
3. Cancel Terminal Payment
Method: KodyPayTerminalService.Cancel
Cancels a payment that is in progress on a terminal.
Request
rpc Cancel(CancelRequest) returns (CancelResponse);
message CancelRequest {
string store_id = 1; // UUID of store
string amount = 2; // Amount in BigDecimal/2.dp (must match the original request)
string terminal_id = 3; // Terminal serial number where the payment was sent
optional string payment_id = 4; // (Optional) Payment ID (order) to cancel
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
store_id | string | Yes | UUID of the store. |
amount | string | Yes | Amount as a 2dp decimal; must match the original request. |
terminal_id | string | Yes | Terminal serial number the payment was sent to. |
payment_id | string | No | Payment (order) ID to cancel. |
Response
message CancelResponse {
PaymentStatus status = 1; // Status of the payment after cancellation attempt, should be CANCELLED if the cancellation is successful.
}
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.pay.v1.CancelRequest;
import com.kodypay.grpc.pay.v1.CancelResponse;
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;
public class CancelTerminalPaymentExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
String storeId = "STORE_ID";
String terminalId = "TERMINAL_ID";
String paymentId = "PAYMENT_ID"; // Optional: include if available
BigDecimal amount = new BigDecimal("10.00");
cancelPayment(storeId, terminalId, paymentId, amount);
}
private static void cancelPayment(String storeId, String terminalId, String paymentId, BigDecimal amount) {
var client = createTerminalClient();
CancelRequest.Builder requestBuilder = CancelRequest.newBuilder()
.setStoreId(storeId)
.setTerminalId(terminalId)
.setAmount(amount.toString());
if (paymentId != null && !paymentId.isEmpty()) {
requestBuilder.setPaymentId(paymentId);
}
CancelResponse response = client.cancel(requestBuilder.build());
System.out.println("Cancel Status: " + response.getStatus());
}
private static KodyPayTerminalServiceGrpc.KodyPayTerminalServiceBlockingStub createTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
return KodyPayTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}
}
import grpc
import kody_clientsdk_python.pay.v1.pay_pb2 as kody_model
import kody_clientsdk_python.pay.v1.pay_pb2_grpc as kody_client
def cancel_terminal_payment():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
terminal_id = "TERMINAL_ID"
amount = "10.00"
# Optionally, set payment_id if available:
payment_id = "PAYMENT_ID" # or leave as empty string if not available
request = kody_model.CancelRequest(
store_id=store_id,
terminal_id=terminal_id,
amount=amount,
)
if payment_id:
request.payment_id = payment_id
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", api_key)]
response = client.Cancel(request, metadata=metadata)
print(f"Cancel Status: {response.status}")
if __name__ == "__main__":
cancel_terminal_payment()
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class CancelTerminalPaymentExample
{
public static async Task Main(string[] args)
{
var HOSTNAME = "HOSTNAME";
var API_KEY = "API_KEY";
// Create a secure channel to the host
var channel = GrpcChannel.ForAddress("https://" + HOSTNAME);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
// Add API key to metadata
var metadata = new Metadata { { "X-API-Key", API_KEY } };
// Build the cancel request
var request = new CancelRequest
{
StoreId = "STORE_ID",
TerminalId = "TERMINAL_ID",
Amount = "10.00"
};
// Optionally, set PaymentId if available
request.PaymentId = "PAYMENT_ID";
// Send the cancel request asynchronously and await the response
var response = await client.CancelAsync(request, metadata);
Console.WriteLine($"Cancel Status: {response.Status}");
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\CancelRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
$request = new CancelRequest();
$request->setStoreId('STORE_ID');
$request->setTerminalId('TERMINAL_ID');
$request->setAmount("10.00");
// Optionally set payment_id if available:
// $request->setPaymentId('PAYMENT_ID');
list($response, $status) = $client->Cancel($request, $metadata)->wait();
if ($status->code !== \Grpc\STATUS_OK) {
echo "Error: " . $status->details . PHP_EOL;
} else {
echo "Cancel Status: " . $response->getStatus() . PHP_EOL;
}
4. Get Terminal Payment Details
Method: KodyPayTerminalService.PaymentDetails
Retrieves the details of a specific terminal payment. Identify the payment by passing the payment_id returned by the Pay response in the order_id field.
The order_id field must be set to the payment_id returned by the Pay response — not a client-supplied order reference.
Request
rpc PaymentDetails(PaymentDetailsRequest) returns (PayResponse);
message PaymentDetailsRequest {
string store_id = 1; // UUID of store
string order_id = 2; // Set to the payment_id returned by the Pay response
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
store_id | string | Yes | UUID of the store. |
order_id | string | Yes | The payment_id returned by the Pay response (passed in the order_id field). |
Response
message PayResponse {
PaymentStatus status = 1;
optional string failure_reason = 2; // Populated on failure
string payment_id = 4;
google.protobuf.Timestamp date_created = 5;
optional PaymentData payment_data = 11;
optional string payment_reference = 12;
optional string order_id = 13;
repeated PayRequest.PaymentMethods accepts_only = 14;
optional bool is_payment_declined = 15; // true if the payment was declined rather than cancelled
repeated RefundDetails refunds = 16;
message PaymentData {
google.protobuf.Timestamp date_paid = 1;
string total_amount = 2;
string sale_amount = 3;
string tips_amount = 4;
string receipt_json = 5; // Receipt details in JSON format
string psp_reference = 6;
PaymentMethodType payment_method_type = 7;
string payment_method = 8; // Payment method. e.g., visa, mc, amex, etc.
optional PaymentCard payment_card = 9; // Deprecated.
optional string payment_method_variant = 10; // Payment method variant. e.g., mc, mcsuperpremiumcredit, visa, visadebit, visacredit, etc.
optional string original_currency = 12; // The currency that applies to total_amount, sale_amount, and tips_amount. Defined by the store’s configured settlement currency.
optional string paid_amount = 13; // The amount paid by the customer. This will differ from total_amount if the customer pays in their home currency.
optional string paid_currency = 14; // The currency in which the customer paid. This will differ from original_currency if the customer pays in their home currency.
optional string exchange_rate = 15; // The exchange rate between original_currency and paid_currency, calculated as total_amount / paid_amount. Precision: up to 6 decimal places.
optional string batch_id = 16; // The identifier of the payment batch on the terminal in which this payment was created. Applicable only to batch payments.
message PaymentCard {
string card_last_4_digits = 1;
string card_expiry_date = 2;
string pos_entry_mode = 3;
string payment_token = 4; // Deprecated — use the Tokenised Payments API instead.
string auth_code = 5;
optional string card_bin = 6; // The card's Bank Identification Number (BIN), used to identify the issuing bank. Corresponds to the first 6 digits of the card number.
optional string card_issuing_bank = 7; // Issuing bank name, e.g., STANDARD CHARTERED BANK (HONG KONG) LTD
optional string funding_source = 8; // The funding source of the customer's card, e.g., CREDIT, DEBIT, or PREPAID.
}
}
message RefundDetails {
string payment_id = 1;
optional string refund_psp_reference = 2; // PSP reference; may not be available yet if the refund is still pending
string payment_transaction_id = 3;
string refund_amount = 4;
google.protobuf.Timestamp event_date = 5;
optional string terminal_id = 6;
}
}
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.PaymentDetailsRequest;
import com.kodypay.grpc.pay.v1.PayResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class GetPaymentDetailsExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
String storeId = "STORE_ID";
// Use the payment_id returned by the Pay response
String orderId = "PAYMENT_ID";
getPaymentDetails(storeId, orderId);
}
private static void getPaymentDetails(String storeId, String orderId) {
var client = createTerminalClient();
PaymentDetailsRequest request = PaymentDetailsRequest.newBuilder()
.setStoreId(storeId)
.setOrderId(orderId)
.build();
PayResponse response = client.paymentDetails(request);
System.out.println("Payment ID: " + response.getPaymentId());
System.out.println("Status: " + response.getStatus());
System.out.println("Order ID: " + response.getOrderId());
System.out.println("Created: " + new Date(response.getDateCreated().getSeconds() * 1000L));
// Inspect additional fields as needed (e.g., receipt_json, payment_data, etc.)
}
private static KodyPayTerminalServiceGrpc.KodyPayTerminalServiceBlockingStub createTerminalClient() {
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
return KodyPayTerminalServiceGrpc.newBlockingStub(ManagedChannelBuilder
.forAddress(HOSTNAME, 443)
.idleTimeout(3, TimeUnit.MINUTES)
.keepAliveTimeout(3, TimeUnit.MINUTES)
.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata))
.build());
}
}
from datetime import datetime
import grpc
import kody_clientsdk_python.pay.v1.pay_pb2 as kody_model
import kody_clientsdk_python.pay.v1.pay_pb2_grpc as kody_client
def get_terminal_payment_details():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
order_id = "PAYMENT_ID" # the payment_id returned by the Pay response
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", api_key)]
request = kody_model.PaymentDetailsRequest(store_id=store_id, order_id=order_id)
response = client.PaymentDetails(request, metadata=metadata)
print(f"Payment ID: {response.payment_id}")
print(f"Status: {kody_model.PaymentStatus.Name(response.status)}")
print(f"Order ID: {response.order_id}")
print(f"Created: {datetime.fromtimestamp(response.date_created.seconds)}")
if response.failure_reason:
print(f"Failure Reason: {response.failure_reason}")
if __name__ == "__main__":
get_terminal_payment_details()
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class Program
{
static async Task Main(string[] args)
{
var HOSTNAME = "HOSTNAME";
var API_KEY = "API_KEY";
var channel = GrpcChannel.ForAddress("https://" + HOSTNAME);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", API_KEY } };
var request = new PaymentDetailsRequest
{
StoreId = "STORE_ID",
OrderId = "PAYMENT_ID" // the payment_id returned by the Pay response
};
var response = await client.PaymentDetailsAsync(request, metadata);
Console.WriteLine($"Payment ID: {response.PaymentId}");
Console.WriteLine($"Status: {response.Status}");
Console.WriteLine($"Order ID: {response.OrderId}");
Console.WriteLine($"Created: {response.DateCreated.ToDateTime():g}");
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\PaymentDetailsRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
$request = new PaymentDetailsRequest();
$request->setStoreId('STORE_ID');
$request->setOrderId('PAYMENT_ID'); // the payment_id returned by the Pay response
list($response, $status) = $client->PaymentDetails($request, $metadata)->wait();
if ($status->code !== \Grpc\STATUS_OK) {
echo "Error: " . $status->details . PHP_EOL;
} else {
echo "Payment ID: " . $response->getPaymentId() . PHP_EOL;
echo "Status: " . $response->getStatus() . PHP_EOL;
echo "Order ID: " . $response->getOrderId() . PHP_EOL;
}
5. Refund Payment
Method: KodyPayTerminalService.Refund
There are two ways to perform refunds:
- Backend Refund — Initiate a refund through the backend system. This method does not require a physical terminal.
- Terminal Refund — Initiate a refund through Kody's physical payment terminal. The terminal must be online at the time of the request. This method enables printing refund receipts directly from the terminal.
Refunds are initiated as a stream of responses.
For two-step payments (TSP), a payment can only be refunded after its batch has been closed. Close the batch first (see Close Batch), then initiate the refund.
Request
rpc Refund(RefundRequest) returns (stream RefundResponse);
message RefundRequest {
string store_id = 1; // UUID of store
string payment_id = 2; // Payment ID to refund
string amount = 3; // Refund amount (BigDecimal/2.dp, e.g., "5.00")
optional string idempotency_uuid = 4; // UUID idempotency key
optional string terminal_id = 5; // Terminal ID for processing terminal-based refunds
optional string ext_pay_reference = 6;
optional string ext_order_id = 7;
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
store_id | string | Yes | UUID of the store. |
payment_id | string | Yes | Payment ID to refund. |
amount | string | Yes | Refund amount as a 2dp decimal string (e.g. "5.00"). |
idempotency_uuid | string | No | Client-generated idempotency key. |
terminal_id | string | No | Terminal ID; include to process the refund on a terminal. |
ext_pay_reference | string | No | Your external payment reference. |
ext_order_id | string | No | Your external order ID. |
Response
message RefundResponse {
RefundStatus status = 1;
optional string failure_reason = 2;
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;
enum RefundStatus {
PENDING = 0;
REQUESTED = 1;
FAILED = 2;
}
optional string order_id = 10;
optional string ext_pay_reference = 11;
optional string ext_order_id = 12;
}
5.1 Backend Refund
Initiates a refund through the backend system without requiring a physical terminal. This is useful when a terminal is not available or needed.
Key Points
- No terminal required — The refund is processed entirely through the backend
- Omit terminal_id — Simply do not include the
terminal_idfield in your request - Immediate processing — The refund is processed immediately and the result is returned in the response
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.RefundRequest;
import com.kodypay.grpc.pay.v1.RefundResponse;
import io.grpc.ManagedChannel;
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 BackendRefundExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
String storeId = "STORE_ID";
String paymentId = "PAYMENT_ID"; // Payment to refund
String refundAmount = "5.00";
String idempotencyKey = UUID.randomUUID().toString();
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);
KodyPayTerminalServiceGrpc.KodyPayTerminalServiceBlockingStub client =
KodyPayTerminalServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
// Create a backend refund request (no terminal_id specified)
RefundRequest refundRequest = RefundRequest.newBuilder()
.setStoreId(storeId)
.setPaymentId(paymentId)
.setAmount(refundAmount)
.setIdempotencyUuid(idempotencyKey)
.build();
Iterator<RefundResponse> responses = client.refund(refundRequest);
while (responses.hasNext()) {
RefundResponse response = responses.next();
System.out.println("Backend Refund Response for Payment ID: " + response.getPaymentId());
System.out.println("Status: " + response.getStatus());
if (response.getStatus() == RefundResponse.RefundStatus.FAILED) {
System.out.println("Failure Reason: " + response.getFailureReason());
}
System.out.println("Total Amount Refunded: " + response.getTotalAmountRefunded());
}
channel.shutdownNow();
try {
channel.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import grpc
import uuid
from kody_clientsdk_python.pay.v1 import pay_pb2 as kody_model
from kody_clientsdk_python.pay.v1 import pay_pb2_grpc as kody_client
def backend_refund_payment():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
payment_id = "PAYMENT_ID" # Payment to refund
refund_amount = "5.00"
idempotency_uuid = str(uuid.uuid4())
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", api_key)]
# Create a backend refund request (no terminal_id specified)
refund_request = kody_model.RefundRequest(
store_id=store_id,
payment_id=payment_id,
amount=refund_amount,
idempotency_uuid=idempotency_uuid
)
responses = client.Refund(refund_request, metadata=metadata)
for response in responses:
print(f"Backend Refund Response for Payment ID: {response.payment_id}")
print(f"Status: {kody_model.RefundResponse.RefundStatus.Name(response.status)}")
if response.status == kody_model.RefundResponse.RefundStatus.FAILED:
print(f"Failure Reason: {response.failure_reason}")
print(f"Total Amount Refunded: {response.total_amount_refunded}")
if __name__ == "__main__":
backend_refund_payment()
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class BackendRefundExample
{
public static async Task Main(string[] args)
{
string hostname = "https://HOSTNAME";
string apiKey = "API_KEY";
string storeId = "STORE_ID";
string paymentId = "PAYMENT_ID"; // Payment to refund
string refundAmount = "5.00";
string idempotencyUuid = Guid.NewGuid().ToString();
using var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
// Create a backend refund request (no terminal_id specified)
var refundRequest = new RefundRequest
{
StoreId = storeId,
PaymentId = paymentId,
Amount = refundAmount,
IdempotencyUuid = idempotencyUuid
};
using var call = client.Refund(refundRequest, metadata);
while (await call.ResponseStream.MoveNext())
{
var response = call.ResponseStream.Current;
Console.WriteLine($"Backend Refund Response for Payment ID: {response.PaymentId}");
Console.WriteLine($"Status: {response.Status}");
if (response.Status == RefundResponse.Types.RefundStatus.Failed)
{
Console.WriteLine($"Failure Reason: {response.FailureReason}");
}
Console.WriteLine($"Total Amount Refunded: {response.TotalAmountRefunded}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\RefundRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
// Create a backend refund request (no terminal_id specified)
$refundRequest = new RefundRequest();
$refundRequest->setStoreId('STORE_ID');
$refundRequest->setPaymentId('PAYMENT_ID'); // Payment to refund
$refundRequest->setAmount("5.00");
// Optionally, set an idempotency UUID:
$refundRequest->setIdempotencyUuid(uuid_create(UUID_TYPE_RANDOM));
$call = $client->Refund($refundRequest, $metadata);
foreach ($call->responses() as $response) {
echo "Backend Refund Response for Payment ID: " . $response->getPaymentId() . PHP_EOL;
echo "Status: " . $response->getStatus() . PHP_EOL;
if ($response->getStatus() === \Com\Kodypay\Pay\V1\RefundResponse\RefundStatus::FAILED) {
echo "Failure Reason: " . $response->getFailureReason() . PHP_EOL;
}
echo "Total Amount Refunded: " . $response->getTotalAmountRefunded() . PHP_EOL;
}
5.2 Terminal Refund
Initiates a refund through a physical payment terminal. The terminal must be online and ready to process the refund request.
Key Points
- Terminal must be online — The specified terminal must be turned on and ready to accept the refund request
- Include terminal_id — Specify which terminal should process the refund
- Any terminal in the store — You can use any terminal belonging to the same store to process the refund, not just the one that processed the original payment
- Hands-free terminal — The refund is processed without requiring any interaction at the terminal
- Receipt printing — The terminal will print a receipt according to the terminal's configured receipt settings
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.RefundRequest;
import com.kodypay.grpc.pay.v1.RefundResponse;
import io.grpc.ManagedChannel;
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 TerminalRefundExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
String storeId = "STORE_ID";
String paymentId = "PAYMENT_ID"; // Payment to refund
String terminalId = "TERMINAL_ID"; // Terminal to process the refund
String refundAmount = "5.00";
String idempotencyKey = UUID.randomUUID().toString();
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);
KodyPayTerminalServiceGrpc.KodyPayTerminalServiceBlockingStub client =
KodyPayTerminalServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
// Create a terminal refund request (with terminal_id specified)
RefundRequest refundRequest = RefundRequest.newBuilder()
.setStoreId(storeId)
.setPaymentId(paymentId)
.setAmount(refundAmount)
.setIdempotencyUuid(idempotencyKey)
.setTerminalId(terminalId)
.build();
Iterator<RefundResponse> responses = client.refund(refundRequest);
while (responses.hasNext()) {
RefundResponse response = responses.next();
System.out.println("Terminal Refund Response for Payment ID: " + response.getPaymentId());
System.out.println("Status: " + response.getStatus());
if (response.getStatus() == RefundResponse.RefundStatus.FAILED) {
System.out.println("Failure Reason: " + response.getFailureReason());
}
System.out.println("Total Amount Refunded: " + response.getTotalAmountRefunded());
}
channel.shutdownNow();
try {
channel.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import grpc
import uuid
from kody_clientsdk_python.pay.v1 import pay_pb2 as kody_model
from kody_clientsdk_python.pay.v1 import pay_pb2_grpc as kody_client
def terminal_refund_payment():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
payment_id = "PAYMENT_ID" # Payment to refund
terminal_id = "TERMINAL_ID" # Terminal to process the refund
refund_amount = "5.00"
idempotency_uuid = str(uuid.uuid4())
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", api_key)]
# Create a terminal refund request (with terminal_id specified)
refund_request = kody_model.RefundRequest(
store_id=store_id,
payment_id=payment_id,
amount=refund_amount,
idempotency_uuid=idempotency_uuid,
terminal_id=terminal_id
)
responses = client.Refund(refund_request, metadata=metadata)
for response in responses:
print(f"Terminal Refund Response for Payment ID: {response.payment_id}")
print(f"Status: {kody_model.RefundResponse.RefundStatus.Name(response.status)}")
if response.status == kody_model.RefundResponse.RefundStatus.FAILED:
print(f"Failure Reason: {response.failure_reason}")
print(f"Total Amount Refunded: {response.total_amount_refunded}")
if __name__ == "__main__":
terminal_refund_payment()
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class TerminalRefundExample
{
public static async Task Main(string[] args)
{
string hostname = "https://HOSTNAME";
string apiKey = "API_KEY";
string storeId = "STORE_ID";
string paymentId = "PAYMENT_ID"; // Payment to refund
string terminalId = "TERMINAL_ID"; // Terminal to process the refund
string refundAmount = "5.00";
string idempotencyUuid = Guid.NewGuid().ToString();
using var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
// Create a terminal refund request (with terminal_id specified)
var refundRequest = new RefundRequest
{
StoreId = storeId,
PaymentId = paymentId,
Amount = refundAmount,
IdempotencyUuid = idempotencyUuid,
TerminalId = terminalId
};
using var call = client.Refund(refundRequest, metadata);
while (await call.ResponseStream.MoveNext())
{
var response = call.ResponseStream.Current;
Console.WriteLine($"Terminal Refund Response for Payment ID: {response.PaymentId}");
Console.WriteLine($"Status: {response.Status}");
if (response.Status == RefundResponse.Types.RefundStatus.Failed)
{
Console.WriteLine($"Failure Reason: {response.FailureReason}");
}
Console.WriteLine($"Total Amount Refunded: {response.TotalAmountRefunded}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\RefundRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
// Create a terminal refund request (with terminal_id specified)
$refundRequest = new RefundRequest();
$refundRequest->setStoreId('STORE_ID');
$refundRequest->setPaymentId('PAYMENT_ID'); // Payment to refund
$refundRequest->setAmount("5.00");
$refundRequest->setTerminalId('TERMINAL_ID'); // Terminal to process the refund
// Optionally, set an idempotency UUID:
$refundRequest->setIdempotencyUuid(uuid_create(UUID_TYPE_RANDOM));
$call = $client->Refund($refundRequest, $metadata);
foreach ($call->responses() as $response) {
echo "Terminal Refund Response for Payment ID: " . $response->getPaymentId() . PHP_EOL;
echo "Status: " . $response->getStatus() . PHP_EOL;
if ($response->getStatus() === \Com\Kodypay\Pay\V1\RefundResponse\RefundStatus::FAILED) {
echo "Failure Reason: " . $response->getFailureReason() . PHP_EOL;
}
echo "Total Amount Refunded: " . $response->getTotalAmountRefunded() . PHP_EOL;
}
5.3 Cross-Store Refund Support
Kody supports cross-store refunds, allowing you to process a refund from a different store than where the original payment occurred, provided both stores are within the same merchant account.
With this feature, a POS terminal in Store A can initiate a refund for a transaction that originally occurred in Store B, provided that the API key used has access to the relevant store.
Key Points
- Cross-store capability — Refunds can be performed from a different store than where the original payment occurred.
- API key permissions — The X-API-Key must have access rights to the store where the original transaction was processed.
- Same API interface — Cross-store refunds use the exact same request format and endpoints as same-store refunds. No changes to your integration are needed.
- Flexible refund origin — You may use either backend or terminal-based refunds for cross-store scenarios.
6. Void Payment
Method: KodyPayTerminalService.Void
Reverses a processed payment before it settles to the merchant account.
Request
rpc Void(VoidPaymentRequest) returns (VoidPaymentResponse);
message VoidPaymentRequest {
oneof ids {
string psp_reference = 1;
string payment_id = 2;
}
optional string payment_reference = 3;
optional string order_id = 4;
string store_id = 5;
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
psp_reference | string | One of | PSP reference of the payment to void. |
payment_id | string | One of | Kody payment ID of the payment to void. |
payment_reference | string | No | Your payment reference. |
order_id | string | No | Your order ID. |
store_id | string | Yes | UUID of the store. |
Response
message VoidPaymentResponse {
string psp_reference = 1;
string payment_id = 2;
VoidStatus status = 3;
optional SaleData sale_data = 4;
google.protobuf.Timestamp date_voided = 5;
enum VoidStatus {
PENDING = 0;
REQUESTED = 1;
VOIDED = 2;
FAILED = 3;
}
message SaleData {
string total_amount = 1;
string sale_amount = 2;
string tips_amount = 3;
string order_id = 4;
string payment_reference = 5;
}
}
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.VoidPaymentRequest;
import com.kodypay.grpc.pay.v1.VoidPaymentResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.util.concurrent.TimeUnit;
public class VoidPaymentExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
String storeId = "STORE_ID";
// Void by payment_id (alternatively, you can use psp_reference)
String paymentId = "PAYMENT_ID";
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);
KodyPayTerminalServiceGrpc.KodyPayTerminalServiceBlockingStub client =
KodyPayTerminalServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
VoidPaymentRequest voidRequest = VoidPaymentRequest.newBuilder()
.setStoreId(storeId)
.setPaymentId(paymentId)
.build();
// Note: `void` is a reserved word in Java, so the generated stub method is `void_()`
VoidPaymentResponse response = client.void_(voidRequest);
System.out.println("Void Payment Response:");
System.out.println("Payment ID: " + response.getPaymentId());
System.out.println("PSP Reference: " + response.getPspReference());
System.out.println("Status: " + response.getStatus());
if (response.hasSaleData()) {
System.out.println("Sale Total Amount: " + response.getSaleData().getTotalAmount());
}
System.out.println("Date Voided: " + response.getDateVoided());
channel.shutdownNow();
try {
channel.awaitTermination(5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import grpc
from kody_clientsdk_python.pay.v1 import pay_pb2 as kody_model
from kody_clientsdk_python.pay.v1 import pay_pb2_grpc as kody_client
def void_payment():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
# Void by payment_id (alternatively, use psp_reference)
payment_id = "PAYMENT_ID"
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", "API_KEY")]
void_request = kody_model.VoidPaymentRequest(
store_id=store_id,
payment_id=payment_id
# Optionally, set payment_reference and order_id if needed
)
response = client.Void(void_request, metadata=metadata)
print("Void Payment Response:")
print(f"Payment ID: {response.payment_id}")
print(f"PSP Reference: {response.psp_reference}")
print(f"Status: {response.status}")
if response.HasField("sale_data"):
print(f"Sale Total Amount: {response.sale_data.total_amount}")
print(f"Date Voided: {response.date_voided}")
if __name__ == "__main__":
void_payment()
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class VoidPaymentExample
{
public static async Task Main(string[] args)
{
string hostname = "https://HOSTNAME";
string apiKey = "API_KEY";
string storeId = "STORE_ID";
// Void by payment_id (alternatively, use psp_reference)
string paymentId = "PAYMENT_ID";
var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
var voidRequest = new VoidPaymentRequest
{
StoreId = storeId,
PaymentId = paymentId
// Optionally, set PaymentReference and OrderId if needed
};
var response = await client.VoidAsync(voidRequest, metadata);
Console.WriteLine("Void Payment Response:");
Console.WriteLine($"Payment ID: {response.PaymentId}");
Console.WriteLine($"PSP Reference: {response.PspReference}");
Console.WriteLine($"Status: {response.Status}");
if (response.SaleData != null)
{
Console.WriteLine($"Sale Total Amount: {response.SaleData.TotalAmount}");
}
Console.WriteLine($"Date Voided: {response.DateVoided.ToDateTime():g}");
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\VoidPaymentRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
$voidRequest = new VoidPaymentRequest();
$voidRequest->setStoreId('STORE_ID');
// Void by payment_id (alternatively, you can set psp_reference)
$voidRequest->setPaymentId('PAYMENT_ID');
list($response, $status) = $client->Void($voidRequest, $metadata)->wait();
if ($status->code !== \Grpc\STATUS_OK) {
echo "Error: " . $status->details . PHP_EOL;
} else {
echo "Void Payment Response:" . PHP_EOL;
echo "Payment ID: " . $response->getPaymentId() . PHP_EOL;
echo "PSP Reference: " . $response->getPspReference() . PHP_EOL;
echo "Status: " . $response->getStatus() . PHP_EOL;
if ($response->getSaleData()) {
echo "Sale Total Amount: " . $response->getSaleData()->getTotalAmount() . PHP_EOL;
}
echo "Date Voided: " . $response->getDateVoided() . PHP_EOL;
}
7. Close Batch
Method: KodyPayTerminalService.CloseBatch
Closes payment batches for one or all terminals in a store. You can target specific terminals or, by omitting terminal IDs, every terminal in the store.
Request
rpc CloseBatch(CloseBatchRequest) returns (CloseBatchResponse);
message CloseBatchRequest {
string store_id = 1; // Required: Store identifier
repeated string terminal_ids = 2; // Optional: Terminal identifiers. If empty, closes batches for all terminals in the store
}
Request fields
| Field | Type | Required | Description |
|---|---|---|---|
store_id | string | Yes | Store identifier. |
terminal_ids | string[] | No | Terminals to close; if empty, closes all in the store. |
Response
message CloseBatchResponse {
CloseBatchStatus status = 1; // Overall status: "success", "partial_success", "failure"
string message = 2; // Human-readable summary of the closure operation, e.g., "Successfully initiated closure for 6 batch(es)"
repeated CloseBatchResult results = 3; // Results for each batch closure attempt
enum CloseBatchStatus {
SUCCESS = 0; // All batch closures initiated successfully
PARTIAL_SUCCESS = 1; // Some batches initiated closure successfully, others failed
FAILURE = 2; // All batch closure attempts failed
}
}
message CloseBatchResult {
string terminal_id = 1; // Terminal that owned the batch
int32 batch_id = 2; // Batch identifier
bool success = 3; // Whether this closure initiation was successful
string message = 4; // Success or error message for this batch
}
Examples
Show code examples (Java · Python · .NET · PHP)
- Java
- Python
- .NET
- PHP
import com.kodypay.grpc.pay.v1.KodyPayTerminalServiceGrpc;
import com.kodypay.grpc.pay.v1.CloseBatchRequest;
import com.kodypay.grpc.pay.v1.CloseBatchResponse;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
public class CloseBatchExample {
public static final String HOSTNAME = "HOSTNAME";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
// Replace with your store ID
String storeId = "STORE_ID";
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
var client = KodyPayTerminalServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
// Close batches for all terminals in the store
CloseBatchRequest request = CloseBatchRequest.newBuilder()
.setStoreId(storeId)
.build();
// Or close batch for specific terminal(s)
// CloseBatchRequest request = CloseBatchRequest.newBuilder()
// .setStoreId(storeId)
// .addTerminalIds("TERMINAL_ID_1")
// .addTerminalIds("TERMINAL_ID_2")
// .build();
CloseBatchResponse response = client.closeBatch(request);
System.out.println("Overall Status: " + response.getStatus());
System.out.println("Message: " + response.getMessage());
for (var result : response.getResultsList()) {
System.out.println("Terminal ID: " + result.getTerminalId());
System.out.println("Batch ID: " + result.getBatchId());
System.out.println("Success: " + result.getSuccess());
System.out.println("Message: " + result.getMessage());
}
}
}
import grpc
import kody_clientsdk_python.pay.v1.pay_pb2 as kody_model
import kody_clientsdk_python.pay.v1.pay_pb2_grpc as kody_client
def close_batch():
hostname = "HOSTNAME:443"
api_key = "API_KEY"
store_id = "STORE_ID"
channel = grpc.secure_channel(hostname, grpc.ssl_channel_credentials())
client = kody_client.KodyPayTerminalServiceStub(channel)
metadata = [("x-api-key", api_key)]
# Close batches for all terminals in the store
request = kody_model.CloseBatchRequest(store_id=store_id)
# Or close batch for specific terminal(s)
# request = kody_model.CloseBatchRequest(
# store_id=store_id,
# terminal_ids=["TERMINAL_ID_1", "TERMINAL_ID_2"]
# )
response = client.CloseBatch(request, metadata=metadata)
print(f"Overall Status: {response.status}")
print(f"Message: {response.message}")
for result in response.results:
print(f"Terminal ID: {result.terminal_id}")
print(f"Batch ID: {result.batch_id}")
print(f"Success: {result.success}")
print(f"Message: {result.message}")
if __name__ == "__main__":
close_batch()
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Pay.V1;
class Program
{
static async Task Main(string[] args)
{
var HOSTNAME = "HOSTNAME";
var API_KEY = "API_KEY";
var channel = GrpcChannel.ForAddress("https://" + HOSTNAME);
var client = new KodyPayTerminalService.KodyPayTerminalServiceClient(channel);
var metadata = new Metadata
{
{ "X-API-Key", API_KEY }
};
// Close batches for all terminals in the store
var request = new CloseBatchRequest { StoreId = "STORE_ID" };
// Or close batch for specific terminal(s)
// var request = new CloseBatchRequest
// {
// StoreId = "STORE_ID"
// };
// request.TerminalIds.Add("TERMINAL_ID_1");
// request.TerminalIds.Add("TERMINAL_ID_2");
var response = await client.CloseBatchAsync(request, metadata);
Console.WriteLine($"Overall Status: {response.Status}");
Console.WriteLine($"Message: {response.Message}");
foreach (var result in response.Results)
{
Console.WriteLine($"Terminal ID: {result.TerminalId}");
Console.WriteLine($"Batch ID: {result.BatchId}");
Console.WriteLine($"Success: {result.Success}");
Console.WriteLine($"Message: {result.Message}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Pay\V1\KodyPayTerminalServiceClient;
use Com\Kodypay\Pay\V1\CloseBatchRequest;
use Grpc\ChannelCredentials;
$HOSTNAME = "HOSTNAME";
$API_KEY = "API_KEY";
$client = new KodyPayTerminalServiceClient($HOSTNAME, [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => [$API_KEY]];
// Close batches for all terminals in the store
$request = new CloseBatchRequest();
$request->setStoreId('STORE_ID');
// Or close batch for specific terminal(s)
// $request->setTerminalIds(['TERMINAL_ID_1', 'TERMINAL_ID_2']);
list($response, $status) = $client->CloseBatch($request, $metadata)->wait();
if ($status->code !== \Grpc\STATUS_OK) {
echo "Error: " . $status->details . PHP_EOL;
} else {
echo "Overall Status: " . $response->getStatus() . PHP_EOL;
echo "Message: " . $response->getMessage() . PHP_EOL;
foreach ($response->getResults() as $result) {
echo "Terminal ID: " . $result->getTerminalId() . PHP_EOL;
echo "Batch ID: " . $result->getBatchId() . PHP_EOL;
echo "Success: " . ($result->getSuccess() ? 'Yes' : 'No') . PHP_EOL;
echo "Message: " . $result->getMessage() . PHP_EOL;
}
}
Need help?
For further support or more detailed information, contact the Kody Support team at support@kody.com.