> ## Documentation Index
> Fetch the complete documentation index at: https://docs.khaime.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Charge

> Create a product-agnostic payment charge.

# 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.

<Note>
  This is the recommended endpoint for plugin integrations. Use [Create Session](/api-reference/payments/create-session) if you need Stripe Checkout hosted pages instead.
</Note>

## Request Body

<ParamField body="amount" type="integer" required>
  Charge amount in smallest currency unit (cents, kobo) — the **customer's payment currency**. **Must be calculated using [/pricing/calculate](/api-reference/products/calculate-pricing)** when currency conversion is involved — we validate it matches our calculation.
</ParamField>

<ParamField body="currency" type="string" required>
  3-letter ISO currency code for what the **customer pays**. Example: `USD`, `NGN`, `GBP`
</ParamField>

<ParamField body="total_amount" type="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 $85 USD with $5 shipping = \$90 total, customer pays ₦144,000 NGN — set `total_amount: 9000`, `total_currency: "USD"`.

  <Note>`original_product_amount` is accepted as a deprecated alias and will continue to work.</Note>
</ParamField>

<ParamField body="total_currency" type="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

  <Note>`original_product_currency` is accepted as a deprecated alias and will continue to work.</Note>
</ParamField>

<ParamField body="converted_total_amount" type="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`.
</ParamField>

<ParamField body="converted_total_currency" type="string">
  The currency of `converted_total_amount`. Must match `currency`. Required when `converted_total_amount` is provided.
</ParamField>

<ParamField body="description" type="string">
  Human-readable description of the charge. Shown on payment receipts.
</ParamField>

<ParamField body="reference" type="string">
  Your unique reference for this charge. Used for idempotency and reconciliation.
</ParamField>

<ParamField body="callback_url" type="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
</ParamField>

<ParamField body="redirectUrl" type="string">
  Camel-case alias for `callback_url`. Useful for StartButton-oriented integrations.
</ParamField>

<ParamField body="redirect_url" type="string">
  Snake-case alias for `callback_url`.
</ParamField>

<ParamField body="customer" type="object" required>
  <Expandable title="Customer fields">
    <ParamField body="email" type="string" required>
      Customer's email address.
    </ParamField>

    <ParamField body="first_name" type="string">
      Customer's first name.
    </ParamField>

    <ParamField body="last_name" type="string">
      Customer's last name.
    </ParamField>

    <ParamField body="country" type="string">
      Customer country code or country name. Recommended for reporting, fee detection, and customer geography analytics. Examples: `GH`, `NG`, `United States`.
    </ParamField>

    <ParamField body="country_code" type="string">
      Alias for `customer.country`. Use this if your checkout already stores ISO-2 country codes as `country_code`.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="user_country" type="string">
  Alias for `customer.country`. Accepted as a top-level field for storefronts that already send `user_country`.
</ParamField>

<ParamField body="customer_country" type="string">
  Alias for `customer.country`. Accepted as a top-level field and inside `metadata`.
</ParamField>

<ParamField body="country_code" type="string">
  Alias for `customer.country`. Accepted as a top-level field.
</ParamField>

<ParamField body="billing_country" type="string">
  Billing country fallback when `customer.country` is absent. Accepted as a top-level field and inside `metadata`.
</ParamField>

<ParamField body="shipping_country" type="string">
  Shipping country fallback when `customer.country` is absent. Accepted as a top-level field and inside `metadata`.
</ParamField>

<Note>
  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.
</Note>

<ParamField body="metadata" type="object">
  Custom key-value pairs attached to the charge. Values must be strings, numbers, or booleans — no nested objects.

  <Expandable title="Example">
    ```json theme={null}
    {
      "wc_order_id": "123",
      "source": "woocommerce",
      "line_items": "[{\"name\":\"Widget\",\"qty\":2}]"
    }
    ```
  </Expandable>
</ParamField>

<ParamField body="preview" type="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](#preview-mode) below for the response shape.
</ParamField>

***

## Marketplace Fields

<Note>
  This field is only relevant if your account is set up as a **marketplace operator**. See [Setup Marketplace](/api-reference/marketplace/setup) to enable marketplace mode.
</Note>

<ParamField body="sub_merchant_id" type="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.
</ParamField>

### 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](/api-reference/marketplace/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`:

```json theme={null}
{
  "source": "marketplace_split",
  "marketplace_id": 15,
  "charge_currency": "NGN",
  "gross_product_amount": 140000,
  "commission_rate": 0.05,
  "commission_amount": 7000,
  "net_credited": 133000,
  "cart_identifier": "intent_abc123"
}
```

<Warning>
  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.
</Warning>

## Response

The response varies based on which gateway Khaime routes to:

