Create Product Payment Intent
curl --request POST \
--url https://api.khaime.com/api/v1/product-payment/intent \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"product_type": "<string>",
"cart": [
{}
],
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"currency": "<string>",
"payment_type": "<string>",
"cart_unique_id": "<string>",
"delivery_details": {},
"user_country": "<string>",
"payment_gateway": "<string>",
"is_coupon_used": true,
"coupon_code": "<string>",
"payment_by": "<string>",
"order_total_charge": 123,
"is_split": true,
"payment_split_position": 123,
"installment_plan_id": 123
}
'import requests
url = "https://api.khaime.com/api/v1/product-payment/intent"
payload = {
"product_type": "<string>",
"cart": [{}],
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"currency": "<string>",
"payment_type": "<string>",
"cart_unique_id": "<string>",
"delivery_details": {},
"user_country": "<string>",
"payment_gateway": "<string>",
"is_coupon_used": True,
"coupon_code": "<string>",
"payment_by": "<string>",
"order_total_charge": 123,
"is_split": True,
"payment_split_position": 123,
"installment_plan_id": 123
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
product_type: '<string>',
cart: [{}],
email: '<string>',
first_name: '<string>',
last_name: '<string>',
currency: '<string>',
payment_type: '<string>',
cart_unique_id: '<string>',
delivery_details: {},
user_country: '<string>',
payment_gateway: '<string>',
is_coupon_used: true,
coupon_code: '<string>',
payment_by: '<string>',
order_total_charge: 123,
is_split: true,
payment_split_position: 123,
installment_plan_id: 123
})
};
fetch('https://api.khaime.com/api/v1/product-payment/intent', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.khaime.com/api/v1/product-payment/intent",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'product_type' => '<string>',
'cart' => [
[
]
],
'email' => '<string>',
'first_name' => '<string>',
'last_name' => '<string>',
'currency' => '<string>',
'payment_type' => '<string>',
'cart_unique_id' => '<string>',
'delivery_details' => [
],
'user_country' => '<string>',
'payment_gateway' => '<string>',
'is_coupon_used' => true,
'coupon_code' => '<string>',
'payment_by' => '<string>',
'order_total_charge' => 123,
'is_split' => true,
'payment_split_position' => 123,
'installment_plan_id' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.khaime.com/api/v1/product-payment/intent"
payload := strings.NewReader("{\n \"product_type\": \"<string>\",\n \"cart\": [\n {}\n ],\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"currency\": \"<string>\",\n \"payment_type\": \"<string>\",\n \"cart_unique_id\": \"<string>\",\n \"delivery_details\": {},\n \"user_country\": \"<string>\",\n \"payment_gateway\": \"<string>\",\n \"is_coupon_used\": true,\n \"coupon_code\": \"<string>\",\n \"payment_by\": \"<string>\",\n \"order_total_charge\": 123,\n \"is_split\": true,\n \"payment_split_position\": 123,\n \"installment_plan_id\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.khaime.com/api/v1/product-payment/intent")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"product_type\": \"<string>\",\n \"cart\": [\n {}\n ],\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"currency\": \"<string>\",\n \"payment_type\": \"<string>\",\n \"cart_unique_id\": \"<string>\",\n \"delivery_details\": {},\n \"user_country\": \"<string>\",\n \"payment_gateway\": \"<string>\",\n \"is_coupon_used\": true,\n \"coupon_code\": \"<string>\",\n \"payment_by\": \"<string>\",\n \"order_total_charge\": 123,\n \"is_split\": true,\n \"payment_split_position\": 123,\n \"installment_plan_id\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.khaime.com/api/v1/product-payment/intent")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"product_type\": \"<string>\",\n \"cart\": [\n {}\n ],\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"currency\": \"<string>\",\n \"payment_type\": \"<string>\",\n \"cart_unique_id\": \"<string>\",\n \"delivery_details\": {},\n \"user_country\": \"<string>\",\n \"payment_gateway\": \"<string>\",\n \"is_coupon_used\": true,\n \"coupon_code\": \"<string>\",\n \"payment_by\": \"<string>\",\n \"order_total_charge\": 123,\n \"is_split\": true,\n \"payment_split_position\": 123,\n \"installment_plan_id\": 123\n}"
response = http.request(request)
puts response.read_body{
"status": true,
"message": "Payment Intent Successful",
"data": {
"token": "eyJpbnRlbnRfaWQiOiJjYXJ0XzEyMzQ1Njc4...",
"cart_identifier": "cart_12345678-abcd-1234-efgh-567890abcdef",
"amount": 6179,
"currency": "USD",
"status": "pending",
"breakdown": {
"subtotal": 5000,
"shipping": 500,
"tax": 329,
"transaction_fee": 350,
"customer_pays_fees": true
}
}
}
Commerce API
Create Product Payment Intent
Create a payment intent for Khaime catalog products
POST
/
product-payment
/
intent
Create Product Payment Intent
curl --request POST \
--url https://api.khaime.com/api/v1/product-payment/intent \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"product_type": "<string>",
"cart": [
{}
],
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"currency": "<string>",
"payment_type": "<string>",
"cart_unique_id": "<string>",
"delivery_details": {},
"user_country": "<string>",
"payment_gateway": "<string>",
"is_coupon_used": true,
"coupon_code": "<string>",
"payment_by": "<string>",
"order_total_charge": 123,
"is_split": true,
"payment_split_position": 123,
"installment_plan_id": 123
}
'import requests
url = "https://api.khaime.com/api/v1/product-payment/intent"
payload = {
"product_type": "<string>",
"cart": [{}],
"email": "<string>",
"first_name": "<string>",
"last_name": "<string>",
"currency": "<string>",
"payment_type": "<string>",
"cart_unique_id": "<string>",
"delivery_details": {},
"user_country": "<string>",
"payment_gateway": "<string>",
"is_coupon_used": True,
"coupon_code": "<string>",
"payment_by": "<string>",
"order_total_charge": 123,
"is_split": True,
"payment_split_position": 123,
"installment_plan_id": 123
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
product_type: '<string>',
cart: [{}],
email: '<string>',
first_name: '<string>',
last_name: '<string>',
currency: '<string>',
payment_type: '<string>',
cart_unique_id: '<string>',
delivery_details: {},
user_country: '<string>',
payment_gateway: '<string>',
is_coupon_used: true,
coupon_code: '<string>',
payment_by: '<string>',
order_total_charge: 123,
is_split: true,
payment_split_position: 123,
installment_plan_id: 123
})
};
fetch('https://api.khaime.com/api/v1/product-payment/intent', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.khaime.com/api/v1/product-payment/intent",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'product_type' => '<string>',
'cart' => [
[
]
],
'email' => '<string>',
'first_name' => '<string>',
'last_name' => '<string>',
'currency' => '<string>',
'payment_type' => '<string>',
'cart_unique_id' => '<string>',
'delivery_details' => [
],
'user_country' => '<string>',
'payment_gateway' => '<string>',
'is_coupon_used' => true,
'coupon_code' => '<string>',
'payment_by' => '<string>',
'order_total_charge' => 123,
'is_split' => true,
'payment_split_position' => 123,
'installment_plan_id' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.khaime.com/api/v1/product-payment/intent"
payload := strings.NewReader("{\n \"product_type\": \"<string>\",\n \"cart\": [\n {}\n ],\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"currency\": \"<string>\",\n \"payment_type\": \"<string>\",\n \"cart_unique_id\": \"<string>\",\n \"delivery_details\": {},\n \"user_country\": \"<string>\",\n \"payment_gateway\": \"<string>\",\n \"is_coupon_used\": true,\n \"coupon_code\": \"<string>\",\n \"payment_by\": \"<string>\",\n \"order_total_charge\": 123,\n \"is_split\": true,\n \"payment_split_position\": 123,\n \"installment_plan_id\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.khaime.com/api/v1/product-payment/intent")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"product_type\": \"<string>\",\n \"cart\": [\n {}\n ],\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"currency\": \"<string>\",\n \"payment_type\": \"<string>\",\n \"cart_unique_id\": \"<string>\",\n \"delivery_details\": {},\n \"user_country\": \"<string>\",\n \"payment_gateway\": \"<string>\",\n \"is_coupon_used\": true,\n \"coupon_code\": \"<string>\",\n \"payment_by\": \"<string>\",\n \"order_total_charge\": 123,\n \"is_split\": true,\n \"payment_split_position\": 123,\n \"installment_plan_id\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.khaime.com/api/v1/product-payment/intent")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"product_type\": \"<string>\",\n \"cart\": [\n {}\n ],\n \"email\": \"<string>\",\n \"first_name\": \"<string>\",\n \"last_name\": \"<string>\",\n \"currency\": \"<string>\",\n \"payment_type\": \"<string>\",\n \"cart_unique_id\": \"<string>\",\n \"delivery_details\": {},\n \"user_country\": \"<string>\",\n \"payment_gateway\": \"<string>\",\n \"is_coupon_used\": true,\n \"coupon_code\": \"<string>\",\n \"payment_by\": \"<string>\",\n \"order_total_charge\": 123,\n \"is_split\": true,\n \"payment_split_position\": 123,\n \"installment_plan_id\": 123\n}"
response = http.request(request)
puts response.read_body{
"status": true,
"message": "Payment Intent Successful",
"data": {
"token": "eyJpbnRlbnRfaWQiOiJjYXJ0XzEyMzQ1Njc4...",
"cart_identifier": "cart_12345678-abcd-1234-efgh-567890abcdef",
"amount": 6179,
"currency": "USD",
"status": "pending",
"breakdown": {
"subtotal": 5000,
"shipping": 500,
"tax": 329,
"transaction_fee": 350,
"customer_pays_fees": true
}
}
}
Create Product Payment Intent
Creates a payment intent for products in your Khaime catalog. Supports physical products (with shipping), digital products, and gift cards.Show the final total before you create the intent. Send this same body to Preview Payment on your review step: it returns the exact subtotal, fee and total without creating anything. Prices, Fees and Totals shows how the pricing calls fit together.
This endpoint is for Khaime catalog products. If your products live on your own platform (e.g., WooCommerce), use Create Payment Intent instead.
Requirements vary by product type:
- Physical products: Requires
cart_unique_idfrom/cart/validateanddelivery_details - Digital/Gift Card/Others: No cart validation needed -
cart_unique_idis auto-generated
Request
string
required
Your API key
Body Parameters
string
required
Type of product being purchased. Determines required fields and checkout flow.Values:
physical_product, digital, gift_cardarray
required
Array of cart items.
Show Cart Item Schema
Show Cart Item Schema
Required Fields:
Variation Fields (required when
Optional Fields:
Additional Information:
Custom Field Types:
Answers are keyed by the field’s The array length must equal
| Field | Type | Description |
|---|---|---|
product_id | integer | Product ID from your Khaime catalog |
quantity | integer | Quantity to purchase (must be positive) |
price | integer | Unit price in cents (minor currency units) |
product_image | string | Product image URL |
has_variation | boolean | Whether product has variants |
product_variant_data | string | Variant description (e.g., "Large / Blue") or "none" |
has_variation is true):| Field | Type | Description |
|---|---|---|
least_sub_variant_id | string | Variant SKU or identifier |
main_variant | string | Primary variant value (e.g., "Large") |
| Field | Type | Description |
|---|---|---|
product_title | string | Product name for display |
product_thumbnail | string | Thumbnail image URL |
shipping_rate | object | null | Pre-calculated shipping rate (physical products only) |
subscription_frequency_key | string | Subscription frequency (for recurring products) |
subscription_plan_key | string | Subscription plan identifier |
| Field | Type | Description |
|---|---|---|
additional_information | object | array | Customer’s answers to the custom fields defined on that product, keyed by each field’s id. Pass an array of one object per unit when quantity is greater than 1 — see below. See Product Custom Fields. |
| Type | Example Value |
|---|---|
text | "Happy Birthday!" |
textarea | "Please handle with care..." |
number | 42 |
date | "2024-12-25" |
email | "jane@example.com" |
phone | "+2348012345678" |
select | "script" |
checkbox | true |
range | 5 |
id, never its label. A key matching no
defined field is rejected, naming the unrecognised key and listing the ids
the product actually defines — a typo fails the request rather than producing a
successful checkout with a missing answer.Every required field is enforced on every cart line, whether or not that line
submitted anything. A line that omits additional_information entirely is
rejected the same way as one that omits a single required answer.One set of answers per unit. When a line’s quantity is greater than 1 and
the product defines any required field, pass an array with exactly one object
per unit. A single object covering several units is rejected, because the
answers describe each unit individually — one insured person, one engraving,
one ticket holder — and collapsing them loses all but the first.{
"product_id": 3807,
"quantity": 2,
"price": 1200,
"additional_information": [
{ "full_name": "Chidi Okafor", "date_of_birth": "2014-09-03" },
{ "full_name": "Ngozi Okafor", "date_of_birth": "2017-01-22" }
]
}
quantity exactly. Errors name the person, not
just the field — "Date of Birth" is required for JulsCare Dependent (person 2 of 2) — so you can tell which unit is incomplete.Dates are validated as real calendar dates: 2015-02-31 is rejected rather
than silently rolling forward to 3 March.Example cart item with custom fields:{
"product_id": 12345,
"quantity": 1,
"price": 4999,
"product_image": "https://example.com/bracelet.jpg",
"has_variation": false,
"product_variant_data": "none",
"additional_information": {
"engraving_text": "Happy Anniversary",
"gift_wrap": true,
"font_style": "script"
}
}
string
required
Customer’s email address
string
required
Customer’s first name
string
required
Customer’s last name
string
required
Currency code (e.g.,
USD, NGN, GBP)string
required
Payment type. Use
one for one-time payments.Values: one, multiple_timestring
Cart ID from
/cart/validate. Required for physical products. Auto-generated for digital products if not provided.object
Shipping address and delivery preferences. Required for physical products. Not needed for digital products.
string
Customer’s country code (e.g.,
US, NG). Auto-detected from IP if not provided.string
Preferred payment gateway. Auto-selected based on currency if not provided.Values:
stripe, paystack, paypal, squareboolean
default:"false"
Whether a coupon code is being applied
string
Coupon code to apply (if
is_coupon_used is true)string
required
Who is paying. Values:
customer, business, educatorRequired, and it must be
customer for a normal purchase. Omitting it is
rejected with a validation error. Sending any other value is accepted, but the
payment then settles down a different path and no merchant wallet is
credited — the charge succeeds and the money is not attributed. If a payment
completes at the gateway but no wallet balance moves, check this field first.number
required
Order total in the smallest currency unit. For an installment, send the plan total.
Installments
The product must be sold in installments, alone in the cart.boolean
default:"false"
true to pay in installments.integer
Installment to pay. Required with
is_split. 1 opens a new plan.integer
Plan to pay. Required for position 2 and later.
Each installment must be above the gateway minimum (e.g. $0.50 on Stripe, ₦50 on Paystack).
Selling a sub-merchant’s product
A marketplace operator can sell a product from a sub-merchant’s catalogue through this endpoint. There is nothing extra to send: call it with your own API key and put the sub-merchant’sproduct_id in the cart. Ownership, authorisation
and commission are all resolved server-side from the product itself.
Never send the seller’s id in the body. Ownership comes from the product record,
so a caller cannot name a merchant they are not entitled to sell for. A cart
containing a product you have no active marketplace relationship with is
rejected with
403 AUTH_PERMISSION_DENIED.- Every product in one cart must belong to the same merchant. A mixed-owner cart
is rejected with
400 MARKETPLACE_MIXED_OWNER_CART— an order settles to a single merchant, so there is no correct split for a mixed one. - Prices come from the catalogue, not from the request. The
priceyou send on each cart line is used for display reconciliation only; the server re-derives the total from the stored product price, any active deal, and the buyer’s market. If they disagree, the catalogue wins. - Commission is a percentage of the sale, set per sub-merchant by the
operator. There is no fixed-fee or markup option — to leave a sub-merchant
exactly 10.00ofa15.00 sale, set the rate to
33.33%.
sub-merchant = sale price − commission
operator = commission − Khaime fee
| Customer pays | $16.20 |
| Sub-merchant receives | $10.00 |
| Operator receives | $5.00 |
| Khaime fee | $1.20 |
The commission must cover the Khaime fee. If it does not, the intent is rejected
with
400 MARKETPLACE_COMMISSION_BELOW_FEES, naming both amounts — raise the
commission rate for that merchant, or have the customer pay the transaction fee.Product Type Requirements
| Field | Physical | Digital/Gift Card |
|---|---|---|
cart_unique_id | Required (from /cart/validate) | Auto-generated |
delivery_details | Required | Not needed |
user_country | From address | Auto-detected from IP |
| Cart validation step | Required | Not required |
Response
{
"status": true,
"message": "Payment Intent Successful",
"data": {
"token": "eyJpbnRlbnRfaWQiOiJjYXJ0XzEyMzQ1Njc4...",
"cart_identifier": "cart_12345678-abcd-1234-efgh-567890abcdef",
"amount": 6179,
"currency": "USD",
"status": "pending",
"breakdown": {
"subtotal": 5000,
"shipping": 500,
"tax": 329,
"transaction_fee": 350,
"customer_pays_fees": true
}
}
}
Response Fields
| Field | Description |
|---|---|
token | Signed token for @khaime/react checkout. HMAC-SHA256 signed, expires in 15 minutes. |
cart_identifier | Unique cart/transaction identifier for tracking |
amount | Final amount in cents (includes fees, shipping, tax) |
currency | Currency code |
status | Payment status (pending) |
breakdown | Payment breakdown: subtotal, discount, shipping, tax, transaction_fee, and customer_pays_fees. Amount fields are omitted when zero. |
Showing the fee to the customer
Whenbreakdown.customer_pays_fees is true, the transaction fee is included in amount and returned as breakdown.transaction_fee. Show it as its own line in your order summary, before the customer pays — it’s best practice for transparency, and a total higher than the catalog price with no explanation erodes trust and invites disputes. See Fees.
Who pays is the business’s customer_pays_transaction_fee setting. When an operator sells a sub-merchant’s product, it’s the operator’s decision.
| Field | Show as |
|---|---|
breakdown.subtotal | Items |
breakdown.discount | Discount (negative) |
breakdown.shipping | Shipping |
breakdown.tax | Tax |
breakdown.transaction_fee | ”Transaction fee” — only when customer_pays_fees is true |
amount | Total to pay |
amount, because that’s what the customer is charged. To show the breakdown while the customer is still reviewing their cart, call Preview Payment first — it returns the same numbers without creating an intent.
transaction_fee is returned even when the merchant absorbs the fee. Decide whether to show the line from customer_pays_fees, not from whether transaction_fee is present — otherwise you’ll show customers a fee they aren’t paying.Token Expiration: Tokens expire after 15 minutes. If a customer takes too long to complete payment, you’ll need to create a new payment intent.
Examples
Physical Product Checkout
Requires cart validation first to calculate shipping.curl -X POST "https://api.khaime.com/api/v1/product-payment/intent" \
-H "X-API-Key: pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_type": "physical_product",
"product_id": 12345,
"cart_unique_id": "bc59bc1f-e1b0-4ee5-a9cb-b966042ce38f",
"cart": [
{
"product_id": 12345,
"quantity": 2,
"price": 2500,
"product_title": "Premium T-Shirt",
"product_image": "https://example.com/shirt.jpg",
"has_variation": true,
"main_variant": "Large",
"least_sub_variant_id": "var_123",
"product_variant_data": "Large / Blue"
}
],
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"currency": "USD",
"payment_type": "one",
"delivery_details": {
"national_delivery": true,
"address": [{
"name": "John Doe",
"address": "123 Main St",
"city": "Austin",
"state": "Texas",
"zip_code": "78701",
"country": "United States",
"country_code": "US"
}]
}
}'
Digital Product Checkout
No cart validation or address required. Supports multi-item carts. Uses simplified cart schema.curl -X POST "https://api.khaime.com/api/v1/product-payment/intent" \
-H "X-API-Key: pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_type": "digital",
"product_title": "Digital Bundle (2 items)",
"order_total_charge": 4000,
"cart": [
{
"product_id": 4821,
"quantity": 1,
"price": 1500,
"product_image": "https://example.com/ebook.jpg",
"has_variation": false,
"product_variant_data": "none"
},
{
"product_id": 4822,
"quantity": 1,
"price": 2500,
"product_image": "https://example.com/course.jpg",
"has_variation": false,
"product_variant_data": "none"
}
],
"email": "buyer@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"currency": "USD",
"payment_type": "one",
"is_coupon_used": false,
"is_second_time_payment": false,
"payment_by": "customer"
}'
Simplified schema for digital products: Do NOT include
product_title, product_thumbnail, main_variant, least_sub_variant_id, or shipping_rate in cart items. These are only for physical products. You can still include additional_information for custom fields.Digital Product with Custom Fields
Example showing a digital product (e.g., personalized certificate) with custom fields.curl -X POST "https://api.khaime.com/api/v1/product-payment/intent" \
-H "X-API-Key: pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_type": "digital",
"product_title": "Personalized Certificate",
"order_total_charge": 2500,
"cart": [
{
"product_id": 5500,
"quantity": 1,
"price": 2500,
"product_image": "https://example.com/certificate.jpg",
"has_variation": false,
"product_variant_data": "none",
"additional_information": {
"recipient_name": "John Michael Smith",
"completion_date": "2024-06-15",
"course_name": "Advanced Photography"
}
}
],
"email": "buyer@example.com",
"first_name": "John",
"last_name": "Smith",
"currency": "USD",
"payment_type": "one"
}'
Physical Product with Custom Fields
Example showing a product where the business has configured custom fields for personalization. Product’s custom fields (configured by business):| Field Key | Name | Type | Required |
|---|---|---|---|
engraving_line_1 | First Line | text | Yes |
engraving_line_2 | Second Line | text | No |
engraving_font | Font Style | select | Yes |
gift_box | Premium Gift Box | boolean | No |
special_instructions | Special Instructions | textarea | No |
curl -X POST "https://api.khaime.com/api/v1/product-payment/intent" \
-H "X-API-Key: pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_type": "physical_product",
"product_id": 98765,
"cart_unique_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"cart": [
{
"product_id": 98765,
"quantity": 1,
"price": 7500,
"product_title": "Custom Engraved Bracelet",
"product_image": "https://example.com/bracelet.jpg",
"has_variation": true,
"main_variant": "Gold",
"least_sub_variant_id": "var_gold_sm",
"product_variant_data": "Gold / Small",
"additional_information": {
"engraving_line_1": "Forever Yours",
"engraving_line_2": "2024",
"engraving_font": "script",
"gift_box": true,
"special_instructions": "Please double-check engraving spelling"
}
}
],
"email": "customer@example.com",
"first_name": "Sarah",
"last_name": "Johnson",
"currency": "USD",
"payment_type": "one",
"delivery_details": {
"national_delivery": true,
"address": [{
"name": "Sarah Johnson",
"address": "456 Oak Avenue",
"city": "San Francisco",
"state": "California",
"zip_code": "94102",
"country": "United States",
"country_code": "US"
}]
}
}'
Accessing custom field values: The
additional_information data is included in order webhooks and the Order API response. Use this to fulfill personalized orders correctly.Gift Card Checkout
Same flow as digital products. Each gift card item creates a separate gift card code.curl -X POST "https://api.khaime.com/api/v1/product-payment/intent" \
-H "X-API-Key: pk_live_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"product_type": "gift_card",
"product_title": "Gift Cards (2)",
"order_total_charge": 10000,
"cart": [
{
"product_id": 5001,
"quantity": 2,
"price": 5000,
"product_image": "https://example.com/giftcard.jpg",
"has_variation": false,
"product_variant_data": "none"
}
],
"email": "buyer@example.com",
"first_name": "John",
"last_name": "Smith",
"currency": "USD",
"payment_type": "one",
"is_coupon_used": false,
"is_second_time_payment": false,
"payment_by": "customer"
}'
Pay in Installments
First installment:curl -X POST https://api.khaime.com/api/v1/product-payment/intent \
-H "X-API-Key: pk_sandbox_your_key" \
-H "Content-Type: application/json" \
-d '{
"product_type": "digital",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"currency": "USD",
"payment_type": "one",
"payment_by": "customer",
"order_total_charge": 10000,
"cart": [
{ "product_id": 3046, "quantity": 1, "has_variation": false, "product_image": "", "price": 10000, "product_variant_data": "Quantity: 1" }
],
"is_split": true,
"payment_split_position": 1
}'
{
"is_split": true,
"payment_split_position": 2,
"installment_plan_id": 812
}
additional_information if the product has custom fields.
Accepting Payments
Once you receive the response, collect payment from your customer using one of these options:Option 1: Embedded Checkout (Recommended)
Embed the checkout directly in your app using@khaime/react.
1
Install @khaime/react
npm install @khaime/react
yarn add @khaime/react
pnpm add @khaime/react
2
Render the Checkout
import { KhaimeCheckout } from '@khaime/react';
function CheckoutPage({ paymentData }) {
return (
<KhaimeCheckout
token={paymentData.token}
onSuccess={(result) => {
// Payment successful - redirect to confirmation
window.location.href = `/order/${paymentData.data.transaction_id}`;
}}
onError={(error) => {
console.error('Payment failed:', error.message);
}}
/>
);
}
React SDK Documentation
See the full React SDK documentation for all available options
Option 2: Redirect Checkout
Redirect the customer to Khaime’s hosted checkout page:window.location.href = response.data.payment_url;
Errors
| Error Code | Description |
|---|---|
CART_EXPIRED | Cart ID is no longer valid (carts expire after 30 minutes) |
CART_NOT_FOUND | Invalid cart ID |
CART_INVALID | Missing cart_unique_id for physical products, or an empty cart |
PRODUCT_NOT_FOUND | Product no longer available. The message names the missing product_id. |
PAYMENT_INTENT_FAILED | Error creating payment intent |
VALIDATION_FAILED | Invalid request parameters — also returned for an unrecognised additional_information key, a wrong-length per-unit array, or a value failing its field’s type (a date that is not a real calendar date, a malformed email, a select value outside the defined options). The message names the field and, for multi-unit lines, which unit. |
VALIDATION_MISSING_FIELD | A required custom field has no answer. The message names the field, the product, and which unit of a multi-unit line. |
AUTH_PERMISSION_DENIED | Selling a product belonging to a merchant you have no active marketplace relationship with |
MARKETPLACE_MIXED_OWNER_CART | One cart contains products from more than one merchant |
MARKETPLACE_COMMISSION_BELOW_FEES | The commission on this sale does not cover the Khaime transaction fee. The message names both amounts. |
INSTALLMENT_PLAN_NOT_FOUND | No such plan for this customer and product |
INSTALLMENT_NOT_FOUND | No installment at that position |
INSTALLMENT_ALREADY_PAID | Installment already paid |
INSTALLMENT_NOT_ALLOWED | Plan completed, cancelled or defaulted |
error_code alongside a human-readable
message. Switch on error_code — the message wording changes, the code does
not. VALIDATION_ERROR was never a real code; use VALIDATION_FAILED.
Confirming Payment
Always verify payments via webhooks before fulfilling orders. Frontend callbacks are for UI purposes only.
Related
Digital Cart Checkout
Complete guide for digital product multi-item carts
Physical Checkout Guide
Complete guide for physical products with shipping
Cart Validation
Validate cart and calculate shipping (physical products)
Webhooks
Listen for payment completion events
