Terminal Payments – In-Person Payments
The Terminal Payments service lets you integrate with Kody’s in-person payment terminals. You can retrieve a list of your terminals, send payment requests to a terminal, cancel payments in progress, retrieve payment details, issue refunds, and void payments.
Note: Every request requires an
X-API-Key
header with your API key.
Common Enums
enum PaymentStatus {
PENDING = 0;
SUCCESS = 1;
FAILED = 2;
CANCELLED = 3;
DECLINED = 4;
REFUND_PENDING = 5;
REFUND_REQUESTED = 6;
}
1. List of Terminals
Service Call: KodyPayTerminalService.Terminals
Retrieve 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
}
Response
message TerminalsResponse {
repeated Terminal terminals = 1;
}
message Terminal {
string terminal_id = 1; // Terminal serial number
bool online = 2; // Online status
}
Examples
- 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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
$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
Service Call: KodyPayTerminalService.Pay
Send a payment request to a terminal. The terminal will display 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 user
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
}
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 user to scan.
}
optional bool enable_pbb = 4; // Flag to enable the pay-by-bank option.
optional bool enable_mcc = 5; // Flag to enable multi-currency conversion.
}
enum PaymentMethodType {
CARD = 0;
E_WALLET = 1;
}
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; // Field to be able to differentiate cancelled from declined, for Oracle use, without breaking backwards compatibility.
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;
optional PaymentCard payment_card = 9;
optional string payment_method_variant = 10;
optional string refund_amount = 11;
message PaymentCard {
string card_last_4_digits = 1;
string card_expiry_date = 2;
string pos_entry_mode = 3;
string payment_token = 4;
string auth_code = 5;
}
}
message RefundDetails {
string payment_id = 1;
optional string refund_psp_reference = 2; // Might not have a PSP yet reference if pending
string payment_transaction_id = 3;
string refund_amount = 4;
google.protobuf.Timestamp event_date = 5;
optional string terminal_id = 6;
}
}
Examples
- 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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com: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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
$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
Service Call: KodyPayTerminalService.Cancel
Cancel 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
}
Response
message CancelResponse {
PaymentStatus status = 1; // Status of the cancellation
}
Examples
- 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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com: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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
$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
Service Call: KodyPayTerminalService.PaymentDetails
Retrieve details of a specific terminal payment using the store and order identifiers.
Request
rpc PaymentDetails(PaymentDetailsRequest) returns (PayResponse);
message PaymentDetailsRequest {
string store_id = 1; // UUID of store
string order_id = 2; // Order ID for the payment
}
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; // Field to be able to differentiate cancelled from declined, for Oracle use, without breaking backwards compatibility.
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;
optional PaymentCard payment_card = 9;
optional string payment_method_variant = 10;
optional string refund_amount = 11;
message PaymentCard {
string card_last_4_digits = 1;
string card_expiry_date = 2;
string pos_entry_mode = 3;
string payment_token = 4;
string auth_code = 5;
}
}
message RefundDetails {
string payment_id = 1;
optional string refund_psp_reference = 2; // Might not have a PSP yet reference if pending
string payment_transaction_id = 3;
string refund_amount = 4;
google.protobuf.Timestamp event_date = 5;
optional string terminal_id = 6;
}
}
Examples
- 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 PaymentDetailsExample {
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API_KEY";
public static void main(String[] args) {
String storeId = "STORE_ID";
String orderId = "ORDER_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 = "grpc-staging.kodypay.com:443"
api_key = "API_KEY"
store_id = "STORE_ID"
order_id = "ORDER_ID"
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 = "grpc-staging.kodypay.com";
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 = "ORDER_ID"
};
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 = "grpc-staging.kodypay.com";
$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('ORDER_ID');
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
Service Call: 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.
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;
}
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
Initiate a refund through the backend system without requiring a physical terminal. This method is useful for refunding payments 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_id
field in your request - Immediate processing - The refund is processed immediately and the result is returned in the response
Examples
- 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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com: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://grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
$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
Initiate 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
- Automatic Processing - The refund is processed without requiring any terminal interaction
- Receipt printing - The terminal will print a receipt according to the terminal's configured receipt settings
Examples
- 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 = "grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com: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://grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
$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
We support cross-store refunds, enabling merchants to process refunds across different store locations 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
Service Call: KodyPayTerminalService.Void
Cancel a payment after it has been processed.
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;
}
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
- 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 = "grpc-staging.kodypay.com";
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: Depending on your code generator, the method may be named `Void` or `VoidPayment`
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 = "grpc-staging.kodypay.com: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(address, 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://grpc-staging.kodypay.com";
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 = "grpc-staging.kodypay.com";
$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;
}
Remember to replace the following placeholders with your actual values:
HOSTNAME
: Use eithergrpc-staging.kodypay.com
(for development and test) orgrpc.kodypay.com
(for live).API_KEY
: Your API keySTORE_ID
: Your store identifierTERMINAL_ID
: Your terminal identifier
For further support or more detailed information, please contact our support team.