<Tabs>
  <Tab title="Stripe">
    ```json theme={null}
    {
      "success": true,
      "data": {
        "charge_id": "charge_a1b2c3d4",
        "payment_gateway": "stripe",
        "client_secret": "pi_3Abc_secret_Xyz",
        "publishable_key": "pk_test_...",
        "stripe_account_id": "acct_1ABC...",
        "transaction_id": "456",
        "fee_details": {
          "base_amount": 5000,
          "transaction_fee": 320,
          "total_amount": 5320,
          "customer_pays_fees": true
        }
      }
    }
    ```

    Use `client_secret` and `publishable_key` to mount [Stripe Payment Element](https://stripe.com/docs/payments/payment-element).
  </Tab>

  <Tab title="Paystack">
    ```json theme={null}
    {
      "success": true,
      "data": {
        "charge_id": "charge_e5f6g7h8",
        "payment_gateway": "paystack",
        "payment_url": "https://checkout.paystack.com/abc123",
        "paystack_reference": "intent_a1b2c3d4",
        "transaction_id": "789",
        "fee_details": {
          "base_amount": 750000,
          "transaction_fee": 48000,
          "total_amount": 798000,
          "customer_pays_fees": true
        }
      }
    }
    ```

    Redirect the customer to `payment_url`. They return to your `callback_url` after payment.
  </Tab>

  <Tab title="StartButton">
    ```json theme={null}
    {
      "success": true,
      "data": {
        "charge_id": "intent_0abc2d3c-558e-4db8-80bf-b2c267203f64",
        "payment_gateway": "startbutton",
        "payment_url": "https://pay-sandbox.startbutton.tech/#/shc-arw6qxtuxa",
        "startbutton_reference": "sb_1776282522145_2lgw9sczh",
        "amount": 25563,
        "currency": "GHS",
        "status": "pending",
        "mode": "live",
        "payment_type": "one_time",
        "description": "Order #2496 from testing-khaime-woocommerce",
        "partner_reference": "wc_2496_xHzrUp9r",
        "transaction_id": 6815,
        "transaction_v2_id": 32875,
        "fee_details": {
          "base_amount": 25563,
          "transaction_fee": 1534,
          "total_amount": 25563,
          "customer_pays_fees": false,
          "is_international": true
        }
      }
    }
    ```

    Redirect the customer to `payment_url`. If you supplied `callback_url`, `redirectUrl`, or `redirect_url`, Khaime forwards the resolved redirect to StartButton and stores it in metadata for downstream processing.
  </Tab>
</Tabs>

## Gateway Routing

The `currency` field determines which gateway processes the payment:

| Currency                     | Gateway     |
| ---------------------------- | ----------- |
| NGN                          | Paystack    |
| GHS, KES, ZAR, TZS, XAF, XOF | StartButton |
| USD, EUR, GBP, CAD, AUD      | Stripe      |

## 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

```json theme={null}
{
  "success": true,
  "message": "Charge preview calculated successfully",
  "data": {
    "preview": true,
    "payment_gateway": "paystack",
    "mode": "sandbox",
    "is_international": true,
    "business_country": "US",
    "customer_country": "NG",
    "customer": {
      "currency": "NGN",
      "base_amount": 1440000,
      "transaction_fee": 92160,
      "total_charged": 1440000,
      "pays_fees": false
    },
    "business": {
      "currency": "USD",
      "priced_amount": 9000
    },
    "conversion": {
      "original_amount": 9000,
      "original_currency": "USD",
      "converted_amount": 1440000,
      "converted_currency": "NGN",
      "exchange_rate": 1600
    }
  }
}
```

<Note>
  `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`.
</Note>

**Typical workflow:**

```
1. POST /payments/charge  { preview: true, ...fields }
   → Show breakdown to customer
2. Customer confirms
3. POST /payments/charge  { ...same fields, no preview }
   → Actual charge created, payment URL / client secret returned
```

***

## Amount Validation (Double Check)

Both you and Khaime calculate the charge amount independently — they must match.

**Your workflow:**

1. Call [/pricing/calculate](/api-reference/products/calculate-pricing) 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 (1% tolerance for rounding)

**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.

```bash theme={null}
# Step 1: Get the converted amount
curl "https://api.khaime.com/api/v1/partner/pricing/calculate?amount=9000&target_currency=NGN" \
  -H "X-API-Key: pk_sandbox_your_key"
# Returns: { "local": { "amount": 1440000, "currency": "NGN" } }

# Step 2: Use that amount in your charge
curl -X POST "https://api.khaime.com/api/v1/partner/payments/charge" \
  -H "X-API-Key: pk_sandbox_your_key" \
  -d '{
    "amount": 1440000,
    "currency": "NGN",
    "total_amount": 9000,
    "total_currency": "USD",
    "converted_total_amount": 1440000,
    "converted_total_currency": "NGN",
    ...
  }'
```

## Error Codes

| Status | Error                                                                                | Fix                                                                                                                     |
| ------ | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `400`  | `Amount mismatch`                                                                    | Your `amount` doesn't match our calculation. Use `/pricing/calculate` to get the correct amount.                        |
| `400`  | `converted_total_amount does not match amount`                                       | Your pre-computed conversion differs from `amount`. Ensure both represent the charge total in the same currency.        |
| `400`  | `converted_total_currency must match the charge currency`                            | `converted_total_currency` must equal `currency`.                                                                       |
| `400`  | `Both converted_total_amount and converted_total_currency must be provided together` | Supply both fields or neither.                                                                                          |
| `422`  | `"amount" is required`                                                               | Include `amount` — use `/pricing/calculate` to convert `total_amount` to the customer's currency.                       |
| `422`  | Missing `total_amount` / `total_currency`                                            | Include `total_amount` and `total_currency` (or their aliases `original_product_amount` / `original_product_currency`). |
| `400`  | `Unable to convert from X to Y`                                                      | Currency conversion failed — try again or use a supported currency.                                                     |
| `400`  | Missing required fields                                                              | Include `amount`, `currency`, `customer.email`.                                                                         |
| `401`  | Invalid API key                                                                      | Check your `X-API-Key` header.                                                                                          |
| `422`  | Metadata validation failed                                                           | Ensure metadata values are scalar (no nested objects).                                                                  |

<RequestExample>
  ```bash Preview charge (fee breakdown only) theme={null}
  curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
    -H "X-API-Key: pk_sandbox_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "preview": true,
      "amount": 1440000,
      "currency": "NGN",
      "total_amount": 9000,
      "total_currency": "USD",
      "description": "Design consultation",
      "customer": {
        "email": "jane@example.com",
        "first_name": "Jane",
        "last_name": "Doe",
        "country": "NG"
      }
    }'
  ```

  ```bash Standard charge (same currency) theme={null}
  curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
    -H "X-API-Key: pk_sandbox_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 5000,
      "currency": "USD",
      "total_amount": 5000,
      "total_currency": "USD",
      "description": "Order #123",
      "reference": "wc_123_abc",
      "callback_url": "https://store.example.com/order-received/123",
      "customer": {
        "email": "jane@example.com",
        "first_name": "Jane",
        "last_name": "Doe",
        "country": "US"
      },
      "metadata": {
        "wc_order_id": "123",
        "source": "woocommerce"
      }
    }'
  ```

  ```bash Multicurrency (merchant prices in USD, customer pays NGN) theme={null}
  # First: GET /pricing/calculate?amount=9000&target_currency=NGN → returns 1440000
  curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
    -H "X-API-Key: pk_sandbox_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 1440000,
      "currency": "NGN",
      "total_amount": 9000,
      "total_currency": "USD",
      "converted_total_amount": 1440000,
      "converted_total_currency": "NGN",
      "description": "Design consultation + shipping",
      "reference": "consult_0042",
      "customer": {
        "email": "client@example.com",
        "first_name": "Emeka",
        "last_name": "Eze",
        "country": "NG"
      },
      "metadata": {
        "service_type": "design_consultation"
      }
    }'
  ```

  ```bash Marketplace (NGN customer, NGN sub-merchant) theme={null}
  curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
    -H "X-API-Key: pk_sandbox_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 140000,
      "currency": "NGN",
      "total_amount": 140000,
      "total_currency": "NGN",
      "description": "Handmade leather bag - Ade Leatherworks",
      "reference": "order_8821",
      "sub_merchant_id": 42,
      "customer": {
        "email": "buyer@example.com",
        "first_name": "Tunde",
        "last_name": "Bello",
        "country": "NG"
      },
      "metadata": {
        "order_id": "8821",
        "vendor": "ade-leatherworks"
      }
    }'
  ```

  ```bash Marketplace (NGN customer, USD sub-merchant) theme={null}
  # First: GET /pricing/calculate?amount=9000&target_currency=NGN → returns 1440000
  curl -X POST https://api.khaime.com/api/v1/partner/payments/charge \
    -H "X-API-Key: pk_sandbox_your_key" \
    -H "Content-Type: application/json" \
    -d '{
      "amount": 1440000,
      "currency": "NGN",
      "total_amount": 9000,
      "total_currency": "USD",
      "converted_total_amount": 1440000,
      "converted_total_currency": "NGN",
      "description": "Design consultation - Jane Designs",
      "reference": "consult_0043",
      "sub_merchant_id": 77,
      "customer": {
        "email": "client@example.com",
        "first_name": "Emeka",
        "last_name": "Eze",
        "country": "NG"
      },
      "metadata": {
        "service_type": "design_consultation"
      }
    }'
  ```
</RequestExample>
