Skip to main content
POST

Create Charge

Create a one-time payment charge without requiring a product in Khaime’s catalog. Ideal for WooCommerce or custom integrations where products live on the merchant’s platform.
This is the recommended endpoint for plugin integrations. Use Create Session if you need Stripe Checkout hosted pages instead.

Request Body

amount
integer
required
Charge amount in smallest currency unit (cents, kobo) — the customer’s payment currency. Must be calculated using /pricing/calculate when currency conversion is involved — we validate it matches our calculation.
currency
string
required
3-letter ISO currency code for what the customer pays. Example: USD, NGN, GBP
total_amount
integer
required
The full amount the merchant is collecting, in smallest currency unit of total_currency. This is inclusive of all fees (shipping, handling, etc.) — not just the base product price.Why required: This field is the source of truth for what gets credited to the merchant’s wallet, and anchors our server-side conversion when multicurrency is involved.
  • No conversion: Pass the same value as amount
  • Multicurrency: Pass the total in the merchant’s baseline currency (e.g. 8500 for $85.00 USD)
  • Marketplace: Pass the total in the sub-merchant’s baseline currency
Example: merchant prices a service at 85USDwith85 USD with 5 shipping = $90 total, customer pays ₦144,000 NGN — set total_amount: 9000, total_currency: "USD".
original_product_amount is accepted as a deprecated alias and will continue to work.
total_currency
string
required
ISO 4217 currency code for total_amount. This is the merchant’s (or sub-merchant’s) pricing currency.
  • No conversion: Pass the same value as currency
  • Multicurrency / Marketplace: Pass the merchant’s baseline currency
original_product_currency is accepted as a deprecated alias and will continue to work.
converted_total_amount
integer
Your pre-computed conversion of total_amount into currency (the customer’s payment currency). Optional — but strongly recommended when total_currency differs from currency.When provided, we cross-validate:
  1. converted_total_currency must equal currency
  2. converted_total_amount must equal amount (within 1% tolerance)
This lets us catch discrepancies before hitting the exchange-rate API and gives you an explicit paper trail of what rate you applied.Must be provided together with converted_total_currency.
converted_total_currency
string
The currency of converted_total_amount. Must match currency. Required when converted_total_amount is provided.
description
string
Human-readable description of the charge. Shown on payment receipts.
reference
string
Your unique reference for this charge. Used for idempotency and reconciliation.
callback_url
string
Canonical redirect URL after payment.
  • Paystack: customer returns here after hosted checkout
  • StartButton: sent through as redirectUrl during payment initialization
  • Stripe: use return_url in confirmPayment() on the client
redirectUrl
string
Camel-case alias for callback_url. Useful for StartButton-oriented integrations.
redirect_url
string
Snake-case alias for callback_url.
customer
object
required
user_country
string
Alias for customer.country. Accepted as a top-level field for storefronts that already send user_country.
customer_country
string
Alias for customer.country. Accepted as a top-level field and inside metadata.
country_code
string
Alias for customer.country. Accepted as a top-level field.
billing_country
string
Billing country fallback when customer.country is absent. Accepted as a top-level field and inside metadata.
shipping_country
string
Shipping country fallback when customer.country is absent. Accepted as a top-level field and inside metadata.
Send any country value you already have. Khaime normalizes common country names and ISO-2 codes, then stores the resolved value on the transaction for customer geography reports. If no country is provided, Stripe/Paystack card country may be used after payment; otherwise the transaction may appear as Unknown in geography analytics.
metadata
object
Custom key-value pairs attached to the charge. Send strings, numbers, or booleans only — nested objects/arrays are not rejected by validation (there is no scalar-type check server-side), but they can fail silently downstream if the resolved gateway’s API rejects non-string metadata values. Treat this as a hard rule, not just a recommendation.
payment_gateway
string
Force a specific gateway instead of Khaime’s automatic currency-based routing (see Gateway Routing below). Only stripe or paystack are accepted — you cannot force startbutton or flutterwave through this field, regardless of currency.
preview
boolean
default:"false"
When true, returns a fee and conversion breakdown without creating any database records or calling any payment gateway. Use this to show the customer exactly what they’ll pay before initiating the real charge.All validation still runs — amount mismatch, currency conversion, sub-merchant, etc. — so a successful preview response means your actual charge will go through.See Preview Mode below for the response shape.

Marketplace Fields

This field is only relevant if your account is set up as a marketplace operator. See Setup Marketplace to enable marketplace mode.
sub_merchant_id
integer
The Khaime educator ID of the sub-merchant this charge is being collected for. Must have active status in your marketplace.When present, the webhook additionally credits the sub-merchant’s wallet for total_amount minus your marketplace commission, in total_currency. Your marketplace wallet is still credited as normal.

How the split works

On webhook receipt after a successful payment:
  1. Your marketplace wallet is credited for amount minus the Khaime platform fee — same as a standard charge.
  2. The sub-merchant wallet is additionally credited for total_amount minus your commission, in total_currency. No additional platform fee is deducted from the sub-merchant — it was already taken on the full charge.
Commission is resolved in this order:
  • Per-merchant commission_rate set via Update Commission
  • Your portfolio default marketplace_commission_rate
  • 0 if neither is set
The sub-merchant wallet transaction stores the full breakdown in conversion_details:
The sub-merchant must have status: active in your marketplace. Pending or suspended sub-merchants will be skipped — your marketplace wallet will still be credited normally.

Response

The response varies based on which gateway Khaime routes to:
Use client_secret and publishable_key to mount Stripe Payment Element.

Gateway Routing

The currency field determines which gateway processes the payment:

Preview Mode

Pass "preview": true in the request body to calculate fees and FX conversion without creating a charge. The response is identical in structure to a normal charge response except:
  • preview: true is set at the top level
  • No charge_id, client_secret, payment_url, or transaction_id — no gateway was called
  • No database records are created
customer and business are always in different currencies when multicurrency is involved — do not compare customer.base_amount with business.priced_amount directly. Use the conversion.exchange_rate to relate them. conversion is only present when currency differs from total_currency.
Typical workflow:

Amount Validation (Double Check)

Both you and Khaime calculate the charge amount independently — they must match. Your workflow:
  1. Call /pricing/calculate with total_amount and target_currency
  2. Use the returned local.amount as your amount in the charge request
  3. Optionally, pass converted_total_amount and converted_total_currency to make your pre-computed conversion explicit
  4. We recalculate using the same conversion service and verify they match
Why double validation? This ensures both parties agree on the conversion. If there’s a mismatch, something is wrong — either a bug, stale rate, or manipulation attempt.
There are two different tolerance checks, easy to conflate: if you supply converted_total_amount, it’s compared against amount with a 1% tolerance. Separately, whenever the server recalculates the expected charge amount itself (the normal path when converted_total_amount is omitted), it uses a wider 2% tolerance to absorb exchange-rate cache drift between your request and Khaime’s own rate refresh.

Error Codes