Skip to main content

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

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

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

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

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

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

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

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

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

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

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

  1. Create tokens only when customers explicitly consent to save their payment method
  2. Monitor token status regularly using GetCardToken to ensure tokens remain valid
  3. Delete tokens when customers request removal or close their accounts
  4. 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 either grpc-staging.kodypay.com (for development and test) or grpc.kodypay.com (for live)
  • API_KEY: Your API key
  • STORE_ID: Your store identifier
  • TOKEN_ID: The card token identifier
  • PAYER_REFERENCE: Your customer identifier
  • PAYMENT_TOKEN: The payment token for processing payments

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