Tokenised Payments
The Tokenised Payments service enables secure storage and reuse of payment methods for e-commerce transactions. This API allows you to create, retrieve, manage, and process payments using stored card tokens, providing a seamless experience for returning customers while maintaining PCI compliance.
Note: Every service call requires an
X-API-Key
header with your API key.
Here's a sequence diagram illustrating the tokenized payment flow:
Common Enums
enum RecurringProcessingModel {
MODEL_UNSPECIFIED = 0; // Default value, not set
CARD_ON_FILE = 1; // Card on file model
SUBSCRIPTION = 2; // Subscription model
UNSCHEDULED_CARD_ON_FILE = 3; // Unscheduled card on file model
}
enum CardTokenStatus {
PENDING = 0;
FAILED = 1;
READY = 2;
DELETED = 3;
PENDING_DELETE = 4; // Token is in the process of being deleted
}
1. Get Card Token
Service Call: KodyEcomPaymentsService.GetCardToken
Retrieves details of a specific card token using either the token_id
or the token_reference
.
Request
rpc GetCardToken(GetCardTokenRequest) returns (GetCardTokenResponse);
message GetCardTokenRequest {
string store_id = 1; // Your Kody store id
oneof token_identifier {
string token_id = 2; // The unique identifier created by Kody
string token_reference = 3; // Your unique payment reference that was set during the initiation
}
}
Response
message GetCardTokenResponse {
oneof result {
Response response = 1;
Error error = 2;
}
message Response {
string token_id = 1; // id from the 0 amount Ecom payment used to tokenise the stored payment method.
optional string token_reference = 2; // the external payment reference associated with the stored payment method, if applicable
string payment_token = 3; // Unique identifier for the stored payment method as created by Kody, e.g. a token that can be used for future payments
string payer_reference = 4; // The payer for whom the token is created, e.g. user id or any unique identifier you use to track users
RecurringProcessingModel recurring_processing_model = 5; // Recurring processing model
CardTokenStatus status = 6;
google.protobuf.Timestamp created_at = 7; // Date when the token was created
PaymentMethods payment_method = 8; // Card brand, e.g. mc, visa and so on
string payment_method_variant = 9; // Card variant, e.g. mccredit, mcdebit, visa, visadebit, etc.
string funding_source = 10; // Funding source of the card, e.g. CREDIT, DEBIT, PREPAID, etc. (aligns with PaymentCard.funding_source)
string card_last_4_digits = 11; // Last four digits of the card number (aligns with PaymentCard.card_last_4_digits)
}
message Error {
Type type = 1;
string message = 2;
enum Type {
UNKNOWN = 0;
PENDING_CREATE = 1; // Token not yet created, still in progress
INVALID_REQUEST = 2;
}
}
}
Examples
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.GetCardTokenRequest;
import com.kodypay.grpc.ecom.v1.GetCardTokenResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
public class GetCardTokenExample {
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API_KEY";
public static void getCardToken(String storeId, String tokenId) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
GetCardTokenRequest request = GetCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setTokenId(tokenId) // or use .setTokenReference() instead
.build();
GetCardTokenResponse response = client.getCardToken(request);
if (response.hasResponse()) {
var tokenInfo = response.getResponse();
System.out.println("Token ID: " + tokenInfo.getTokenId());
System.out.println("Payment Token: " + tokenInfo.getPaymentToken());
System.out.println("Status: " + tokenInfo.getStatus());
System.out.println("Card Last 4: " + tokenInfo.getCardLast4Digits());
System.out.println("Payment Method: " + tokenInfo.getPaymentMethod());
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}
import grpc
import kody_clientsdk_python.ecom.v1.ecom_pb2 as kody_model
import kody_clientsdk_python.ecom.v1.ecom_pb2_grpc as kody_client
def get_card_token(store_id: str, token_id: str) -> None:
hostname = "grpc-staging.kodypay.com"
api_key = "API_KEY"
with grpc.secure_channel(hostname, grpc.ssl_channel_credentials()) as channel:
client = kody_client.KodyEcomPaymentsServiceStub(channel)
request = kody_model.GetCardTokenRequest(
store_id=store_id,
token_id=token_id # or use token_reference instead
)
response = client.GetCardToken(request, metadata=[("x-api-key", api_key)])
if response.HasField("response"):
token_info = response.response
print(f"Token ID: {token_info.token_id}")
print(f"Payment Token: {token_info.payment_token}")
print(f"Status: {token_info.status}")
print(f"Card Last 4: {token_info.card_last_4_digits}")
print(f"Payment Method: {token_info.payment_method}")
else:
print(f"Error: {response.error.message}")
if __name__ == "__main__":
get_card_token("STORE_ID", "TOKEN_ID")
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Ecom.V1;
class GetCardTokenExample
{
static async Task Main()
{
string hostname = "https://grpc-staging.kodypay.com";
string apiKey = "API_KEY";
var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyEcomPaymentsService.KodyEcomPaymentsServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
var request = new GetCardTokenRequest
{
StoreId = "STORE_ID",
TokenId = "TOKEN_ID" // or use TokenReference instead
};
var response = await client.GetCardTokenAsync(request, metadata);
if (response.ResultCase == GetCardTokenResponse.ResultOneofCase.Response)
{
var tokenInfo = response.Response;
Console.WriteLine($"Token ID: {tokenInfo.TokenId}");
Console.WriteLine($"Payment Token: {tokenInfo.PaymentToken}");
Console.WriteLine($"Status: {tokenInfo.Status}");
Console.WriteLine($"Card Last 4: {tokenInfo.CardLast4Digits}");
Console.WriteLine($"Payment Method: {tokenInfo.PaymentMethod}");
}
else
{
Console.WriteLine($"Error: {response.Error.Message}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Ecom\V1\KodyEcomPaymentsServiceClient;
use Com\Kodypay\Ecom\V1\GetCardTokenRequest;
use Grpc\ChannelCredentials;
$client = new KodyEcomPaymentsServiceClient('grpc-staging.kodypay.com:443', [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => ['API_KEY']];
$request = new GetCardTokenRequest();
$request->setStoreId('STORE_ID');
$request->setTokenId('TOKEN_ID'); // or use setTokenReference() instead
list($response, $status) = $client->GetCardToken($request, $metadata)->wait();
if ($status->code === \Grpc\STATUS_OK && $response->getResponse()) {
$tokenInfo = $response->getResponse();
echo "Token ID: " . $tokenInfo->getTokenId() . PHP_EOL;
echo "Payment Token: " . $tokenInfo->getPaymentToken() . PHP_EOL;
echo "Status: " . $tokenInfo->getStatus() . PHP_EOL;
echo "Card Last 4: " . $tokenInfo->getCardLast4Digits() . PHP_EOL;
echo "Payment Method: " . $tokenInfo->getPaymentMethod() . PHP_EOL;
} else {
echo "Error: " . $response->getError()->getMessage() . PHP_EOL;
}
2. Delete Card Token
Service Call: KodyEcomPaymentsService.DeleteCardToken
Permanently deletes a stored card token. This action cannot be undone.
Request
rpc DeleteCardToken(DeleteCardTokenRequest) returns (DeleteCardTokenResponse);
message DeleteCardTokenRequest {
string store_id = 1; // Kody store id
oneof token_identifier {
string token_id = 2; // The unique identifier created by Kody
string token_reference = 3; // Your unique payment reference that was set during the initiation
}
}
Response
message DeleteCardTokenResponse {
oneof result {
Response response = 1;
Error error = 2;
}
message Response {
// Empty response indicates successful deletion
}
message Error {
Type type = 1;
string message = 2;
enum Type {
UNKNOWN = 0;
NOT_FOUND = 1; // Token not found
FAILED = 2; // Deletion failed
INVALID_REQUEST = 3; // Invalid request parameters
}
}
}
Examples
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.DeleteCardTokenRequest;
import com.kodypay.grpc.ecom.v1.DeleteCardTokenResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
public class DeleteCardTokenExample {
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API_KEY";
public static void deleteCardToken(String storeId, String tokenId) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
DeleteCardTokenRequest request = DeleteCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setTokenId(tokenId) // or use .setTokenReference() instead
.build();
DeleteCardTokenResponse response = client.deleteCardToken(request);
if (response.hasResponse()) {
System.out.println("Card token deleted successfully");
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}
import grpc
import kody_clientsdk_python.ecom.v1.ecom_pb2 as kody_model
import kody_clientsdk_python.ecom.v1.ecom_pb2_grpc as kody_client
def delete_card_token(store_id: str, token_id: str) -> None:
hostname = "grpc-staging.kodypay.com"
api_key = "API_KEY"
with grpc.secure_channel(hostname, grpc.ssl_channel_credentials()) as channel:
client = kody_client.KodyEcomPaymentsServiceStub(channel)
request = kody_model.DeleteCardTokenRequest(
store_id=store_id,
token_id=token_id # or use token_reference instead
)
response = client.DeleteCardToken(request, metadata=[("x-api-key", api_key)])
if response.HasField("response"):
print("Card token deleted successfully")
else:
print(f"Error: {response.error.message}")
if __name__ == "__main__":
delete_card_token("STORE_ID", "TOKEN_ID")
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Ecom.V1;
class DeleteCardTokenExample
{
static async Task Main()
{
string hostname = "https://grpc-staging.kodypay.com";
string apiKey = "API_KEY";
var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyEcomPaymentsService.KodyEcomPaymentsServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
var request = new DeleteCardTokenRequest
{
StoreId = "STORE_ID",
TokenId = "TOKEN_ID" // or use TokenReference instead
};
var response = await client.DeleteCardTokenAsync(request, metadata);
if (response.ResultCase == DeleteCardTokenResponse.ResultOneofCase.Response)
{
Console.WriteLine("Card token deleted successfully");
}
else
{
Console.WriteLine($"Error: {response.Error.Message}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Ecom\V1\KodyEcomPaymentsServiceClient;
use Com\Kodypay\Ecom\V1\DeleteCardTokenRequest;
use Grpc\ChannelCredentials;
$client = new KodyEcomPaymentsServiceClient('grpc-staging.kodypay.com:443', [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => ['API_KEY']];
$request = new DeleteCardTokenRequest();
$request->setStoreId('STORE_ID');
$request->setTokenId('TOKEN_ID'); // or use setTokenReference() instead
list($response, $status) = $client->DeleteCardToken($request, $metadata)->wait();
if ($status->code === \Grpc\STATUS_OK && $response->getResponse()) {
echo "Card token deleted successfully" . PHP_EOL;
} else {
echo "Error: " . $response->getError()->getMessage() . PHP_EOL;
}
3. Get Card Tokens
Service Call: KodyEcomPaymentsService.GetCardTokens
Retrieves all stored card tokens for a specific payer (customer).
Request
rpc GetCardTokens(GetCardTokensRequest) returns (GetCardTokensResponse);
message GetCardTokensRequest {
string store_id = 1;
string payer_reference = 2; // The shopper for whom to list tokens
}
Response
message GetCardTokensResponse {
oneof result {
Response response = 1;
Error error = 2;
}
message Response {
repeated GetCardTokenResponse.Response tokens = 1;
}
message Error {
Type type = 1;
string message = 2;
enum Type {
UNKNOWN = 0;
INVALID_REQUEST = 1; // e.g. missing store_id or payer_reference
}
}
}
Examples
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.GetCardTokensRequest;
import com.kodypay.grpc.ecom.v1.GetCardTokensResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
public class GetCardTokensExample {
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API_KEY";
public static void getCardTokens(String storeId, String payerReference) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
GetCardTokensRequest request = GetCardTokensRequest.newBuilder()
.setStoreId(storeId)
.setPayerReference(payerReference)
.build();
GetCardTokensResponse response = client.getCardTokens(request);
if (response.hasResponse()) {
System.out.println("Found " + response.getResponse().getTokensCount() + " tokens:");
for (var token : response.getResponse().getTokensList()) {
System.out.println("Token ID: " + token.getTokenId());
System.out.println("Card Last 4: " + token.getCardLast4Digits());
System.out.println("Payment Method: " + token.getPaymentMethod());
System.out.println("Status: " + token.getStatus());
System.out.println("---");
}
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}
import grpc
import kody_clientsdk_python.ecom.v1.ecom_pb2 as kody_model
import kody_clientsdk_python.ecom.v1.ecom_pb2_grpc as kody_client
def get_card_tokens(store_id: str, payer_reference: str) -> None:
hostname = "grpc-staging.kodypay.com"
api_key = "API_KEY"
with grpc.secure_channel(hostname, grpc.ssl_channel_credentials()) as channel:
client = kody_client.KodyEcomPaymentsServiceStub(channel)
request = kody_model.GetCardTokensRequest(
store_id=store_id,
payer_reference=payer_reference
)
response = client.GetCardTokens(request, metadata=[("x-api-key", api_key)])
if response.HasField("response"):
tokens = response.response.tokens
print(f"Found {len(tokens)} tokens:")
for token in tokens:
print(f"Token ID: {token.token_id}")
print(f"Card Last 4: {token.card_last_4_digits}")
print(f"Payment Method: {token.payment_method}")
print(f"Status: {token.status}")
print("---")
else:
print(f"Error: {response.error.message}")
if __name__ == "__main__":
get_card_tokens("STORE_ID", "PAYER_REFERENCE")
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Ecom.V1;
class GetCardTokensExample
{
static async Task Main()
{
string hostname = "https://grpc-staging.kodypay.com";
string apiKey = "API_KEY";
var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyEcomPaymentsService.KodyEcomPaymentsServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
var request = new GetCardTokensRequest
{
StoreId = "STORE_ID",
PayerReference = "PAYER_REFERENCE"
};
var response = await client.GetCardTokensAsync(request, metadata);
if (response.ResultCase == GetCardTokensResponse.ResultOneofCase.Response)
{
var tokens = response.Response.Tokens;
Console.WriteLine($"Found {tokens.Count} tokens:");
foreach (var token in tokens)
{
Console.WriteLine($"Token ID: {token.TokenId}");
Console.WriteLine($"Card Last 4: {token.CardLast4Digits}");
Console.WriteLine($"Payment Method: {token.PaymentMethod}");
Console.WriteLine($"Status: {token.Status}");
Console.WriteLine("---");
}
}
else
{
Console.WriteLine($"Error: {response.Error.Message}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Ecom\V1\KodyEcomPaymentsServiceClient;
use Com\Kodypay\Ecom\V1\GetCardTokensRequest;
use Grpc\ChannelCredentials;
$client = new KodyEcomPaymentsServiceClient('grpc-staging.kodypay.com:443', [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => ['API_KEY']];
$request = new GetCardTokensRequest();
$request->setStoreId('STORE_ID');
$request->setPayerReference('PAYER_REFERENCE');
list($response, $status) = $client->GetCardTokens($request, $metadata)->wait();
if ($status->code === \Grpc\STATUS_OK && $response->getResponse()) {
$tokens = $response->getResponse()->getTokens();
echo "Found " . count($tokens) . " tokens:" . PHP_EOL;
foreach ($tokens as $token) {
echo "Token ID: " . $token->getTokenId() . PHP_EOL;
echo "Card Last 4: " . $token->getCardLast4Digits() . PHP_EOL;
echo "Payment Method: " . $token->getPaymentMethod() . PHP_EOL;
echo "Status: " . $token->getStatus() . PHP_EOL;
echo "---" . PHP_EOL;
}
} else {
echo "Error: " . $response->getError()->getMessage() . PHP_EOL;
}
4. Pay With Card Token
Service Call: KodyEcomPaymentsService.PayWithCardToken
Processes a payment using a previously stored card token. This allows for instant payments without requiring the customer to re-enter their card details.
Request
rpc PayWithCardToken(PayWithCardTokenRequest) returns (PaymentDetailsResponse);
message PayWithCardTokenRequest {
string store_id = 1; // Your Kody store id
string idempotency_uuid = 2; // Idempotency key to ensure the request is processed only once
string payment_token = 3; // The ID of the payment token to be charged
uint64 amount_minor_units = 4; // Amount in minor units. For example, 2000 means GBP 20.00.
string currency = 5; // ISO 4217 three letter currency code
string payment_reference = 6; // Your unique reference for this payment
optional string order_id = 7; // Your identifier for the order
optional string order_metadata = 8; // Optional order details, not yet implemented.
optional string payer_statement = 9; // Optional text for payer's bank statement
optional PaymentInitiationRequest.CaptureOptions capture_options = 10; // Optional capture settings if the charge is an authorization
}
Response
The response follows the same structure as the standard PaymentDetailsResponse
from the main payments API, containing payment status, transaction details, and card information.
Examples
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.PayWithCardTokenRequest;
import com.kodypay.grpc.ecom.v1.PaymentDetailsResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.util.UUID;
public class PayWithCardTokenExample {
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API_KEY";
public static void payWithCardToken(String storeId, String paymentToken) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
PayWithCardTokenRequest request = PayWithCardTokenRequest.newBuilder()
.setStoreId(storeId)
.setIdempotencyUuid(UUID.randomUUID().toString())
.setPaymentToken(paymentToken)
.setAmountMinorUnits(2500) // £25.00
.setCurrency("GBP")
.setPaymentReference("token_pay_" + UUID.randomUUID())
.setOrderId("order_" + UUID.randomUUID()) // optional field
.setPayerStatement("Online Purchase")
.build();
PaymentDetailsResponse response = client.payWithCardToken(request);
if (response.hasResponse()) {
var paymentDetails = response.getResponse();
System.out.println("Payment ID: " + paymentDetails.getPaymentId());
System.out.println("Status: " + paymentDetails.getStatus());
if (paymentDetails.hasPaymentData()) {
var paymentData = paymentDetails.getPaymentData();
System.out.println("Auth Status: " + paymentData.getAuthStatus());
if (paymentData.hasPaymentCard()) {
System.out.println("Card Last 4: " + paymentData.getPaymentCard().getCardLast4Digits());
}
}
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}
import grpc
from uuid import uuid4
import kody_clientsdk_python.ecom.v1.ecom_pb2 as kody_model
import kody_clientsdk_python.ecom.v1.ecom_pb2_grpc as kody_client
def pay_with_card_token(store_id: str, payment_token: str) -> None:
hostname = "grpc-staging.kodypay.com"
api_key = "API_KEY"
with grpc.secure_channel(hostname, grpc.ssl_channel_credentials()) as channel:
client = kody_client.KodyEcomPaymentsServiceStub(channel)
request = kody_model.PayWithCardTokenRequest(
store_id=store_id,
idempotency_uuid=str(uuid4()),
payment_token=payment_token,
amount_minor_units=2500, # £25.00
currency="GBP",
payment_reference=f"token_pay_{uuid4()}",
order_id=f"order_{uuid4()}", # optional field
payer_statement="Online Purchase"
)
response = client.PayWithCardToken(request, metadata=[("x-api-key", api_key)])
if response.HasField("response"):
payment_details = response.response
print(f"Payment ID: {payment_details.payment_id}")
print(f"Status: {payment_details.status}")
if payment_details.HasField("payment_data"):
payment_data = payment_details.payment_data
print(f"Auth Status: {payment_data.auth_status}")
if payment_data.HasField("payment_card"):
print(f"Card Last 4: {payment_data.payment_card.card_last_4_digits}")
else:
print(f"Error: {response.error.message}")
if __name__ == "__main__":
pay_with_card_token("STORE_ID", "PAYMENT_TOKEN")
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Ecom.V1;
class PayWithCardTokenExample
{
static async Task Main()
{
string hostname = "https://grpc-staging.kodypay.com";
string apiKey = "API_KEY";
var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyEcomPaymentsService.KodyEcomPaymentsServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
var request = new PayWithCardTokenRequest
{
StoreId = "STORE_ID",
IdempotencyUuid = Guid.NewGuid().ToString(),
PaymentToken = "PAYMENT_TOKEN",
AmountMinorUnits = 2500, // £25.00
Currency = "GBP",
PaymentReference = $"token_pay_{Guid.NewGuid()}",
OrderId = $"order_{Guid.NewGuid()}", // optional field
PayerStatement = "Online Purchase"
};
var response = await client.PayWithCardTokenAsync(request, metadata);
if (response.ResultCase == PaymentDetailsResponse.ResultOneofCase.Response)
{
var paymentDetails = response.Response;
Console.WriteLine($"Payment ID: {paymentDetails.PaymentId}");
Console.WriteLine($"Status: {paymentDetails.Status}");
if (paymentDetails.HasPaymentData)
{
var paymentData = paymentDetails.PaymentData;
Console.WriteLine($"Auth Status: {paymentData.AuthStatus}");
if (paymentData.PaymentMethodDetailsCase == PaymentData.PaymentMethodDetailsOneofCase.PaymentCard)
{
Console.WriteLine($"Card Last 4: {paymentData.PaymentCard.CardLast4Digits}");
}
}
}
else
{
Console.WriteLine($"Error: {response.Error.Message}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Ecom\V1\KodyEcomPaymentsServiceClient;
use Com\Kodypay\Ecom\V1\PayWithCardTokenRequest;
use Grpc\ChannelCredentials;
$client = new KodyEcomPaymentsServiceClient('grpc-staging.kodypay.com:443', [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => ['API_KEY']];
$request = new PayWithCardTokenRequest();
$request->setStoreId('STORE_ID');
$request->setIdempotencyUuid(uniqid());
$request->setPaymentToken('PAYMENT_TOKEN');
$request->setAmountMinorUnits(2500); // £25.00
$request->setCurrency('GBP');
$request->setPaymentReference('token_pay_' . uniqid());
$request->setOrderId('order_' . uniqid()); // optional field
$request->setPayerStatement('Online Purchase');
list($response, $status) = $client->PayWithCardToken($request, $metadata)->wait();
if ($status->code === \Grpc\STATUS_OK && $response->getResponse()) {
$paymentDetails = $response->getResponse();
echo "Payment ID: " . $paymentDetails->getPaymentId() . PHP_EOL;
echo "Status: " . $paymentDetails->getStatus() . PHP_EOL;
if ($paymentDetails->hasPaymentData()) {
$paymentData = $paymentDetails->getPaymentData();
echo "Auth Status: " . $paymentData->getAuthStatus() . PHP_EOL;
if ($paymentData->hasPaymentCard()) {
echo "Card Last 4: " . $paymentData->getPaymentCard()->getCardLast4Digits() . PHP_EOL;
}
}
} else {
echo "Error: " . $response->getError()->getMessage() . PHP_EOL;
}
5. Create Card Token
Service Call: KodyEcomPaymentsService.CreateCardToken
Creates a new card token by initiating a tokenization process. This returns a URL where the customer can securely enter their card details to create a reusable token.
Request
rpc CreateCardToken(CreateTokenRequest) returns (CreateTokenResponse);
message CreateTokenRequest {
string store_id = 1; // Your Kody store id
string idempotency_uuid = 2; // Idempotency key to ensure the request is processed only once, generated by client.
optional string token_reference = 3; // Your unique reference for this token request, if applicable. This can be used to match the token with your internal systems.
string payer_reference = 4; // The payer for whom the token is being created. This can be a user ID or any unique identifier you use to track users.
optional string metadata = 5; // A data set that can be used to store information about the order and used in the tokenisation process.
string return_url = 6; // The URL that your client application will be redirected to after the tokenisation is authorised. You can include additional query parameters, for example, the user id or order reference.
optional string payer_statement = 7; // The text to be shown on the payer's bank statement. Maximum 22 characters, otherwise banks might truncate the string. If not set it will use the store's terminals receipt printing name. Allowed characters: a-z, A-Z, 0-9, spaces, and special characters . , ' _ - ? + * /
optional string payer_email_address = 8; // We recommend that you provide this data, as it is used in velocity fraud checks. Required for 3D Secure 2 transactions.
optional string payer_phone_number = 9; // We recommend that you provide this data, as it is used in velocity fraud checks. Required for 3D Secure 2 transactions.
optional RecurringProcessingModel recurring_processing_model = 10; // The recurring model to use for the payment, if applicable. Can be 'Subscription', 'UnscheduledCardOnFile' or 'CardOnFile'.
}
Response
message CreateTokenResponse {
oneof result {
Response response = 1;
Error error = 2;
}
message Response {
string token_id = 1; // The unique identifier created by Kody
string create_token_url = 2; // The URL to send the user to for tokenization
}
message Error {
Type type = 1;
string message = 2;
enum Type {
UNKNOWN = 0;
DUPLICATE_ATTEMPT = 1;
INVALID_REQUEST = 2;
}
}
}
Examples
- Java
- Python
- .NET
- PHP
package com.kody;
import com.kodypay.grpc.ecom.v1.KodyEcomPaymentsServiceGrpc;
import com.kodypay.grpc.ecom.v1.CreateTokenRequest;
import com.kodypay.grpc.ecom.v1.CreateTokenResponse;
import com.kodypay.grpc.ecom.v1.RecurringProcessingModel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import java.util.UUID;
public class CreateCardTokenExample {
public static final String HOSTNAME = "grpc-staging.kodypay.com";
public static final String API_KEY = "API_KEY";
public static void createCardToken(String storeId, String payerReference) {
ManagedChannel channel = ManagedChannelBuilder.forAddress(HOSTNAME, 443)
.useTransportSecurity()
.build();
Metadata metadata = new Metadata();
metadata.put(Metadata.Key.of("X-API-Key", Metadata.ASCII_STRING_MARSHALLER), API_KEY);
var client = KodyEcomPaymentsServiceGrpc.newBlockingStub(channel)
.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(metadata));
CreateTokenRequest request = CreateTokenRequest.newBuilder()
.setStoreId(storeId)
.setIdempotencyUuid(UUID.randomUUID().toString())
.setTokenReference("token_ref_" + UUID.randomUUID())
.setPayerReference(payerReference)
.setReturnUrl("https://your-website.com/token-complete")
.setPayerEmailAddress("customer@example.com")
.setRecurringProcessingModel(RecurringProcessingModel.CARD_ON_FILE)
.build();
CreateTokenResponse response = client.createCardToken(request);
if (response.hasResponse()) {
System.out.println("Token ID: " + response.getResponse().getTokenId());
System.out.println("Token Creation URL: " + response.getResponse().getCreateTokenUrl());
System.out.println("Redirect the customer to this URL to complete tokenization");
} else {
System.err.println("Error: " + response.getError().getMessage());
}
channel.shutdownNow();
}
}
import grpc
from uuid import uuid4
import kody_clientsdk_python.ecom.v1.ecom_pb2 as kody_model
import kody_clientsdk_python.ecom.v1.ecom_pb2_grpc as kody_client
def create_card_token(store_id: str, payer_reference: str) -> None:
hostname = "grpc-staging.kodypay.com"
api_key = "API_KEY"
with grpc.secure_channel(hostname, grpc.ssl_channel_credentials()) as channel:
client = kody_client.KodyEcomPaymentsServiceStub(channel)
request = kody_model.CreateTokenRequest(
store_id=store_id,
idempotency_uuid=str(uuid4()),
token_reference=f"token_ref_{uuid4()}",
payer_reference=payer_reference,
return_url="https://your-website.com/token-complete",
payer_email_address="customer@example.com",
recurring_processing_model=kody_model.RecurringProcessingModel.CARD_ON_FILE
)
response = client.CreateCardToken(request, metadata=[("x-api-key", api_key)])
if response.HasField("response"):
print(f"Token ID: {response.response.token_id}")
print(f"Token Creation URL: {response.response.create_token_url}")
print("Redirect the customer to this URL to complete tokenization")
else:
print(f"Error: {response.error.message}")
if __name__ == "__main__":
create_card_token("STORE_ID", "PAYER_REFERENCE")
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using Com.Kodypay.Ecom.V1;
class CreateCardTokenExample
{
static async Task Main()
{
string hostname = "https://grpc-staging.kodypay.com";
string apiKey = "API_KEY";
var channel = GrpcChannel.ForAddress(hostname);
var client = new KodyEcomPaymentsService.KodyEcomPaymentsServiceClient(channel);
var metadata = new Metadata { { "X-API-Key", apiKey } };
var request = new CreateTokenRequest
{
StoreId = "STORE_ID",
IdempotencyUuid = Guid.NewGuid().ToString(),
TokenReference = $"token_ref_{Guid.NewGuid()}",
PayerReference = "PAYER_REFERENCE",
ReturnUrl = "https://your-website.com/token-complete",
PayerEmailAddress = "customer@example.com",
RecurringProcessingModel = RecurringProcessingModel.CardOnFile
};
var response = await client.CreateCardTokenAsync(request, metadata);
if (response.ResultCase == CreateTokenResponse.ResultOneofCase.Response)
{
Console.WriteLine($"Token ID: {response.Response.TokenId}");
Console.WriteLine($"Token Creation URL: {response.Response.CreateTokenUrl}");
Console.WriteLine("Redirect the customer to this URL to complete tokenization");
}
else
{
Console.WriteLine($"Error: {response.Error.Message}");
}
}
}
<?php
require __DIR__ . '/../vendor/autoload.php';
use Com\Kodypay\Ecom\V1\KodyEcomPaymentsServiceClient;
use Com\Kodypay\Ecom\V1\CreateTokenRequest;
use Com\Kodypay\Ecom\V1\RecurringProcessingModel;
use Grpc\ChannelCredentials;
$client = new KodyEcomPaymentsServiceClient('grpc-staging.kodypay.com:443', [
'credentials' => ChannelCredentials::createSsl()
]);
$metadata = ['X-API-Key' => ['API_KEY']];
$request = new CreateTokenRequest();
$request->setStoreId('STORE_ID');
$request->setIdempotencyUuid(uniqid());
$request->setTokenReference('token_ref_' . uniqid());
$request->setPayerReference('PAYER_REFERENCE');
$request->setReturnUrl('https://your-website.com/token-complete');
$request->setPayerEmailAddress('customer@example.com');
$request->setRecurringProcessingModel(RecurringProcessingModel::CARD_ON_FILE);
list($response, $status) = $client->CreateCardToken($request, $metadata)->wait();
if ($status->code === \Grpc\STATUS_OK && $response->getResponse()) {
echo "Token ID: " . $response->getResponse()->getTokenId() . PHP_EOL;
echo "Token Creation URL: " . $response->getResponse()->getCreateTokenUrl() . PHP_EOL;
echo "Redirect the customer to this URL to complete tokenization" . PHP_EOL;
} else {
echo "Error: " . $response->getError()->getMessage() . PHP_EOL;
}
Best Practices
Security Considerations
- Store
payer_reference
values securely and ensure they are unique per customer - Never store actual card details on your servers - use tokens instead
- Implement proper access controls for token management operations
Token Lifecycle Management
- Create tokens only when customers explicitly consent to save their payment method
- Monitor token status regularly using
GetCardToken
to ensure tokens remain valid - Delete tokens when customers request removal or close their accounts
- Handle expired tokens gracefully by prompting for new payment method creation
Error Handling
- Implement retry logic for network failures
- Handle
PENDING_CREATE
status when tokens are still being processed - Validate input parameters before making API calls
- Log errors appropriately for debugging and monitoring
Performance Optimization
- Use
GetCardTokens
to batch-load all tokens for a customer - Implement caching for frequently accessed token information
- Consider using connection pooling for high-volume applications
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 identifierTOKEN_ID
: The card token identifierPAYER_REFERENCE
: Your customer identifierPAYMENT_TOKEN
: The payment token for processing payments
For further support or more detailed information, please contact our support team.