# Documentation (/docs)
Kashier lets you accept payments in Egypt through a simple API, a hosted
checkout, ready-made store plugins, and in-person terminals. Everything can be
tested for free in the test environment before you go live.
## Which integration should I use? [#which-integration-should-i-use]
| Integration path | When to use it | Time to launch | Technical level |
| -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -------------------------------------- |
| [Payment sessions](/docs/accept-payments/payment-sessions) / [embedded checkout](/docs/accept-payments/payment-sessions#step-2-send-the-customer-to-pay) | A checkout page hosted by Kashier, or embedded in your own page, reached by a single backend API call | Hours to days — needs a backend call to create a payment session | Low |
| [Direct API integration](/docs/direct-api) | Full control over your own card form, saved-card tokens, 3D Secure, wallets, and installments | Days — the most integration work of any path here | High — also changes your PCI DSS scope |
| [E-commerce plugins](/docs/plugins) | You run WooCommerce, Shopify, Magento, PrestaShop, or another supported store platform and want no custom code | Fastest — install and configure a pre-built plugin | None to low |
| [In-person payments (POS)](/docs/pos) | You want to drive Kashier POS terminals from your own Android app or backend | Requires physical terminals plus setup (IP whitelisting, APK signing for app-to-app) | Medium to high |
Need something in between? [Payment links](/docs/accept-payments/payment-links) let you accept a payment with no code at all, and [recurring payments](/docs/accept-payments/recurring) charge customers on a schedule.
## Choose your integration path [#choose-your-integration-path]
## Environments [#environments]
Kashier has separate test and live environments. Use the test environment with
test API keys while you build — no real money moves. See
[API keys](/docs/get-started/api-keys) for how the two modes work.
# Accounts and payout methods (/docs/account-and-balance/accounts)
Every merchant starts with one **primary account**. Payout-destination details — bank name, account number, account holder name, and so on — are not a separate "beneficiary" object anywhere in the API. They're simply fields on an **Account's `payoutMethod`**. If you're looking for a beneficiary resource to create or manage, this is it: manage the `payoutMethod` on an account instead.
You can also create **additional accounts** beyond the primary one, each with its own independent `payoutMethod`. This page covers creating and managing accounts and their payout methods; for the immutable balance ledger and holds, see [Balance ledger and holds](/docs/account-and-balance/balance).
`GET`/`PUT /v2/account/payoutMethod` always operate on your **primary** account specifically. To manage an additional (non-primary) account's payout method — or any other field on any account — use `GET`/`PUT /v2/account/:accountId` with that account's `accountId` instead.
There is no endpoint to delete an account or a payout method. Once created, an account persists; you can edit its `payoutMethod` and other fields, but not remove the account itself.
## Create an additional account [#create-an-additional-account]
Creates a new, non-primary account for your merchant. There's no cap on the number of accounts you can create today — don't assume there's a limit when designing your integration.
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/account](https://test-api.kashier.io/v2/account) |
| LIVE-URL | [https://api.kashier.io/v2/account](https://api.kashier.io/v2/account) |
| Method | POST |
### Body parameters [#body-parameters]
| Key | Description |
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| accountName | Name for the new account. |
| payoutMethod | Object describing the payout destination. No field inside it is required — you can create the account first and set its payout method later via [Edit an account](#edit-an-account). |
| payoutMethod.method | Identifies the payout rail, e.g. a bank transfer vs. a mobile wallet. |
| payoutMethod.accountHolderName | Name on the payout destination. Accepts Arabic and Latin letters, digits, spaces, periods, hyphens, and apostrophes; max 70 characters. |
| payoutMethod.accountNumber | Bank account number or wallet number. |
| payoutMethod.bankName | Bank name. |
| payoutMethod.bankAbbreviation | Bank abbreviation/code. |
| payoutMethod.bankBranchName | Bank branch name. |
| payoutMethod.bankBranchCode | Bank branch code. |
```bash
curl --location 'https://test-api.kashier.io/v2/account' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"accountName": "Second Store Account",
"payoutMethod": {
"method": "bank",
"accountHolderName": "Ahmed Hassan",
"accountNumber": "1234567890",
"bankName": "National Bank of Egypt",
"bankAbbreviation": "NBE",
"bankBranchName": "Downtown",
"bankBranchCode": "001"
}
}'
```
### Headers [#headers]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response]
```json
{
"accountId": "ACC-46254-582-02",
"accountName": "Second Store Account",
"merchantId": "MID-46254-582",
"isPrimary": false,
"totalBalance": 0,
"availableBalance": 0,
"onHoldBalance": 0,
"payoutFees": 5,
"isIncludeInBulkTransfer": true,
"payoutMethod": {
"method": "bank",
"accountHolderName": "Ahmed Hassan",
"accountNumber": "1234567890",
"bankName": "National Bank of Egypt",
"bankAbbreviation": "NBE",
"bankBranchName": "Downtown",
"bankBranchCode": "001"
},
"createdAt": "2026-06-18T10:30:00.000Z",
"updatedAt": "2026-06-18T10:30:00.000Z"
}
```
| Field | Description |
| ----------------------------------------------------- | ------------------------------------------------------------------------------- |
| `accountId` | Unique identifier for the new account. |
| `accountName` | The account's name. |
| `merchantId` | The merchant the account belongs to. |
| `isPrimary` | Always `false` for an account created through this endpoint. |
| `totalBalance` / `availableBalance` / `onHoldBalance` | Running balances for the new account — all `0` until it starts receiving funds. |
| `payoutFees` | Flat fee applied per payout for this account. |
| `isIncludeInBulkTransfer` | Whether the account is eligible for the recurring payout run. |
| `payoutMethod` | The payout destination you supplied, echoed back. |
| `createdAt` / `updatedAt` | Timestamps. |
## Get account info [#get-account-info]
Fetch your **primary** account — a GET request to `/v2/account`, with no account ID needed. To fetch a non-primary account, or to look up the primary account by its `accountId`, use [Get an account](#get-an-account) below instead.
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/account](https://test-api.kashier.io/v2/account) |
| LIVE-URL | [https://api.kashier.io/v2/account](https://api.kashier.io/v2/account) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/account' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-1]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-1]
Full parameter and response reference → [Get account info](/docs/api-reference/payouts/getAccountInfo).
## Get an account [#get-an-account]
Fetch a single account by ID, including its `payoutMethod`.
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/account/:accountId](https://test-api.kashier.io/v2/account/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/account/:accountId](https://api.kashier.io/v2/account/:accountId) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/account/:accountId' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-2]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-2]
```json
{
"accountId": "ACC-46254-582-02",
"accountName": "Second Store Account",
"merchantId": "MID-46254-582",
"isPrimary": false,
"totalBalance": 12500,
"availableBalance": 12000,
"onHoldBalance": 500,
"allowedNegativeBalance": 0,
"payoutFees": 5,
"isIncludeInBulkTransfer": true,
"payoutMethod": {
"method": "bank",
"accountHolderName": "Ahmed Hassan",
"accountNumber": "1234567890",
"bankName": "National Bank of Egypt",
"bankAbbreviation": "NBE",
"bankBranchName": "Downtown",
"bankBranchCode": "001"
},
"createdAt": "2026-06-18T10:30:00.000Z",
"updatedAt": "2026-06-20T09:15:00.000Z"
}
```
Fields match the create response above, plus `allowedNegativeBalance` (how far the account may go negative before further deductions are blocked).
## Edit an account [#edit-an-account]
Update an account's name and/or payout method.
This endpoint requires OTP verification. Call it without an `x-otp` header first to trigger the code, then retry the same request with `x-otp` set to the code you received.
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/account/:accountId](https://test-api.kashier.io/v2/account/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/account/:accountId](https://api.kashier.io/v2/account/:accountId) |
| Method | PUT |
### Body parameters [#body-parameters-1]
| Key | Description |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| accountName | New name for the account. |
| payoutMethod | New payout method for the account — same shape as [Create an additional account](#create-an-additional-account). Sending `payoutMethod` **replaces it wholesale**; it isn't merged field by field. |
```bash
curl --location --request PUT 'https://test-api.kashier.io/v2/account/:accountId' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--header 'x-otp: 123456' \
--data '{
"accountName": "Second Store Account (renamed)",
"payoutMethod": {
"accountHolderName": "Ahmed Hassan",
"accountNumber": "9876543210",
"bankName": "National Bank of Egypt",
"bankAbbreviation": "NBE",
"bankBranchName": "Maadi",
"bankBranchCode": "014"
}
}'
```
### Headers [#headers-3]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
| x-otp | The OTP code, once you've received one. Omit it on the first call to trigger generation. |
### Response [#response-3]
Returns the updated account in the same shape as [Get an account](#get-an-account).
## Get the primary account's payout method [#get-the-primary-accounts-payout-method]
Returns the `payoutMethod` for your **primary** account only.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/account/payoutMethod](https://test-api.kashier.io/v2/account/payoutMethod) |
| LIVE-URL | [https://api.kashier.io/v2/account/payoutMethod](https://api.kashier.io/v2/account/payoutMethod) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/account/payoutMethod' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-4]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-4]
```json
{
"method": "bank",
"accountHolderName": "Ahmed Hassan",
"accountNumber": "1234567890",
"bankName": "National Bank of Egypt",
"bankAbbreviation": "NBE",
"bankBranchName": "Downtown",
"bankBranchCode": "001"
}
```
## Update the primary account's payout method [#update-the-primary-accounts-payout-method]
Replaces the `payoutMethod` on your **primary** account. The request body **is** the payout method object — there's no wrapper.
This endpoint requires OTP verification, the same way [Edit an account](#edit-an-account) does: call it without `x-otp` to trigger a code, then retry with `x-otp` set.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/account/payoutMethod](https://test-api.kashier.io/v2/account/payoutMethod) |
| LIVE-URL | [https://api.kashier.io/v2/account/payoutMethod](https://api.kashier.io/v2/account/payoutMethod) |
| Method | PUT |
### Body parameters [#body-parameters-2]
| Key | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| method | Identifies the payout rail, e.g. a bank transfer vs. a mobile wallet. |
| accountHolderName | Name on the payout destination. Accepts Arabic and Latin letters, digits, spaces, periods, hyphens, and apostrophes; max 70 characters. |
| accountNumber | Bank account number or wallet number. |
| bankName | Bank name. |
| bankAbbreviation | Bank abbreviation/code. |
| bankBranchName | Bank branch name. |
| bankBranchCode | Bank branch code. |
No field is required — this is the same shared payout-method shape used across account creation and editing, and it's replaced wholesale on every update, so send the full object.
```bash
curl --location --request PUT 'https://test-api.kashier.io/v2/account/payoutMethod' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'Content-Type: application/json' \
--header 'x-otp: 123456' \
--data '{
"method": "bank",
"accountHolderName": "Ahmed Hassan",
"accountNumber": "1234567890",
"bankName": "National Bank of Egypt",
"bankAbbreviation": "NBE",
"bankBranchName": "Downtown",
"bankBranchCode": "001"
}'
```
This endpoint replaces your primary account's payout method wholesale, and Kashier's internal sources currently disagree on the exact field names it expects. Rather than offer a Send button that could overwrite a working payout destination with a half-right body, this page documents the call only. Build the request from the cURL above and confirm the field names against a `GET /v2/account/payoutMethod` response for your own account first.
### Headers [#headers-5]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
| x-otp | The OTP code, once you've received one. Omit it on the first call to trigger generation. |
### Response [#response-5]
Returns the updated `payoutMethod` in the same shape as [Get the primary account's payout method](#get-the-primary-accounts-payout-method).
## Get account records [#get-account-records]
Returns a paginated feed of account-level activity for a given account — broader than the balance ledger (see [Balance ledger and holds](/docs/account-and-balance/balance)), since it isn't limited to balance-affecting events.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/account/:accountId/records](https://test-api.kashier.io/v2/account/:accountId/records) |
| LIVE-URL | [https://api.kashier.io/v2/account/:accountId/records](https://api.kashier.io/v2/account/:accountId/records) |
| Method | GET |
### Query parameters [#query-parameters]
| Key | Description |
| ----- | ---------------------------------------- |
| page | Page number for pagination. Example: `1` |
| limit | Records per page. Example: `10` |
```bash
curl --location 'https://test-api.kashier.io/v2/account/:accountId/records?page=1&limit=10' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-6]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-6]
```json
{
"data": [
{ "...": "one entry per account-level activity item" }
],
"total": 12,
"page": 1,
"limit": 10
}
```
`data` is paginated the same way as the balance ledger (`total`/`page`/`limit`). The shape of an individual entry depends on the kind of activity it represents; use the "Try it" panel above against a test account to inspect a live response.
## Export account records [#export-account-records]
The same account activity feed as a file download.
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/account/:accountId/records/export](https://test-api.kashier.io/v2/account/:accountId/records/export) |
| LIVE-URL | [https://api.kashier.io/v2/account/:accountId/records/export](https://api.kashier.io/v2/account/:accountId/records/export) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/account/:accountId/records/export' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--output account-records.xlsx
```
### Headers [#headers-7]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
## Account overview [#account-overview]
Returns an overview for a single account. Merchant-only (not callable by agents).
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/account/overview/:accountId](https://test-api.kashier.io/v2/account/overview/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/account/overview/:accountId](https://api.kashier.io/v2/account/overview/:accountId) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/account/overview/:accountId' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-8]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-7]
Returns a `200` with a JSON object summarizing the given account. The exact field set isn't
confirmed here — use the "Try it" panel above against a test account to inspect a live response.
## List accounts overview [#list-accounts-overview]
Returns an overview covering all of your accounts at once — useful for a dashboard-style summary instead of fetching each account individually. Merchant-only (not callable by agents).
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/account/overview/accounts-list](https://test-api.kashier.io/v2/account/overview/accounts-list) |
| LIVE-URL | [https://api.kashier.io/v2/account/overview/accounts-list](https://api.kashier.io/v2/account/overview/accounts-list) |
| Method | GET |
### Query parameters [#query-parameters-1]
| Key | Description |
| ----- | ---------------------------------------- |
| page | Page number for pagination. Example: `1` |
| limit | Accounts per page. Example: `10` |
```bash
curl --location 'https://test-api.kashier.io/v2/account/overview/accounts-list?page=1&limit=10' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-9]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-8]
Returns a `200` with a paginated JSON object — `page`/`limit` mirror the query parameters above,
the same pagination envelope used elsewhere in this API. The exact per-account field set isn't
confirmed here — use the "Try it" panel above against a test account to inspect a live response.
## Payments overview [#payments-overview]
Returns a payments summary for a given account over a date range. Merchant-only (not callable by agents).
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/account/overview/payments/:accountId](https://test-api.kashier.io/v2/account/overview/payments/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/account/overview/payments/:accountId](https://api.kashier.io/v2/account/overview/payments/:accountId) |
| Method | GET |
### Query parameters [#query-parameters-2]
| Key | Description |
| -------- | --------------------------------------------- |
| dateFrom | Start of the summary window, as `YYYY-MM-DD`. |
| dateTo | End of the summary window, as `YYYY-MM-DD`. |
| page | Page number for pagination. Example: `1` |
| limit | Items per page. Example: `20` |
The window below is deliberately wide so it covers whatever history your account already has — narrow it to the range you actually want to summarize.
```bash
curl --location 'https://test-api.kashier.io/v2/account/overview/payments/:accountId?dateFrom=2020-01-01&dateTo=2030-12-31' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-10]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-9]
Returns a `200` with a JSON object summarizing payments on the account over the requested date
range. The exact field set isn't confirmed here — use the "Try it" panel above against a test
account to inspect a live response.
## Payouts overview [#payouts-overview]
Returns a payouts summary for a given account over a date range. Merchant-only (not callable by agents).
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/account/overview/payouts/:accountId](https://test-api.kashier.io/v2/account/overview/payouts/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/account/overview/payouts/:accountId](https://api.kashier.io/v2/account/overview/payouts/:accountId) |
| Method | GET |
### Query parameters [#query-parameters-3]
| Key | Description |
| -------- | --------------------------------------------- |
| dateFrom | Start of the summary window, as `YYYY-MM-DD`. |
| dateTo | End of the summary window, as `YYYY-MM-DD`. |
| page | Page number for pagination. Example: `1` |
| limit | Items per page. Example: `20` |
As with the payments overview, the window below is deliberately wide — narrow it to the range you actually want.
```bash
curl --location 'https://test-api.kashier.io/v2/account/overview/payouts/:accountId?dateFrom=2020-01-01&dateTo=2030-12-31' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-11]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-10]
Returns a `200` with a JSON object summarizing payouts on the account over the requested date
range. The exact field set isn't confirmed here — use the "Try it" panel above against a test
account to inspect a live response.
# Balance ledger and holds (/docs/account-and-balance/balance)
Kashier keeps an immutable ledger of every balance-affecting event on your account — settlements, payouts, refunds, and adjustments each post a record. Use the balance ledger to reconcile your Kashier balance against your own books, and use the holds endpoint to see why your available balance can be lower than your total balance.
This page covers the balance ledger under `/v2/balance/*` — immutable balance-affecting records (settlements, payouts, adjustments) plus holds. A separate, broader account activity feed also exists — see [Get account records](/docs/account-and-balance/accounts#get-account-records) on the [Accounts and payout methods](/docs/account-and-balance/accounts) page. Don't confuse the two — they return different record shapes for different purposes.
## Get balance records (the ledger) [#get-balance-records-the-ledger]
Returns a paginated list of balance-affecting records for an account — the core ledger.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/balance/records/:accountId](https://test-api.kashier.io/v2/balance/records/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/balance/records/:accountId](https://api.kashier.io/v2/balance/records/:accountId) |
| Method | GET |
### Query parameters [#query-parameters]
| Key | Description |
| -------- | --------------------------------------------------------- |
| page | Page number for pagination. Example: `1` |
| limit | Records per page. Example: `10` |
| dateFrom | Start date for a date-range filter. Example: `2026-01-01` |
| dateTo | End date for a date-range filter. Example: `2026-06-30` |
```bash
curl --location 'https://test-api.kashier.io/v2/balance/records/:accountId?page=1&limit=10' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response]
```json
{
"data": [
{
"accountId": "ACC-39550-436-01",
"amount": 500,
"originalAmount": 500,
"fees": 0,
"operation": "manualAdjustment",
"origin": "operations team",
"originReference": "REF-12345",
"comment": "Manual adjustment",
"valueDate": "2026-06-18",
"isReflected": true,
"totalBalanceBefore": 12000,
"totalBalanceAfter": 12500,
"createdAt": "2026-06-18T15:30:00Z"
},
{
"accountId": "ACC-39550-436-01",
"amount": 1000,
"originalAmount": 1015,
"fees": 15,
"operation": "settlement",
"origin": "settlements",
"originReference": "STW-88213",
"comment": "Settlement",
"valueDate": "2026-06-18",
"isReflected": true,
"totalBalanceBefore": 11000,
"totalBalanceAfter": 12000,
"createdAt": "2026-06-18T14:00:00Z"
}
],
"total": 2,
"page": 1,
"limit": 10
}
```
| Field | Description |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data[].accountId` | The account the record was posted against (`ACC-` format). |
| `data[].amount` | Net amount after fees — this is what moves the balance. |
| `data[].originalAmount` | Gross amount before fees. |
| `data[].fees` | Fees deducted from `originalAmount`. |
| `data[].operation` | What generated the record. Free text; values in use include `refund`, `settlement`, `payout`, `deduct`, `transfer` and `manualAdjustment`. |
| `data[].origin` | The service that posted it, e.g. `settlements`, `transfers`, `instant settlement`. |
| `data[].originReference` | The originating reference (`TRX-`, `STW-`, `STB-`, `ISR-`, `TRS-`…). Together with `accountId`, `operation` and `origin` this forms the idempotency key. |
| `data[].comment` | Free-text description of what generated the record. |
| `data[].valueDate` | The date the record is value-dated against. |
| `data[].isReflected` | `false` while the entry is future-dated and not yet applied to the balance. |
| `data[].totalBalanceBefore` / `totalBalanceAfter` | Balance snapshots either side of the entry. |
| `data[].createdAt` | When the record was created. |
| `total` | Total number of records matching the filter. |
| `page` | Current page number. |
| `limit` | Records per page. |
## Get balance record details [#get-balance-record-details]
Fetch the full detail of a single ledger record by its ID — use the `id` returned from the list above.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/balance/record-details/:recordId](https://test-api.kashier.io/v2/balance/record-details/:recordId) |
| LIVE-URL | [https://api.kashier.io/v2/balance/record-details/:recordId](https://api.kashier.io/v2/balance/record-details/:recordId) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/balance/record-details/:recordId' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-1]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-1]
```json
{
"accountId": "ACC-39550-436-01",
"amount": 500,
"originalAmount": 500,
"fees": 0,
"operation": "manualAdjustment",
"origin": "operations team",
"originReference": "REF-12345",
"comment": "Manual adjustment",
"valueDate": "2026-06-18",
"totalBalanceBefore": 12000,
"totalBalanceAfter": 12500,
"metaData": { "fees": 0, "vat": 0 },
"createdAt": "2026-06-18T15:30:00Z"
}
```
Fields match the ledger list above, plus `metaData`, which carries the fee/VAT breakdown for the record.
## Get payout details [#get-payout-details]
Given a ledger record that represents a payout, list the underlying transactions that were bundled into it.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/balance/payout-details/:recordId](https://test-api.kashier.io/v2/balance/payout-details/:recordId) |
| LIVE-URL | [https://api.kashier.io/v2/balance/payout-details/:recordId](https://api.kashier.io/v2/balance/payout-details/:recordId) |
| Method | GET |
### Query parameters [#query-parameters-1]
| Key | Description |
| --------- | --------------------------------------------------------- |
| page | Page number for pagination. Example: `1` |
| limit | Records per page. Example: `10` |
| sortBy | Field to sort by. Example: `transactionDate` |
| sortOrder | Sort order, `asc` or `desc`. Example: `desc` |
| status | Filter transactions by status. Example: `completed` |
| dateFrom | Start date for a date-range filter. Example: `2026-01-01` |
| dateTo | End date for a date-range filter. Example: `2026-06-30` |
```bash
curl --location 'https://test-api.kashier.io/v2/balance/payout-details/:recordId?page=1&limit=10' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-2]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-2]
```json
{
"recordId": "507f1f77bcf86cd799439029",
"accountId": "507f1f77bcf86cd799439012",
"amount": 5000,
"status": "processed",
"payoutDate": "2026-06-18",
"transactions": [
{
"transactionId": "txn_001",
"amount": 1000,
"status": "settled",
"settleDate": "2026-06-18"
}
],
"totalTransactions": 5
}
```
### Export payout details [#export-payout-details]
The same underlying data is also available as a file download.
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/balance/payout-details/:recordId/export](https://test-api.kashier.io/v2/balance/payout-details/:recordId/export) |
| LIVE-URL | [https://api.kashier.io/v2/balance/payout-details/:recordId/export](https://api.kashier.io/v2/balance/payout-details/:recordId/export) |
| Method | GET |
| Key | Description |
| -------- | --------------------------------------------------------- |
| status | Filter transactions by status. Example: `completed` |
| dateFrom | Start date for a date-range filter. Example: `2026-01-01` |
| dateTo | End date for a date-range filter. Example: `2026-06-30` |
```bash
curl --location 'https://test-api.kashier.io/v2/balance/payout-details/:recordId/export' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--output payout-details.xlsx
```
## Get account holds [#get-account-holds]
Returns holds placed on an account. Holds reduce your **available** balance without changing your **total** balance — this is why the amount you can withdraw or use can be less than your account's total balance.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/hold/account/:accountId](https://test-api.kashier.io/v2/hold/account/:accountId) |
| LIVE-URL | [https://api.kashier.io/v2/hold/account/:accountId](https://api.kashier.io/v2/hold/account/:accountId) |
| Method | GET |
### Query parameters [#query-parameters-2]
| Key | Description |
| ------ | ------------------------------------------------------- |
| page | Page number for pagination. Example: `1` |
| limit | Records per page. Example: `10` |
| status | Filter by hold status: `HELD`, `RELEASED` or `DEDUCTED` |
```bash
curl --location 'https://test-api.kashier.io/v2/hold/account/:accountId?page=1&limit=10' \
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-3]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
### Response [#response-3]
```json
{
"data": [
{
"holdId": "HLD-39550-436-0007",
"accountId": "ACC-39550-436-01",
"amount": 1000,
"status": "HELD",
"valueDate": "2026-06-18",
"createdAt": "2026-06-18T10:30:00Z"
}
],
"total": 1,
"page": 1,
"limit": 10
}
```
| Field | Description |
| ------------------ | ------------------------------------------------------- |
| `data[].holdId` | Unique identifier for the hold (`HLD-` format). |
| `data[].accountId` | The account the hold is placed against (`ACC-` format). |
| `data[].amount` | The amount held. |
| `data[].status` | Hold lifecycle state: `HELD`, `RELEASED` or `DEDUCTED`. |
| `data[].valueDate` | The date the hold is value-dated against. |
| `data[].createdAt` | When the hold was created. |
The `status` filter accepts the hold-lifecycle values `HELD`, `RELEASED` and `DEDUCTED`. A hold starts `HELD`; releasing it returns the amount to the available balance, deducting it removes the amount for good.
# Accounts and balance (/docs/account-and-balance)
Every merchant has a balance and at least one account. This section covers the two resources behind them: **accounts**, which hold your payout methods and can be created beyond the primary one, and the **balance ledger**, an immutable record of every balance-affecting event on your account.
# API reference and playground (/docs/api-reference)
Save your test keys once. Every endpoint page then fills its own auth fields and
request body, so you can press **Send** and read the live response.
## Check your setup [#check-your-setup]
## Endpoints [#endpoints]
## Good to know [#good-to-know]
* Requests go straight from your browser to Kashier — the documented URLs
point at the test hosts, and your keys never reach this site's servers.
* Keys stay in your browser's localStorage and travel only with requests you fire.
* Each endpoint renders your request as cURL, JavaScript, Python, PHP and more.
* Everything here is generated from the [OpenAPI spec](/openapi.yaml) — point
your tooling or AI agent at it.
* New to the API? The [quick start](/docs/get-started/quickstart) walks one
payment end to end.
# Apple Pay (/docs/accept-payments/apple-pay)
Add an Apple Pay button to your website with the Kashier SDK. Customers on Apple devices pay directly from your checkout page.
An Apple Pay payment settles over the card rail (MPGS), so refunds, void and authorize/capture behave the same as a card payment. `enable3DS` is forced to `false` — the device cryptogram already carries the authentication, so no separate 3D Secure step runs. Google Pay is not supported.
## Prerequisites [#prerequisites]
Before implementing the Kashier Apple Pay integration, ensure you have:
1. A website with HTTPS enabled
2. Provided your website domain to Kashier's team so it can be verified with Apple
3. The Kashier SDK script
## Step 1: Domain registration [#step-1-domain-registration]
Provide your website domain to the Kashier support team for certification with Apple.
## Step 2: Certificate installation [#step-2-certificate-installation]
After receiving the Apple Pay certificate from Kashier:
1. Save the certificate file as `apple-developer-merchantid-domain-association.txt`
2. Create a `.well-known` directory in your web server's root directory
3. Upload the certificate file to this directory
4. Ensure the file is accessible at: `https://your-domain.com/.well-known/apple-developer-merchantid-domain-association.txt`
## Implementation [#implementation]
After receiving the Apple Pay certificate from Kashier:
### Basic integration [#basic-integration]
Add the following code to your checkout page:
```html
```
### HTML elements [#html-elements]
| Element ID | Required | Description |
| ------------------ | -------- | ---------------------------------------------------------------- |
| kashier-sdk-id | Yes | Container where the Apple Pay button will be rendered |
| kashier-success-id | No | Optional container for displaying successful payment information |
| kashier-failure-id | No | Optional container for displaying payment failure information |
## Configuration options [#configuration-options]
The `kashierSDK.load()` method accepts a configuration object with the following properties:
### Required parameters [#required-parameters]
| Parameter | Type | Description |
| -------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| data.mode | String | Environment mode: 'test' or 'live' |
| data.sessionId | String | Unique payment session identifier provided by Kashier's payment session creation API. See [payment sessions](/docs/accept-payments/payment-sessions). |
### Optional parameters [#optional-parameters]
| Parameter | Type | Description |
| --------- | -------- | ---------------------------------------------------------------------- |
| locale | String | Language for the Apple Pay button: `'en'` (English) or `'ar'` (Arabic) |
| onSuccess | Function | Callback function that executes after successful payment completion |
| onFailure | Function | Callback function that executes after payment failure |
## Support [#support]
For technical support, contact Kashier support at:
* Email: [techsupport@kashier.io](mailto:techsupport@kashier.io)
# Authorize and capture (/docs/accept-payments/authorize-capture)
You can use an authorized transaction to hold the amount of the order or service. You can either manually capture a partial or full amount of the order payment. Order amounts can also be released back to customers.
## How it works [#how-it-works]
* You will be notified when the order has been authorized via your dashboard, and in the case of [Payment Sessions](/docs/accept-payments/payment-sessions), the Status response will be AUTHORIZED, and the webhook event will be "authorize."
* You can use the manual release or manual capture operations in the endpoint.
Approval is needed. The Authorization Capture feature can be enabled by contacting your account manager or customer success.
## Capture [#capture]
The amount of the order is either captured fully or partially. Capture is done by sending a PUT request with the order ID `orderId` in the URL as a parameter, and inserting a transaction ID and amount in the body.
In case you are still in the development phase, you will need to call our API using the following testing endpoint API URL:
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------ |
| URL | [https://test-fep.kashier.io/v3/orders/:orderId](https://test-fep.kashier.io/v3/orders/:orderId) |
| Method | PUT |
Meanwhile, whenever you are ready for production, you should use the following production API endpoint URL instead:
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------- |
| URL | [https://fep.kashier.io/v3/orders/:orderId](https://fep.kashier.io/v3/orders/:orderId) |
| Method | PUT |
### Headers [#headers]
| Key | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Capture, void and refund authenticate with the `Authorization` **secret key** — the same credential the [Dashboard API](/docs/dashboard-api/authentication) uses. That differs from the pay call on the same host: creating a payment (`POST /v3/orders`) is gated by a [`Kashier-Hash`](/docs/direct-api/hashing) header and takes no secret key. Kashier publishes no `Kashier-Hash` string-to-sign for `PUT /v3/orders/:orderId`, so there is nothing to compute here. Sending the header anyway is what some older integrations do and we have no report of it being rejected, but that is unconfirmed — if an update ever fails with `INVALID_HASH_CHECK`, raise it with Kashier support.
```bash
curl -X 'PUT' 'https://test-fep.kashier.io/v3/orders/:orderId'
-H 'Authorization: your_secretKey'
-H 'accept: application/json'
-H 'Content-Type: application/json'
-d '{"apiOperation":"CAPTURE","transaction":{"amount":3}}'
```
### Body structure [#body-structure]
```json
{
"apiOperation": "CAPTURE",
"transaction": {
"amount": 3
}
}
```
Full parameter and response reference → [Refund, void, or capture an order](/docs/api-reference/order-operations/updateOrder).
Capture an authorized amount as soon as you are ready to take the money. An authorization is a hold placed by the issuing bank, and holds do not last indefinitely — how long yours stays capturable depends on the issuer and on your account configuration, so confirm the window that applies to you with your Kashier account manager rather than assuming one.
## Release [#release]
Releasing an authorized amount is a **void** of the authorize transaction — there is no separate release operation. Send a PUT request to the same endpoint with `apiOperation` set to `VOID`, the order ID `orderId` in the URL, and the `transactionId` of the authorized transaction as `transaction.targetTransactionId` in the body. Include `transaction.amount` to release only part of the hold.
The `transactionId` is returned in the callback URL, webhook, and response body.
```json
{
"apiOperation": "VOID",
"transaction": {
"targetTransactionId": "TX-1902526801"
}
}
```
Kashier also releases the remainder of a hold for you automatically after a partial capture when you send `autoVoid: true` on the capture.
Full contract → [Void](/docs/accept-payments/void)
# Connected accounts (/docs/accept-payments/connected-accounts)
This feature gives the ability for authorized platforms to make payments on behalf of other Kashier merchants that are enabled for connected accounts. A platform is an aggregator of multiple merchants or service providers. Examples: Amazon, Talabat. A connected account refers to the merchants on the platform, such as McDonald's, KFC.
## Account requirements [#account-requirements]
* Connected account: The connected account should be an existing Kashier merchant that is enabled for live payments. It must authorize the connection to the platform.
* Platform account: The platform should be an existing Kashier merchant. No need for a bank account or live payment acceptance, but the connected accounts must authorize the platform to collect payments on their behalf (i.e., by displaying the connected account's payment UI on the platform app).
## Making payments on behalf of connected accounts [#making-payments-on-behalf-of-connected-accounts]
Learn how to add the right information to your [Payment Sessions](/docs/accept-payments/payment-sessions) so you can make payments on behalf of your connected accounts.
You can make a payment on behalf of a connected account by passing the connected account's merchant ID as `connectedAccount.merchantId` on the [Payment Sessions](/docs/accept-payments/payment-sessions) request. The value is the connected account's own MID and must carry the `MID-` prefix:
```json
{
"connectedAccount": {
"merchantId": "MID-XXXXX-XXX"
}
}
```
Both the platform and sub-merchant can view transactions and export them from the [Merchant Portal](https://merchant.kashier.io).
# Currency conversion (/docs/accept-payments/currency-conversion)
Kashier exposes a lightweight exchange-rate lookup you can use to price cross-currency amounts before creating a payment — for example, quoting a customer a price in USD while the actual charge settles in EGP.
This endpoint only reads a rate. It does not convert a payment, does not change
how a payment session behaves, and is not switched on or off per merchant —
calling it has no effect on anything else in your account. If you are looking for
customer-facing display currencies on checkout, that is a separate, separately
gated product feature; ask your account manager whether it is available to you.
Unlike the rest of the payments API, this endpoint is **public and unauthenticated** — no `Authorization` or `api-key` header is required. You can call it directly from a browser or server without a merchant secret key.
## Get exchange rate [#get-exchange-rate]
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v3/payment/exchange-rate](https://test-api.kashier.io/v3/payment/exchange-rate) |
| LIVE-URL | [https://api.kashier.io/v3/payment/exchange-rate](https://api.kashier.io/v3/payment/exchange-rate) |
| Method | GET |
### Query parameters [#query-parameters]
| Key | Description |
| ---- | ------------------------------------------------------------------------------------------------ |
| from | Source currency code (e.g. `USD`). Optional — defaults to `USD`. Returned as `base`. |
| to | Target currency code (e.g. `EGP`). Optional — defaults to `EGP`. Becomes the key inside `rates`. |
```bash
curl --location 'https://test-api.kashier.io/v3/payment/exchange-rate?from=USD&to=EGP'
```
### Response [#response]
```json
{
"success": true,
"timestamp": 1787654094144,
"base": "USD",
"date": "2026-08-25",
"rates": {
"EGP": 50.76884413
}
}
```
| Field | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `success` | `true` when a rate was resolved. |
| `timestamp` | When the rate was produced, in milliseconds since the epoch. |
| `base` | The source currency — echoes `from`. |
| `date` | The rate's date, `YYYY-MM-DD`. |
| `rates` | An object with a single key: the `to` currency, mapped to the rate. Multiply an amount in `base` by this number to get the amount in `to`. |
So `1 USD = 50.76884413 EGP` in the example above.
Successful responses here are passed through from the upstream rate provider, so they have **no** `status`/`response`/`messages` wrapper — unlike the rest of the API. Errors *are* wrapped in the standard envelope. Parse the two shapes separately.
### Supported currencies [#supported-currencies]
`from` and `to` accept ISO 4217 codes, and the range is wide — the rate comes
from an upstream FX provider, not from a short Kashier list. `EGP`, `USD`, `EUR`,
`GBP`, `SAR`, `AED`, `KWD`, `JPY`, `CAD`, `CHF`, and `AUD` all resolve, and so do
many others. An unrecognised code is rejected rather than silently defaulted (see
[Errors](#errors)).
This endpoint quoting `USD → JPY` does not mean Kashier can take a payment in
JPY. Payment sessions accept **`EGP`, `USD`, `GBP`, and `EUR`** only — see
[create payment session](/docs/api-reference/payment-sessions/createPaymentSession).
Use this endpoint to price or display a cross-currency amount; charge in a
currency your account actually supports.
### Errors [#errors]
An unknown `to` currency returns **404**:
```json
{
"error": { "cause": "Invalid Data" },
"messages": {
"en": "The Exchange Rate Service is temporarily unavailable. Please try again.",
"ar": "حدث خطأ ما"
},
"status": "FAILURE"
}
```
An unknown `from` currency, or an upstream outage, returns **503**:
```json
{
"error": { "cause": "Service unavailable" },
"messages": {
"en": "Exchange rate is temporarily unavailable. Please try again later",
"ar": "سعر الصرف غير متاح مؤقتًا. يرجى المحاولة مرة أخرى لاحقًا"
},
"status": "FAILURE"
}
```
Note that the 404 message says "temporarily unavailable" even though the cause is an invalid currency code — branch on the status code and `error.cause`, not on the message text.
# Customers (/docs/accept-payments/customers)
The Customers module in Kashier's API allows merchants to store, manage, and retrieve customer information. It enables the creation of customer profiles and links them to transactions at checkout. Merchants can update customer details, track payment history, and personalize the experience. This module simplifies recurring payments and improves customer insights for both businesses and customers.
## Customer object structure [#customer-object-structure]
The customer interface.
```json
{
"_id": "67ba0311bf4f31001203c6c2",
"name": "John Doe",
"phoneNumber": "01XXXXXXXXX",
"emailAddress": "",
"customerId": "C-XXXXXXXXXXXXX",
"merchantId": "MID-XXXXX-XXX",
"customFields": [
{
"name": "custom-key-1",
"value": "custom-value-1"
},
{
"name": "custom-key-2",
"value": "custom-value-2"
}
],
"createdByUserId": "5f8d0d55b54764421b7156c3",
"__v": 0,
"createdAt": "2025-02-22T17:02:10.098Z",
"updatedAt": "2025-02-25T12:31:40.474Z",
"id": "67ba0311bf4f31001203c6c2"
}
```
## List all customers [#list-all-customers]
Retrieve a list of all customers.
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name](https://test-api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name) |
| LIVE URL | [https://api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name](https://api.kashier.io/v2/customers?page=1\&limit=20\&sortType=-1\&sortBy=name) |
| Method | GET |
### Query parameters [#query-parameters]
All of these are optional — call the route with no query string to get the first page with the defaults.
| Key | Description |
| ----------------------- | --------------------------------------------------------- |
| `page` | Page number. Default `1`. |
| `limit` | Records per page. Default `20`. |
| `sortBy` | Sort field: `name` or `createdAt`. |
| `sortType` | Sort order: `1` ascending, `-1` descending. Default `-1`. |
| `search` | Search term. |
| `searchBy` | Field to search in, for example `name`. |
| `startDate` / `endDate` | Date range filter, `YYYY-MM-DD`. |
| `labels` | Comma-separated label names. |
| `branchIds` | Comma-separated branch IDs. |
```bash
curl --location 'https://test-api.kashier.io/v2/customers?page=1&limit=20&sortType=-1&sortBy=name'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [List all customers](/docs/api-reference/customers/listCustomers).
## Get customer details [#get-customer-details]
Retrieve details of a specific customer.
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v2/customers/:id](https://test-api.kashier.io/v2/customers/:id) |
| LIVE URL | [https://api.kashier.io/v2/customers/:id](https://api.kashier.io/v2/customers/:id) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/customers/:id'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-1]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [Get customer details](/docs/api-reference/customers/getCustomer).
## Add new customer [#add-new-customer]
Add a new customer.
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------ |
| TEST URL | [https://test-api.kashier.io/v2/customers](https://test-api.kashier.io/v2/customers) |
| LIVE URL | [https://api.kashier.io/v2/customers](https://api.kashier.io/v2/customers) |
| Method | POST |
```bash
curl --location 'https://test-api.kashier.io/v2/customers'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--data-raw '{
"name": "John doe",
"phoneNumber": "01123456789",
"emailAddress": "john.doe@example.com",
"customFields": [
{
"name": "custom-key-1",
"value": "custom-value-1"
}
],
"preferredCommunicationChannel":"sms"
}'
```
### Headers [#headers-2]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [Add a new customer](/docs/api-reference/customers/createCustomer).
## Update customer [#update-customer]
Update a customer.
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v2/customers/:id](https://test-api.kashier.io/v2/customers/:id) |
| LIVE URL | [https://api.kashier.io/v2/customers/:id](https://api.kashier.io/v2/customers/:id) |
| Method | PUT |
```bash
curl --location --request PUT 'https://test-api.kashier.io/v2/customers/:id'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--data-raw '{
"name": "John doe",
"phoneNumber": "01123456789",
"emailAddress": "john.doe@example.com",
"customFields": [
{
"name": "custom-key-1",
"value": "custom-value-1"
}
],
"preferredCommunicationChannel":"sms"
}'
```
### Headers [#headers-3]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [Update a customer](/docs/api-reference/customers/updateCustomer).
## Delete customer [#delete-customer]
Delete a customer.
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v2/customers/:id](https://test-api.kashier.io/v2/customers/:id) |
| LIVE URL | [https://api.kashier.io/v2/customers/:id](https://api.kashier.io/v2/customers/:id) |
| Method | DELETE |
```bash
curl --location --request DELETE 'https://test-api.kashier.io/v2/customers/:id'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Headers [#headers-4]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [Delete a customer](/docs/api-reference/customers/deleteCustomer).
## Bulk import [#bulk-import]
The process for importing customers from an Excel sheet requires two sequential API calls.
### Upload Excel file API [#upload-excel-file-api]
* Purpose: Upload and validate the Excel file containing customer data
* Response:
* If errors are found in the data, they will be returned for correction.
* If validation passes, the API returns a `correlationId`
* The `correlationId` serves as a reference to your validated data
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v2/customers/import](https://test-api.kashier.io/v2/customers/import) |
| LIVE URL | [https://api.kashier.io/v2/customers/import](https://api.kashier.io/v2/customers/import) |
| Method | POST |
```bash
curl --location --request POST 'https://test-api.kashier.io/v2/customers/import'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--form 'file=@"/home/boody/Downloads/customers.xlsx"'
```
#### Headers [#headers-5]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [Upload a customers sheet for review](/docs/api-reference/customers/importCustomers).
### Save customers API [#save-customers-api]
* Purpose: Permanently save the validated customer data to the system
* Required: Include the `correlationId` as a query string parameter
* This API finalizes the import process using the previously validated data
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId](https://test-api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId) |
| LIVE URL | [https://api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId](https://api.kashier.io/v2/customers/savecustomers?correlationId=:correlationId) |
| Method | GET |
```bash
curl --location 'https://test-api.kashier.io/v2/customers/savecustomers?correlationId={{correlationId}}'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
#### Headers [#headers-6]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is the secret key that is used to identify the merchant. You can obtain it from Kashier dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
Full parameter and response reference → [Save the uploaded customers](/docs/api-reference/customers/saveImportedCustomers).
This two-step approach ensures data integrity by separating validation from the actual import process.
# Flutter SDK (/docs/accept-payments/flutter-sdk)
A complete, code-accurate guide to installing, configuring, and shipping Kashier payments in a Flutter app — Apple Pay, mobile wallets, and cards — from a single API call.
| Field | Value |
| -------- | -------------------------------------------------------------------------------------- |
| Version | 0.1.1 |
| Audience | Merchant developers integrating Kashier payments into Flutter apps |
| Package | [pub.dev/packages/kashier\_flutter\_sdk](https://pub.dev/packages/kashier_flutter_sdk) |
## SDK overview and prerequisites [#sdk-overview-and-prerequisites]
The Kashier Flutter SDK is a drop-in payment library that lets your Flutter app accept payments from Egyptian customers through Apple Pay, mobile wallets (Vodafone Cash and others), and credit / debit cards (with 3-D Secure and saved cards) — all from a single API call.
The SDK ships its own pre-built payment sheet UI. You hand it a session ID from your backend, and it picks the right method, renders the right screens, handles 3DS WebViews, polls for wallet reconciliation, and calls back with a typed result.
### What you can do [#what-you-can-do]
* Accept Apple Pay payments on iOS using your own Apple Pay Merchant ID (per-merchant — see the Apple Pay section).
* Accept mobile wallet payments (Vodafone Cash and other R2P-compatible Egyptian wallets) with built-in polling.
* Accept card payments (new card, saved card, 3-D Secure) without ever touching raw card data — the SDK handles tokenization and the 3DS WebView.
* Switch between test and live environments at initialization time.
* Render the SDK UI in Arabic or English, switchable at runtime.
### Supported platforms [#supported-platforms]
| Requirement | Minimum |
| --------------------- | ---------------------------------------------- |
| Flutter | 3.22.0 |
| Dart | 3.4.0 |
| iOS deployment target | 14.0 |
| Android | inherits Flutter default minSdk (typically 21) |
| Apple device | iPhone with Face ID or Touch ID for Apple Pay |
| Account | Active Kashier merchant account |
### Method and platform matrix [#method-and-platform-matrix]
| Payment method | iOS | Android |
| -------------------------------- | --- | ------------------------ |
| Apple Pay | Yes | No (degrades gracefully) |
| Mobile wallet (Vodafone Cash, …) | Yes | Yes |
| Card (new) | Yes | Yes |
| Card (saved) | Yes | Yes |
| 3-D Secure | Yes | Yes |
The SDK's native layer is iOS-only (Swift + PassKit for Apple Pay). Wallet and card flows are pure Dart (HTTP + WebView) and run on both platforms.
### Architecture at a glance [#architecture-at-a-glance]
```text
┌────────────────────────────────────────────────────────┐
│ Your Flutter App │
│ └── KashierPaymentProvider ◀─ wraps MaterialApp │
│ └── MaterialApp / Router │
└────────────────────────────────────────────────────────┘
│
▼ KashierSDK.startPayment(sessionId, …)
┌────────────────────────────────────────────────────────┐
│ Kashier SDK (Dart) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Apple Pay │ │ Wallet │ │ Card + 3DS │ │
│ │ Handler │ │ Handler │ │ Handler │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ MethodChannel │
│ ┌──────────────────────────────────────────────────┐ │
│ │ KashierBridge (iOS only) → PassKit (Apple Pay) │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────┘
│
▼ HTTPS
┌────────────────────────────────────────────────────────┐
│ Kashier API + FEP (test or live) │
└────────────────────────────────────────────────────────┘
```
* Apple Pay crosses the native bridge three times (DOWN: present sheet with merchant ID, UP: encrypted token, DOWN: finalize sheet).
* Wallet and card flows are pure Dart with HTTP + WebView — no native bridge needed.
### Prerequisites [#prerequisites]
1. An active Kashier merchant account.
2. Your Kashier API credentials (available in your Kashier Dashboard).
3. A backend server capable of creating payment sessions on Kashier's API.
4. For Apple Pay only: your own Apple Pay Merchant ID and a payment processing certificate.
### What is a session ID? [#what-is-a-session-id]
A session ID is a unique identifier created by your backend that represents a single payment transaction. Your backend calls Kashier's [Payment Sessions API](/docs/accept-payments/payment-sessions) with the amount, currency, allowed methods, and customer details, and Kashier returns a session ID.
Your Flutter app then passes only that session ID to the SDK — no API key, no Kashier merchant ID, no card data ever lives in the mobile binary.
```text
Your Backend ──POST /sessions──▶ Kashier API
│
session_id ◀────────────┘
│
▼
Your Flutter app
│
▼ KashierSDK.startPayment(sessionId)
Kashier SDK
```
Backend integration is out of scope for this guide. See the [Payment Sessions API documentation](/docs/accept-payments/payment-sessions) for backend details.
## Installation and setup [#installation-and-setup]
### Add the dependency [#add-the-dependency]
Add the SDK from [pub.dev](https://pub.dev/packages/kashier_flutter_sdk) to your `pubspec.yaml`:
```yaml
dependencies:
kashier_flutter_sdk: ^0.1.1
```
Then install:
```bash
flutter pub get
```
The SDK pulls in these transitive dependencies (no action needed — listed for transparency):
| Package | Version | Used for |
| --------------------------- | ------- | ------------------------- |
| http | ^1.2.0 | Kashier API / FEP calls |
| webview\_flutter | ^4.0.0 | 3DS challenge container |
| flutter\_inappwebview | ^6.0.0 | 3DS WebView |
| connectivity\_plus | ^7.1.1 | Pre-flight offline checks |
| flutter\_svg | ^2.0.10 | Sheet iconography |
| plugin\_platform\_interface | ^2.0.2 | Platform channel contract |
| meta | ^1.16.0 | `@internal` annotations |
### iOS setup [#ios-setup]
Open `ios/Podfile` and ensure the platform is at least iOS 14.0:
```ruby
platform :ios, '14.0'
```
Install the iOS pods:
```bash
cd ios && pod install
```
Apple Pay requires per-merchant configuration (your own Apple Merchant ID, an Xcode entitlement, and a payment processing certificate). Follow the runbook in the Apple Pay section before enabling it.
### Android setup [#android-setup]
The SDK requires only the standard INTERNET permission, which is already part of the default Flutter `AndroidManifest.xml`. No extra setup is needed.
Apple Pay is iOS-only. The SDK has no native Android code — wallet and card flows are pure Dart. On Android, `KashierSDK.isApplePayAvailable()` returns false and the unified payment sheet automatically hides the Apple Pay option.
### Wrap your app with KashierPaymentProvider [#wrap-your-app-with-kashierpaymentprovider]
The SDK renders its payment sheet on its own overlay, outside your app's Navigator. To enable that overlay, wrap your `MaterialApp` (or `CupertinoApp` / `MaterialApp.router`) with `KashierPaymentProvider`:
```dart
import 'package:flutter/material.dart';
import 'package:kashier_flutter_sdk/kashier_flutter_sdk.dart';
void main() {
runApp(
KashierPaymentProvider(
child: MaterialApp(
home: HomeScreen(),
),
),
);
}
```
This works with any navigation solution: Navigator 2.0, GoRouter, AutoRoute, nested navigators, etc.
Why this is required: because the provider holds the overlay, you can call `KashierSDK.startPayment(...)` from anywhere — no BuildContext required. The SDK manages its own UI lifecycle and never pushes onto your routes.
### Initialize the SDK [#initialize-the-sdk]
Call `KashierSDK.initialize()` once before starting any payment — typically at app startup or in the `initState` of the screen that triggers payment:
```dart
KashierSDK.initialize(
mode: KashierMode.test, // or KashierMode.live
language: KashierLanguage.en, // or KashierLanguage.ar
appleMerchantId: 'merchant.com.yourcompany.app', // iOS Apple Pay only
);
```
| Parameter | Type | Required | Default | Description |
| --------------- | --------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| mode | KashierMode | yes | — | test (sandbox) or live (production) |
| language | KashierLanguage | no | en | UI language for the payment sheet |
| appleMerchantId | String? | no | null | Your own Apple Pay Merchant ID. Required only to enable Apple Pay; must match the Merchant ID in your Xcode entitlement. When omitted, Apple Pay is not offered. |
Switching language at runtime: call `initialize()` again with the new `KashierLanguage`. There is no separate setter — re-initialization is the supported pattern. It disposes the previous API client and re-registers payment handlers.
## Quick start [#quick-start]
End-to-end integration in under 60 lines:
```dart
import 'package:flutter/material.dart';
import 'package:kashier_flutter_sdk/kashier_flutter_sdk.dart';
void main() {
runApp(
KashierPaymentProvider(
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CheckoutScreen(),
);
}
}
class CheckoutScreen extends StatefulWidget {
@override
State createState() => _CheckoutScreenState();
}
class _CheckoutScreenState extends State {
@override
void initState() {
super.initState();
KashierSDK.initialize(
mode: KashierMode.test,
language: KashierLanguage.en,
appleMerchantId: 'merchant.com.yourcompany.app', // omit if not using Apple Pay
);
}
void _pay() {
KashierSDK.startPayment(
sessionId: 'sess_abc123', // from your backend
onSuccess: (KashierPaymentResult result) {
print('Paid: ${result.transactionId}');
},
onPending: (KashierPaymentPending pending) {
print('Unconfirmed — reconcile server-side: ${pending.orderId}');
},
onFailure: (KashierPaymentError error) {
if (error.code == KashierErrorCode.userCancelled) return;
print('Failed: ${error.message}');
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: _pay,
child: const Text('Pay'),
),
),
);
}
}
```
That's the entire integration. The SDK reads the session, renders the method-selection sheet (Apple Pay, Wallet, Card — whichever the session and device support), runs the chosen flow, and fires exactly one callback when done.
A complete runnable sample lives in `example/lib/main.dart`.
## Apple Pay integration guide [#apple-pay-integration-guide]
Apple Pay is iOS-only. Unlike wallet and card, it requires one-time per-merchant setup: you bring your own Apple Pay Merchant ID and pass it to `initialize()`. The unified payment sheet is the recommended path because it handles availability, fallback, and method selection automatically. Use the standalone Apple Pay button only if you want to launch Apple Pay directly without showing a method picker.
### The per-merchant model [#the-per-merchant-model]
Each merchant uses their own Apple Pay Merchant ID — there is no shared/bundled Kashier identifier. The ID you register with Apple is:
1. Passed to the SDK via `KashierSDK.initialize(appleMerchantId: …)`.
2. Carried DOWN to native PassKit and set as `PKPaymentRequest.merchantIdentifier`.
3. Backed by your own Apple Pay Payment Processing Certificate so Kashier can decrypt the resulting token for your account.
If `appleMerchantId` is null or empty, the SDK cannot build a valid Apple Pay request — `isApplePayAvailable()` returns false and Apple Pay is hidden from the sheet.
### Enable and configure runbook [#enable-and-configure-runbook]
Complete these steps once per merchant app. Steps that involve Kashier-side certificate handling are flagged Preview.
1. Register an Apple Pay Merchant ID. Apple Developer portal → Certificates, Identifiers & Profiles → Identifiers → Merchant IDs → +. Use a reverse-DNS identifier, e.g. `merchant.com.yourcompany.app`.
2. Create a Payment Processing Certificate. \[Preview — coordinate with Kashier, early access] Apple requires a Certificate Signing Request (CSR) tied to the processor that will decrypt the token. Open a ticket with Kashier support to obtain the CSR / certificate for your Merchant ID before going live. This onboarding is currently manual.
3. Add the Apple Pay capability in Xcode. Open `ios/Runner.xcworkspace` → select the Runner target → Signing & Capabilities → + Capability → Apple Pay, then check your Merchant ID. This writes the entitlement into `ios/Runner/Runner.entitlements`:
```xml
com.apple.developer.in-app-payments
merchant.com.yourcompany.app
```
4. Pass the Merchant ID to the SDK. The value here must match the entitlement exactly:
```dart
KashierSDK.initialize(
mode: KashierMode.live,
appleMerchantId: 'merchant.com.yourcompany.app',
);
```
5. Verify availability on a physical device with a card in Apple Wallet:
```dart
final ok = await KashierSDK.isApplePayAvailable(); // expect true
```
If `isApplePayAvailable()` returns false after setup, check (in order): you passed `appleMerchantId`; it matches the entitlement; you're on a real device (not Simulator); a supported card is in Apple Wallet.
### Check availability [#check-availability]
```dart
final available = await KashierSDK.isApplePayAvailable();
if (available) {
// Render Apple Pay button or include in payment sheet
} else {
// Fall back to other methods
}
```
`isApplePayAvailable()` returns false when:
* `appleMerchantId` was not supplied to `initialize()` (or is empty).
* Running on Android or any non-iOS platform.
* Running on iOS Simulator (no real Wallet).
* The user has not added any supported card to Apple Wallet.
* The device hardware does not support Apple Pay.
### Display the Apple Pay button (optional, direct-launch) [#display-the-apple-pay-button-optional-direct-launch]
The SDK ships a styled `KashierApplePayButton` widget for merchants who want to skip the method-selection sheet:
```dart
KashierApplePayButton(
onPressed: () {
KashierSDK.startPayment(
sessionId: 'sess_abc123',
onSuccess: (result) { /* … */ },
onFailure: (error) { /* … */ },
);
},
height: 50, // optional, default 50
cornerRadius: 10, // optional, default 10
)
```
The button label flips automatically between English (Pay with Pay) and Arabic (ادفع مع Pay). The Apple logo glyph (U+F8FF) renders correctly on Apple devices.
The button is a Flutter widget (not the native PKPaymentButton) so it renders identically across iOS versions and respects your theme. Tapping it does not start a payment automatically — wire that in `onPressed`, typically by calling `KashierSDK.startPayment(...)`.
### End-to-end Apple Pay flow [#end-to-end-apple-pay-flow]
The validation handshake has been removed — the flow now crosses the native bridge three times:
| # | Direction | Bridge call | Payload |
| - | ------------ | ---------------------------- | ----------------------------------------------------------------------------------- |
| 1 | Dart → Swift | presentApplePay | amount, currency, storeName, supportedNetworks, merchantId |
| 2 | Swift → Dart | token callback | `{ paymentData, paymentMethod{displayName, network, type}, transactionIdentifier }` |
| 3 | Dart → Swift | completePaymentAuthorization | `{ success: bool }` (finalizes the sheet) |
Native PassKit builds the PKPaymentRequest with `merchantIdentifier = merchantId`, `countryCode = "EG"`, `merchantCapabilities = .capability3DS`, and the resolved networks (visa, mastercard, meeza, amex).
### Callbacks [#callbacks]
| Event | Callback fired | Notes |
| -------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------- |
| FEP returns 200 | `onSuccess(KashierPaymentResult)` | `result.card.wallet == "APPLE_PAY"` |
| User taps Cancel in Apple Pay sheet | onFailure | code == userCancelled. Don't show error UI — the user knows they cancelled |
| Token processing failure | onFailure | tokenProcessingFailed — FEP could not process the token |
| Misconfiguration / Simulator / missing merchant ID | onFailure | applePayConfigError or applePayNotAvailable |
Apple Pay never fires `onPending`.
### Platform fallback [#platform-fallback]
On Android (or any non-iOS platform), `KashierApplePayButton` still renders but `onPressed` will surface applePayNotAvailable from `startPayment`. Use `isApplePayAvailable()` to gate the button, or rely on the unified payment sheet which hides Apple Pay automatically on unsupported platforms.
## Unified payment sheet [#unified-payment-sheet]
The recommended integration path. One call, every method, automatic device gating.
```dart
KashierSDK.startPayment(
sessionId: 'sess_abc123',
onSuccess: (result) { /* … */ },
onFailure: (error) { /* … */ },
onPending: (pending) { /* … */ }, // terminal, for card + wallet flows
);
```
The SDK:
1. Fetches session details from Kashier (`GET /sessions/{id}`).
2. Computes which methods to show by intersecting registered methods, session-allowed methods, and device capability.
3. Renders the method-selection sheet on its own overlay.
4. Runs the chosen flow (Apple Pay / Wallet / Card) end-to-end.
5. Calls one of `onSuccess`, `onFailure`, or `onPending`. For both card and wallet, `onPending` is terminal.
### How method visibility is decided [#how-method-visibility-is-decided]
A method appears in the sheet only if all three are true:
| Check | Source | Notes |
| ---------------------- | ---------------------------------- | ----------------------------------------------------------- |
| Registered in SDK | `KashierSDK._registerHandler(...)` | applePay, wallet, card all registered in v1 |
| Allowed by the session | `session.allowedMethods` | Empty list = all methods allowed |
| Available on device | `isAvailable()` | e.g., Apple Pay → merchant ID set + PassKit + cards present |
Special case: Apple Pay is unlocked when `allowedMethods` contains either "applePay" or "card" — Apple Pay is treated as a card method on the backend.
### Session knobs that affect the sheet [#session-knobs-that-affect-the-sheet]
Set these on your backend when creating the session (see [Payment Sessions API](/docs/accept-payments/payment-sessions)):
| Field | Type | Effect |
| ----------------- | ---------------------- | ----------------------------------------------------------------------------------------------------- |
| allowedMethods | comma-separated string | Which methods appear: "APPLEPAY,WALLET,CARD". Omit or send empty to allow all. |
| supportedNetworks | list | Card networks for Apple Pay (visa, mastercard, meeza). Defaults to all three. |
| enable3DS | bool | Whether card payments require 3-D Secure |
| saveCard | string | "optional" (checkbox shown), "forced" (always saves), "hidden" (no save UI), or omit (same as hidden) |
| retrieveSavedCard | bool | Whether to fetch the customer's saved cards into the picker |
| brandColor | hex string | Accent color for the payment sheet (see below) |
| customer | object | Customer info displayed and posted with the card payment |
`brandColor` formats accepted: 6-digit RRGGBB or 8-digit AARRGGBB, with or without a leading # (e.g., "#FF5733", "FF5733", "#CCFF5733"). A 6-digit value is treated as fully opaque. Null, empty, or unparseable values fall back to the Kashier primary color #001F5F.
### Localization [#localization]
Sheet headers, button labels, success / failure copy, error messages, and pending copy all flip to Arabic when the SDK is initialized with `KashierLanguage.ar`. Supported languages: en, ar.
### Sheet lifecycle [#sheet-lifecycle]
* The sheet appears above your existing UI on the `KashierPaymentProvider` overlay.
* Only one sheet can be visible at a time.
* The sheet dismisses itself when the flow completes (success, failure, or user dismissal).
* For wallet payments, the sheet remains visible while polling.
## Wallet payment integration guide [#wallet-payment-integration-guide]
Mobile-wallet payments (Vodafone Cash and other Egyptian R2P-compatible wallets) are an out-of-band flow: your customer enters their phone number in the SDK, then completes the payment in their wallet's own app. The SDK polls Kashier for reconciliation and fires `onSuccess` or `onFailure` when the wallet confirms. If reconcile polling runs the full 5 minutes without a terminal result, the SDK shows an "I have paid" button; tapping it fires the terminal `onPending` callback.
### Wallet flow [#wallet-flow]
```text
1. User picks "Wallet" in the unified sheet.
2. Sheet shows phone-number input.
3. SDK calls INITIATE_R2P (POST /v3/orders, apiOperation=INITIATE_R2P) — no callback fires yet.
4. User opens their wallet app and approves the charge.
5. SDK polls reconcile on a fixed schedule capped at 5 minutes total:
- Phase 1: 12 attempts, 10s apart (120s).
- Phase 2: increasing intervals (30s, 45s, 60s, …) until the 5-min cap.
6. Outcome:
a. Confirmed → onSuccess(KashierPaymentResult)
b. Declined → onFailure(KashierPaymentError)
c. Still pending at the 5-min cap → SDK shows an "I have paid" button.
Customer taps it → terminal onPending(KashierPaymentPending) fires
(if implemented) and the sheet closes. Cancel → onFailure(userCancelled).
```
Polling schedule, precisely: Phase 1 is 12 fixed 10-second intervals (120s). Phase 2 intervals are 30s, 45s, 60s, then 90s/120s/180s — but the SDK never dispatches a request that would cross the 300-second (5-minute) hard cap, so only the 30s/45s/60s Phase-2 attempts are reachable in practice. The cap is 5 minutes total, not 10.
### Wiring onPending [#wiring-onpending]
`onPending` is optional and terminal for wallet: it fires at most once, only when reconcile polling exhausts after 5 minutes with the payment still pending and the customer taps "I have paid". It does not fire at initiation, and no `onSuccess`/`onFailure` follows it.
```dart
KashierSDK.startPayment(
sessionId: 'sess_abc123',
onSuccess: (KashierPaymentResult result) {
// Wallet confirmed the payment
showSuccessScreen(result);
},
onPending: (KashierPaymentPending pending) {
// Polling exhausted while still pending and the customer asserted payment.
// Confirm final settlement server-side using the orderId.
print('Pending order: ${pending.orderId}'); // authoritative reference
print('Status: ${pending.message}'); // localized
},
onFailure: (KashierPaymentError error) {
showErrorScreen(error);
},
);
```
Card vs wallet pending: the same `onPending` callback has unified semantics across card and wallet — it is terminal in both, firing after reconcile polling exhausts while the payment is still settling. For both, `orderId` is authoritative, `transactionId` may be empty, and you should confirm final settlement via webhook/backend.
### What KashierPaymentPending contains [#what-kashierpaymentpending-contains]
| Field | Type | Description |
| ------------- | ------- | ----------------------------------------------------- |
| orderId | String | Kashier order ID (authoritative reference) |
| transactionId | String | Transaction ID (e.g., "TX-35524544764"; may be empty) |
| message | String | Status message (localized to SDK language) |
| messageEn | String? | English status message |
| messageAr | String? | Arabic status message |
### Wallet provider hint (KashierWalletProvider) [#wallet-provider-hint-kashierwalletprovider]
```dart
enum KashierWalletProvider {
vodafoneCash, // displayNameEn: "Vodafone Cash" | displayNameAr: "فودافون كاش"
other, // displayNameEn: "Mobile Wallet" | displayNameAr: "محفظة إلكترونية"
}
```
The SDK uses this enum internally to label wallet methods in the sheet. You do not need to instantiate it.
### Polling cap and webhook fallback [#polling-cap-and-webhook-fallback]
The SDK polls for up to 5 minutes total. If the wallet user does not complete in time and the payment is still pending at the cap, the SDK shows the "I have paid" button (→ terminal `onPending`); an outright timeout surfaces `onFailure(paymentTimeout)`. The session itself may still settle later — your backend should rely on the server-to-server webhook (`session.serverWebhook`) as the source of truth, not solely on the SDK callback.
## Card payment integration guide [#card-payment-integration-guide]
Card payments handle four sub-flows from a single entry point: new card, saved card, 3-D Secure, and (optionally) save-card-after-payment. The unified sheet renders the card form, runs validation, opens the 3DS WebView when the issuer requires it, reconciles, and reports back via `onSuccess` / `onFailure` / `onPending`.
### New-card flow [#new-card-flow]
1. User picks "Card" in the sheet → card form appears.
2. SDK validates card number, expiry, CVV, holder name inline before submitting.
3. SDK posts to Kashier card endpoint, receives the card response.
4. If 3DS is required → SDK opens an in-sheet WebView at the issuer's redirect URL.
5. After the OTP is submitted, the SDK reconciles on the post-OTP schedule.
6. SDK fires `onSuccess`, `onFailure`, or terminal `onPending` (if reconcile exhausts while still settling).
### Saved-card flow [#saved-card-flow]
When `session.retrieveSavedCard == true`, the sheet shows the customer's previously-saved cards as selectable chips:
1. User taps a saved card → CVV-only prompt appears (CVV is never stored).
2. SDK posts the saved card token + CVV → same 3DS / reconcile flow as new card.
3. SDK fires `onSuccess` / `onFailure` / `onPending`.
### Save-card-after-payment [#save-card-after-payment]
Controlled by `session.saveCard`:
| Value | Behavior |
| ---------- | --------------------------------------------------- |
| "optional" | Save-card checkbox shown; user opts in |
| "forced" | Card is always saved (checkbox shown but locked on) |
| "hidden" | No save UI |
| null | Same as "hidden" — no save behavior, no UI |
The customer's saved cards live on Kashier's side, keyed by the customer reference in the session. They are never persisted in the SDK or your app.
### 3-D Secure (3DS) and reconciliation [#3-d-secure-3ds-and-reconciliation]
When the issuer mandates 3DS, the SDK opens the issuer's challenge URL in an embedded flutter\_inappwebview view.
After the user submits the OTP, the WebView closes when Kashier's redirect URL is hit and the SDK reconciles on the post-OTP schedule:
* Post-OTP reconcile: 8 attempts over \~60s — four 5-second intervals, then four 10-second intervals.
* If a terminal result arrives → `onSuccess` / `onFailure`.
* If the schedule exhausts while still settling → terminal `onPending(KashierPaymentPending)`.
If the user cancels the 3DS challenge (taps the close button), the SDK does not fail immediately. It switches to a "verifying" view and runs a cancel safety-net reconcile — 5 attempts at 0s, 5s, 10s, 15s, 30s (\~60s) — in case the payment actually went through. Terminal result found → `onSuccess` / `onFailure`; nothing found after exhaustion → `onFailure(userCancelled)`.
If 3DS is disabled on the session (`session.enable3DS == false`) or not required by the issuer, the SDK skips the WebView entirely.
### What can fail [#what-can-fail]
| Error code | Cause |
| -------------------- | -------------------------------------------------------------- |
| paymentDeclined | Issuer or processor declined the card |
| authorizationFailed | 403 from Kashier (invalid hash, bad merchant config) |
| authenticationFailed | 3DS challenge failed (e.g., wrong OTP) |
| invalidCardDetails | Card field validation failed before submit |
| cardRetrievalFailed | Could not load saved cards (sheet falls back to new-card form) |
| cardDeletionFailed | Could not delete a saved card from the picker |
| networkError | No internet at the time of the call |
All map to `KashierPaymentError` with a localized message and optional issuer responseCode.
### PCI scope [#pci-scope]
Card data is collected by the SDK's own form, tokenized against Kashier FEP, and never persisted in your app. Your app stays out of PCI scope as long as you use the SDK's card form — do not build your own card form against the same endpoints.
## Complete API reference [#complete-api-reference]
Everything below is exported from a single import:
```dart
import 'package:kashier_flutter_sdk/kashier_flutter_sdk.dart';
```
Public surface: `KashierSDK`, `KashierPaymentProvider`, `KashierApplePayButton`, `KashierMode`, `KashierLanguage`, `KashierWalletProvider`, `KashierErrorCode`, `KashierPaymentResult`, `KashierCardInfo`, `KashierPaymentError`, `KashierPaymentPending`.
### KashierSDK (static API) [#kashiersdk-static-api]
The main entry point. All members are static.
#### initialize [#initialize]
```dart
static void initialize({
required KashierMode mode,
KashierLanguage language = KashierLanguage.en,
String? appleMerchantId,
})
```
Initializes the SDK. Must be called once before any other method. Re-call to switch language or mode at runtime; this disposes the previous internal API client and re-registers payment handlers. Returns void.
#### startPayment [#startpayment]
```dart
static Future startPayment({
required String sessionId,
required void Function(KashierPaymentResult result) onSuccess,
required void Function(KashierPaymentError error) onFailure,
void Function(KashierPaymentPending pending)? onPending,
})
```
Starts a payment flow for the given session. Fetches the session, picks available methods, shows the unified sheet, and reports the outcome via callbacks. Returns `Future` (resolves when the SDK has handed off to the sheet — not when payment completes). If `initialize()` was not called, fires `onFailure(unknownError)`.
#### isApplePayAvailable [#isapplepayavailable]
```dart
static Future isApplePayAvailable()
```
Checks whether Apple Pay is available on the current device (merchant ID set + iOS + PassKit + at least one card). Returns false on non-iOS platforms, if `appleMerchantId` was not supplied, or if the SDK is not yet initialized.
#### Static getters [#static-getters]
| Getter | Type | Description |
| ------------- | --------------- | ------------------------------------------- |
| isInitialized | bool | true after `initialize()` has been called |
| mode | KashierMode? | Current mode, or null before initialization |
| language | KashierLanguage | Current display language (defaults to en) |
### Enums [#enums]
`KashierMode` — test and live environments:
| Value | baseUrl | fepBaseUrl |
| ----- | ---------------------------------------------------------- | ---------------------------------------------------------- |
| test | [https://test-api.kashier.io](https://test-api.kashier.io) | [https://test-fep.kashier.io](https://test-fep.kashier.io) |
| live | [https://api.kashier.io](https://api.kashier.io) | [https://fep.kashier.io](https://fep.kashier.io) |
`KashierLanguage` — en (`Locale('en')`) and ar (`Locale('ar')`). `KashierWalletProvider` — vodafoneCash ("Vodafone Cash" / "فودافون كاش") and other ("Mobile Wallet" / "محفظة إلكترونية").
### Models [#models]
`KashierPaymentResult` — returned by `onSuccess` on a terminal successful payment:
| Field | Type | Description |
| ------------------- | ---------------- | -------------------------------------------- |
| sessionId | String | The session this result belongs to |
| status | String | Payment status ("SUCCESS", "CAPTURED", etc.) |
| orderId | String? | Kashier order ID (systemOrderId) |
| orderReference | String? | Merchant order reference |
| transactionId | String? | Transaction ID (e.g., "TX-35524544753") |
| authorizationNumber | String? | Issuer authorization number |
| amount | num? | Charged amount |
| currency | String? | ISO 4217 currency code (e.g., "EGP") |
| card | KashierCardInfo? | Card / wallet details used |
| message | String? | Localized success message (English) |
| messageAr | String? | Localized success message (Arabic) |
| transactionData | Map? | Raw API response body for advanced use |
`KashierCardInfo` — accessed via `KashierPaymentResult.card`: brand (e.g. "Visa"), maskedNumber (e.g. "541674\*\*\*\*\*\*7777"), wallet (e.g. "APPLE\_PAY").
`KashierPaymentError` — returned by `onFailure`: code (KashierErrorCode), message (English), messageAr, responseCode (issuer code e.g. "57"), details. Construct via `KashierPaymentError.fromCode(KashierErrorCode.networkError)`.
## Error handling guide [#error-handling-guide]
All failures (including user cancellation) flow through `onFailure(KashierPaymentError)`. Inspect `error.code` to decide how to respond.
### Full error code table [#full-error-code-table]
| Code | Meaning | When it fires | Recoverable? | Recommended UX |
| ------------------------ | ---------------------------------------------- | ------------------------------------------------- | ------------ | ------------------------------------------- |
| sessionExpired | Session is no longer valid | Session timed out before completion | No | Mint a new session on the backend, restart |
| sessionNotFound | Session ID not recognised | Wrong / mistyped ID, or wrong env | No | Verify the ID and that mode matches backend |
| applePayNotAvailable | Apple Pay not available on device | Non-iOS, no merchant ID, no cards, Simulator | No | Hide Apple Pay; offer wallet / card |
| userCancelled | User dismissed the payment sheet | User taps Cancel | Yes | Show no error UI — silently return |
| validationFailed | Apple Pay validation failed | Pre-flight validation rejected | No | Contact Kashier support; backend issue |
| applePayConfigError | Apple Pay misconfiguration | Wrong/absent merchant ID, missing cert, Simulator | No | Re-check the runbook; contact support |
| merchantValidationFailed | Apple Pay validation failed during the session | Mid-flow validation issue | No | Contact Kashier support |
| tokenProcessingFailed | FEP could not process the Apple Pay token | FEP rejected the encrypted token | No | Retry; if persistent, contact support |
| paymentDeclined | Issuer / processor declined the card | Insufficient funds, fraud rules, etc. | Yes | Offer to retry with a different card |
| authorizationFailed | HTTP 403 — authentication failed | Invalid hash, missing credentials | No | Backend config issue; not user-recoverable |
| networkError | No internet connection | Pre-flight or mid-flight network drop | Yes | Surface "check connection, retry" |
| paymentTimeout | Wallet polling cap reached | Wallet not completed within the 5-minute cap | Yes | Show retry; webhook may still confirm later |
| walletInitFailed | INITIATE\_R2P API call failed | Wallet provider unavailable | Yes | Retry, or offer card |
| cardRetrievalFailed | Could not fetch saved cards | Backend or network error | Yes | Sheet falls back to new-card form |
| cardDeletionFailed | Could not delete a saved card | Backend or network error | No | Show "try again later" |
| invalidCardDetails | Card form validation failed | Invalid PAN, expiry, CVV, etc. | Yes | Re-render the form with field errors |
| authenticationFailed | 3-D Secure failed | Wrong OTP, bank decline | Yes | Retry the card or offer another |
| unknownError | Unclassified (incl. SDK not initialized) | Any uncaught path | No | Log error.details; contact support |
### Special-case patterns [#special-case-patterns]
* userCancelled: never show an error toast / dialog. The user knows they cancelled — `if (error.code == KashierErrorCode.userCancelled) return;`
* networkError: the SDK pre-checks connectivity via connectivity\_plus. On a drop, surface a retry button.
* paymentTimeout (wallet only): the polling cap is 5 minutes. The session may still settle — treat the server webhook as the source of truth.
* sessionExpired / sessionNotFound: the user can't recover; your app must mint a new session and restart.
### Localization of error copy [#localization-of-error-copy]
Every error carries message (English) and messageAr (Arabic):
```dart
final isAr = KashierSDK.language == KashierLanguage.ar;
final copy = isAr ? (error.messageAr ?? error.message) : error.message;
```
### Diagnostics [#diagnostics]
When opening a Kashier support ticket about a failure, include `error.code.name`, `error.message`, `error.responseCode`, `error.details`, the session ID and approximate timestamp.
## Callbacks and results [#callbacks-and-results]
`startPayment` resolves to exactly one of three terminal callbacks. Wire all three (`onPending` optional) and confirm settlement server-side for pending.
| Callback | Type | Fires when |
| --------- | --------------------- | ---------------------------------------------------------------------------------------------- |
| onSuccess | KashierPaymentResult | Payment confirmed (card / wallet / Apple Pay) |
| onPending | KashierPaymentPending | Card 3DS reconcile exhausted, OR wallet 5-min poll exhausted + "I have paid" tapped. Terminal. |
| onFailure | KashierPaymentError | Any failure, including userCancelled |
Card pending and wallet pending render on distinct surfaces, but both deliver the same terminal `onPending(KashierPaymentPending)`. Always reconcile final settlement using `pending.orderId` via your webhook/backend.
## Testing guide [#testing-guide]
### Test mode [#test-mode]
```dart
KashierSDK.initialize(mode: KashierMode.test);
```
Test mode endpoints: API [https://test-api.kashier.io](https://test-api.kashier.io), FEP [https://test-fep.kashier.io](https://test-fep.kashier.io). Make sure your backend mints sessions against the matching test API and returns test-mode session IDs. A live session in test mode will fail with sessionNotFound.
### Apple Pay testing [#apple-pay-testing]
Apple Pay testing requires a physical iOS device (not the Simulator), the full setup complete (merchant ID + entitlement + certificate), and an Apple sandbox tester account with at least one test card in Apple Wallet.
1. Complete the runbook and pass `appleMerchantId` to `initialize()`.
2. Create a sandbox tester at [App Store Connect](https://appstoreconnect.apple.com/access/users) → Sandbox Testers.
3. Sign into the sandbox account on the device under Settings → App Store → Sandbox Account.
4. Add a test card to Apple Wallet — see Apple's [Sandbox Testing for Apple Pay](https://developer.apple.com/apple-pay/sandbox-testing/).
5. Run your Flutter app on the device, open the SDK sheet, and complete the flow.
Simulator and a missing/mismatched merchant ID both return applePayNotAvailable. There is no way to test Apple Pay end-to-end on the Simulator.
### Wallet testing [#wallet-testing]
Use Kashier-provided sandbox phone numbers for Vodafone Cash and other wallets. The wallet flow is asynchronous: after entering the test number, the SDK polls reconcile and fires `onSuccess` (or a configured failure) within a few seconds in test mode. If you let polling run the full 5 minutes without a terminal result, the SDK shows the "I have paid" button, and tapping it fires the terminal `onPending`.
### Card testing [#card-testing]
Use standard Kashier test PANs to exercise success, decline, and 3DS-required paths.
| Scenario | Expected outcome |
| ------------------------------------- | ---------------------------------------------------- |
| Success | onSuccess |
| Generic decline | onFailure(paymentDeclined) |
| 3DS required → success | 3DS WebView → onSuccess |
| 3DS required → wrong OTP | 3DS WebView → onFailure(authenticationFailed) |
| 3DS cancelled but charge went through | cancel reconcile → onSuccess |
| 3DS cancelled, nothing settled | cancel reconcile exhausts → onFailure(userCancelled) |
| Reconcile exhausts while settling | onPending |
| Insufficient funds | onFailure(paymentDeclined) with issuer code |
### Going live [#going-live]
1. Switch to live mode: `KashierSDK.initialize(mode: KashierMode.live, appleMerchantId: '…')`.
2. Verify your backend uses live Kashier API credentials and creates sessions against [https://api.kashier.io](https://api.kashier.io).
3. Confirm your Apple Pay Payment Processing Certificate is provisioned with Kashier.
4. Run a real, low-value smoke transaction through each method you support.
5. Ensure your backend webhook handler treats Kashier as the source of truth — do not rely solely on the mobile SDK callback.
6. Test the failure paths once more in live mode (cancellation, declined card).
## Frequently asked questions [#frequently-asked-questions]
**Do I need my own Apple Pay merchant ID or certificate?**
Yes. Apple Pay is per-merchant: register your own Apple Pay Merchant ID, add it to your Xcode entitlement, and pass it to `KashierSDK.initialize(appleMerchantId: …)`. You also need a Payment Processing Certificate provisioned with Kashier so your tokens can be decrypted — this onboarding is currently manual (Preview); open a Kashier support ticket.
**Why doesn't startPayment need a BuildContext?**
Because `KashierPaymentProvider` holds a global overlay reference. The SDK pushes its sheet onto that overlay rather than your Navigator, so it works from any layer of your app — including background callbacks, Bloc handlers, services, etc.
**Can I switch language at runtime?**
Yes — call `KashierSDK.initialize(mode: ..., language: KashierLanguage.ar)` again with the new language. This re-creates the internal API client and re-registers payment handlers.
**What happens if the user cancels Apple Pay?**
`onFailure` fires with `KashierErrorCode.userCancelled`. Suppress your error UI for that code — treat it the same as a benign back-tap.
**My wallet payment never fires onSuccess. Why?**
The customer hasn't completed the payment in their wallet app. The SDK polls Kashier for up to 5 minutes; if still pending at the cap it shows "I have paid" (→ terminal `onPending`), and an outright timeout surfaces `onFailure(paymentTimeout)`. The session may still settle later — treat the server webhook as authoritative.
**What happens if the user cancels the 3DS challenge?**
The SDK doesn't fail immediately. It runs a short cancel safety-net reconcile (5 attempts over \~60s) in case the payment actually went through, then fires `onSuccess`/`onFailure` if a result is found, or `onFailure(userCancelled)` if nothing settled.
**How do I customize the sheet's brand color?**
Set `brandColor` on the session when your backend creates it. Accepts 6-digit RRGGBB or 8-digit AARRGGBB, with or without #. Invalid values fall back to Kashier's primary color.
**The Apple Pay button doesn't show on Android. Is that a bug?**
No — Apple Pay is iOS-only. The button still renders on Android but tapping it surfaces applePayNotAvailable. The unified payment sheet hides Apple Pay entirely on Android.
**Where do I get a session ID?**
Your backend creates one by calling Kashier's [Payment Sessions API](/docs/accept-payments/payment-sessions). Backend integration is out of scope for this guide.
**Is the SDK PCI-compliant?**
Card data never touches your app's process. The SDK collects card input in its own form and tokenizes directly against Kashier FEP. Your app stays out of PCI scope as long as you use the SDK's card form.
**Can I show my own custom payment UI instead of the SDK's sheet?**
Not supported in v1. The SDK owns the sheet UI to ensure consistent behavior across methods. If you need a deeply custom flow, contact Kashier support.
**Does the SDK support recurring or stored-credential payments?**
Yes — through the save-card feature. Set `session.saveCard = "optional"` or "forced" and `session.retrieveSavedCard = true` on subsequent sessions for the same customer. The SDK handles tokenization; your backend issues the recurring charge against the saved token.
**Does the SDK work with MaterialApp.router (GoRouter, AutoRoute)?**
Yes. `KashierPaymentProvider` is independent of routing — wrap whichever app widget you use. The SDK overlay sits above the router's navigation stack.
**What happens if the device goes offline mid-payment?**
The SDK pre-checks connectivity (via connectivity\_plus) before each API call and surfaces `KashierErrorCode.networkError` if offline. Mid-flight network drops are caught and reported the same way.
**Can two startPayment calls run at once?**
No — only one sheet can be visible at a time. Call `startPayment` once and wait for one of the callbacks to fire before calling again.
# Accept payments (/docs/accept-payments)
Kashier gives you several ways to accept a payment. Pick the one that matches
how much you want to build:
| You want | Use | Effort |
| --------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------ |
| A checkout page hosted by Kashier, reached by an API call | [Payment sessions](/docs/accept-payments/payment-sessions) | Low |
| The checkout embedded in your own page (iframe) | [Embed the checkout](/docs/accept-payments/payment-sessions#step-2-send-the-customer-to-pay) | Low |
| A payment page you can share by link, with no code at all | [Payment links](/docs/accept-payments/payment-links) | None |
| Apple Pay directly on your site | [Apple Pay](/docs/accept-payments/apple-pay) | Medium |
| A QR code or request to pay over InstaPay | [InstaPay](/docs/accept-payments/instapay) | Medium |
| Egyptian Meeza cards, submitted as ordinary cards | [Meeza](/docs/accept-payments/meeza) | None |
| Charging customers on a schedule | [Recurring payments](/docs/accept-payments/recurring) | Medium |
| Full control over the card form and payment flow | [Direct API integration](/docs/direct-api) | High |
Before building the payment, a couple of supporting tools are worth knowing about:
* [Currency conversion](/docs/accept-payments/currency-conversion) — look up live exchange rates for cross-currency pricing.
* [Product catalog](/docs/accept-payments/products) — build itemized payment links and checkout pages from products instead of a flat amount.
After the payment, you can manage the full lifecycle:
* [Authorize and capture](/docs/accept-payments/authorize-capture) — hold funds first, capture later.
* [Transactions](/docs/accept-payments/transactions) — query payment status and history.
* [Refunds](/docs/accept-payments/refunds) and [void](/docs/accept-payments/void).
* [Settlement](/docs/accept-payments/settlement) and [order reconciliation](/docs/accept-payments/order-reconciliation).
* [Webhooks](/docs/webhooks) — get notified server-to-server about every event.
# InstaPay (/docs/accept-payments/instapay)
Collect a payment over InstaPay, Egypt's instant payment network, by generating a QR code the customer scans or by pushing a request to pay to a payer address you already know.
InstaPay is a first-class payment method: you select it with `paymentMethod.type = "instapay"`. It is a collection rail, not a card rail, so it has no tokenization, no recurring and no authorize/capture. It does support refund, cancel and transaction force closure.
The flow is:
1. You initiate a collection — `GENERATE_QR` for a scan-to-pay QR code, or `INITIATE_R2P` to push a pay request to a payer address.
2. The customer approves the request in their bank or wallet app.
3. Kashier notifies you through the standard server [webhook](/docs/webhooks).
## Initiate a collection [#initiate-a-collection]
An InstaPay collection is a [Direct API](/docs/direct-api) order: you post it to the same `POST /v3/orders` endpoint used for cards and wallets, and pick the collection method with `apiOperation`.
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------ |
| TEST-URL | [https://test-fep.kashier.io/v3/orders](https://test-fep.kashier.io/v3/orders) |
| LIVE-URL | [https://fep.kashier.io/v3/orders](https://fep.kashier.io/v3/orders) |
| Method | POST |
### Headers [#headers]
| Key | Description |
| ------------ | ------------------------------------------------------------ |
| Kashier-Hash | Order hash [generated in hashing](/docs/direct-api/hashing). |
| Content-Type | application/json |
### Operations [#operations]
| Operation | Collection method |
| -------------- | ----------------------------------------------- |
| `GENERATE_QR` | QR code the customer scans to pay |
| `INITIATE_R2P` | Request to pay, pushed to a known payer address |
You do not choose the provider. InstaPay is selected with `paymentMethod.type = "instapay"`; which acquirer that resolves to is set up on your account by Kashier, not per request.
### InstaPay fields [#instapay-fields]
The `paymentMethod.instapay` object:
| Field | Type | Required | Description |
| ---------------- | ------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `validity` | String | Yes | Validity period of the collection request. Required for both `GENERATE_QR` and `INITIATE_R2P` — without it the request fails with `request.paymentMethod?.instapay?.validity is required for IPN generate QR`. |
| `payerAddress` | String | Yes for `INITIATE_R2P` | The payer's InstaPay address. Only sent when the operation is `INITIATE_R2P`. |
| `tip` | Boolean | No | Set to `true` to allow a tip on the collection. |
| `convenienceFee` | Number | No | Convenience fee to add to the collection. |
| `refundSource` | String | No | Carried for refund flows. |
### Request [#request]
```json
{
"apiOperation": "INITIATE_R2P",
"interactionSource": "ECOMMERCE",
"order": {
"reference": "",
"amount": 100.00,
"currency": "EGP"
},
"paymentMethod": {
"type": "instapay",
"instapay": {
"payerAddress": "payer@instapay",
"validity": "30"
}
},
"merchantId": "MID-XXXXX-XXX"
}
```
For a QR collection, send `"apiOperation": "GENERATE_QR"` and omit `paymentMethod.instapay.payerAddress`.
## Supported operations [#supported-operations]
| Operation | Type |
| --------------------------- | ------------ |
| `GENERATE_QR` | Initiate |
| `INITIATE_R2P` | Initiate |
| `REFUND` | Post-payment |
| `CANCEL` | Post-payment |
| `TRANSACTION_FORCE_CLOSURE` | Post-payment |
Tokenization, recurring and authorize/capture are not available on InstaPay. See [refunds](/docs/accept-payments/refunds) and [void](/docs/accept-payments/void) for the shared post-payment contract.
## Results and response codes [#results-and-response-codes]
A successful InstaPay collection reaches you as `status: SUCCESS` with `transactionResponseCode` `00` ("Approved") — the same approved code as any other payment. Every other code is a `FAILURE` and carries the InstaPay code itself, for example `20903`.
InstaPay uses its own five-digit code space, separate from the card and ISO codes in [payment reason codes](/docs/accept-payments/payment-reason-codes). Match on the exact code.
| Code | Meaning |
| ------- | -------------------------------------------------------------------------------- |
| `00000` | Success |
| `20101` | Payer Payment Address is not registered |
| `20102` | Invalid validity period |
| `20103` | Declined, Merchant Collection Limits Exceeded |
| `20201` | Declined by Consumer |
| `20202` | Declined, Payment Order Expired |
| `20203` | Declined, Merchant Collection Limits Exceeded |
| `20204` | Declined by Consumer bank |
| `20301` | Refund Declined, Applicable Refund amount for this transaction has been exceeded |
| `20302` | Transaction has already been refunded |
| `20303` | Invalid Merchant Reference Number |
| `20304` | Invalid Original Transaction ID |
| `20305` | Refund Declined, insufficient funds |
| `20306` | Refund Declined, invalid Merchant Access Permission |
| `20307` | Refund Declined, Original transaction exceeded refund duration |
| `20308` | Refund In Progress |
| `20309` | Refund Declined |
| `20310` | Refund not concluded, please inquire the refund |
| `20401` | Invalid Merchant Reference Number |
| `20402` | Cannot be cancelled, cancellation window has expired. Kindly use refund option |
| `20501` | Invalid Merchant Reference Number |
| `20502` | Declined by Consumer |
| `20503` | Declined, Payment Order Expired |
| `20504` | Declined, Merchant Collection Limits Exceeded |
| `20901` | Invalid JSON structure or field format |
| `20902` | Invalid version number |
| `20903` | Invalid request signature |
| `20904` | Your request cannot be processed please try again later |
| `20905` | Merchant is suspended or blocked, please refer back to your bank |
| `20906` | Invalid Merchant ID |
| `20907` | Invalid Acquirer Merchant ID |
| `20908` | Invalid Acquirer Bank ID |
| `20909` | Duplicated Merchant Reference Number |
| `20999` | Service currently not available, Please try again later |
Only the `2xxxx` codes above are mapped. A code outside this table falls back to the general message set and can arrive as `k_default`, so treat an unknown InstaPay code as a plain failure rather than looking it up.
## Webhooks [#webhooks]
InstaPay has no webhook event of its own. Kashier notifies you with the standard server webhook, where `event` is the operation — `pay`, `refund` and so on. See [webhooks](/docs/webhooks).
# Meeza (/docs/accept-payments/meeza)
Accept Meeza, Egypt's national card scheme, by submitting the card exactly like any other card. Send `paymentMethod.type = "card"` — never `"meeza"`. Meeza is not a payment method or a provider at Kashier; it is a card brand that Kashier detects from the card number.
## What changes [#what-changes]
Nothing in your request. Everything in the [customized card form](/docs/direct-api/card-form), [3D Secure](/docs/direct-api/3d-secure), [payment sessions](/docs/accept-payments/payment-sessions) and the direct API applies unchanged — you just submit a Meeza card number.
| Question | Answer |
| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Which `paymentMethod.type` do I send? | `card`. `meeza` is not a valid method type. |
| Are the response codes different? | No. Meeza returns the same `transactionResponseCode` dictionary and the same statuses as any card. See [payment reason codes](/docs/accept-payments/payment-reason-codes). |
| Does Meeza support 3D Secure, refund and void? | Yes, the same as any other card. |
| How do I identify a Meeza payment afterwards? | By its card brand: a Meeza transaction carries `cardBrand: "MEEZA"` in reporting and in the `sourceOfFunds` details on the webhook. |
## Meeza is rejected on recurring payments [#meeza-is-rejected-on-recurring-payments]
A Meeza card cannot be charged on a subscription or recurring-origin payment. Kashier rejects the request with `MEEZA_NOT_ALLOWED`:
| Field | Value |
| ------------- | ------------------------------- |
| `cause` | `cause.invalid.data` |
| `explanation` | `explanation.meeza.not.allowed` |
| `messageKey` | `message.meeza.not.allowed` |
| `status` | failed |
Meeza cards are not tokenizable for recurring the way Visa and Mastercard cards are. Use a Visa or Mastercard card for [recurring payments](/docs/accept-payments/recurring), and fall back to a one-off card payment if the customer only has a Meeza card.
## Testing [#testing]
Kashier does not publish a Meeza test card. The [test cards](/docs/get-started/testing) are Visa and Mastercard only — ask [techsupport@kashier.io](mailto:techsupport@kashier.io) if you need to test a Meeza card number.
# Mobile SDKs (/docs/accept-payments/mobile-sdks)
Drop-in payment libraries for accepting Kashier payments in your mobile app. Pick a platform to get started.
## Flutter SDK [#flutter-sdk]
Accept Apple Pay, mobile wallet, and card payments in your Flutter app from a single API call.
[Read the guide](/docs/accept-payments/flutter-sdk)
# Order reconciliation (/docs/accept-payments/order-reconciliation)
Kashier reconciliation is the process of verifying that the electronic payment and refund transactions processed in your system match the transactions reported by Kashier.
## Retrieve order details [#retrieve-order-details]
Kashier enables you to retrieve details of your orders.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------- |
| TEST URL | [https://test-api.kashier.io/v3/payment/orders](https://test-api.kashier.io/v3/payment/orders) |
| LIVE URL | [https://api.kashier.io/v3/payment/orders](https://api.kashier.io/v3/payment/orders) |
| Method | GET |
### Parameters [#parameters]
| Parameter | Type | Description | Required |
| --------- | --------------- | -------------------------------------------------------------------------------------------------------- | -------- |
| search | Query Parameter | Case-insensitive partial match on your merchant order id. Required — the request returns 400 without it. | true |
| status | Query Parameter | Order status, for example `CAPTURED`. Uppercased before matching. | false |
| startDate | Query Parameter | Lower bound on the order date. `startDate` after `endDate` returns 400. | false |
| endDate | Query Parameter | Upper bound on the order date. | false |
| page | Query Parameter | Page number. Default `1`. | false |
| limit | Query Parameter | Page size. Default `20`. | false |
### Headers [#headers]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location 'https://test-api.kashier.io/v3/payment/orders?search={{merchantOrderId}}&status={{status}}&startDate=2025-07-21&endDate=2025-12-23'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
### Reconciliation verdicts [#reconciliation-verdicts]
Every entry in an order's `transactions[]` array carries two reconciliation verdicts.
| Field | What it tells you |
| ------------------------------- | --------------------------------------------------------------------------------- |
| `reconcilation` | Kashier's verdict for this transaction against the provider/gateway. |
| `merchantWebhookReconciliation` | Whether the merchant webhook for this transaction was delivered and acknowledged. |
`reconcilation` has one `i`, not two. That is the spelling in the API response — match it exactly when you parse, and don't "correct" it to `reconciliation`. Note that the neighbouring `merchantWebhookReconciliation` **is** spelled with two.
Both fields use the same four values:
| Value | Meaning |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `OK` | Reconciled and consistent. Kashier compared the two sides and they matched. |
| `Failed` | Reconciliation ran but did not match, or could not be confirmed. Investigate this transaction. |
| `Not_Exists` | The counterpart record was not found on the side being reconciled — for example the transaction is absent at the provider, or there is no webhook delivery record. |
| `NA` | The default: **not yet reconciled**. Common on intermediate steps such as `3dsecure_verify` and `authenticate_payer`, which are not reconciled on their own. |
`OK` is a verdict about *agreement between two records*, not about the money. A transaction that failed at the provider and is correctly recorded as failed on both sides is also `reconcilation: "OK"`. To learn the outcome, read the transaction's own `status` (`SUCCESS`, `FAILURE`, `PENDING`, …) and `transactionResponseCode`. Never treat `OK` as a payment confirmation.
A transaction that Kashier declined on its own before it ever reached the acquirer carries `kashierBlocked: true`. Those transactions are excluded from the reconciliation jobs by design, so expect them to sit at `NA` permanently.
### Response structure [#response-structure]
```json
{
"status": "SUCCESS",
"message": "Orders retrieved successfully",
"data": [
{
"_id": "68235607d26af80012b574cf",
"order": {
"amount": 11179.51,
"currency": "EGP",
"callbackURL": "https://checkouts.kashier.io/en/payment-request/68235534b463b10012cb893c"
},
"status": "CAPTURED",
"isLockedForPaymentProcessing": false,
"provider": "mpgs",
"paymentAgreement": "regular",
"providerReconcilationResults": [],
"merchantId": "MID-123-123",
"merchantDetails": {
"storeName": "TEST-Demo",
"MCC": "1111",
"isLive": true,
"businessIndustry": "Airline",
"createdUserId": "5f8d0d55b54764421b7156c3",
"businessEmail": "demo@example.com",
"email": "merchant@example.com",
"businessContactInfo": {
"addressLine1": "cairo",
"addressLine2": "nasr city",
"governorate": "EG-C",
"zipcode": "12345",
"fax": "",
"hotlineNumber": "123456",
"landlineNumber": "123456"
},
"accountType": "PF",
"subMerchantName": "Demo",
"merchantIdentifier": "MID-123-123",
"tradingName": "Demo",
"merchantIdentifierBM": "1550000000",
"businessAddress": "cairo nasr city",
"apiKey": "YOUR_TEST_API_KEY",
"merchantId": "MID-123-123",
"webhook": {
"MID": "MID-123-123",
"apiKey": "YOUR_PAYMENT_API_KEY",
"isEnabled": true,
"url": "http://webhook_url.com"
},
"isTrustedMerchant": false,
"enableNotEnrolled3DSCaptcha": false
},
"date": "2025-05-13T14:24:07.190Z",
"orderReference": "TEST-ORD-193392644",
"lastModifiedDate": "2025-05-13T14:24:11.277Z",
"sessionId": "68235607d26af80012b574c9",
"orderId": "3e9b9e27-9900-4b17-982e-763f6ffd106b",
"merchantOrderId": "68235534b463b10012cb893c",
"totalRefundedAmount": 0,
"totalCapturedAmount": 11179.51,
"totalAuthorizedAmount": 11179.51,
"providerVersion": "68",
"method": "card",
"metaData": {
"kashierOriginType": "paymentLink",
"kashierOriginDetails": {
"name": "page with reqular item",
"id": "PL-2435857566",
"customerName": "سلمى حسن عبدالله حسن"
},
"termsAndConditions": {
"time": -3,
"ip": "197.37.152.242",
"userAgent": {
"browser": "Firefox",
"version": "138.0"
}
},
"kashier payment UI version": "V3",
"referral url": "https://checkouts.kashier.io/",
"merchantWebhook": "http://webhook_url.com"
},
"sourceOfFunds": {
"cardInfo": {
"maskedCard": "512345******2346",
"cardBrand": "Mastercard",
"cardHolderName": "John Doe",
"cardDataToken": "95d75151-8daa-4c89-ad19-2666f2dbec52",
"ccvToken": "476a148b-0482-468e-a25d-87687c20d1cb",
"expiryYear": "25",
"expiryMonth": "06",
"storedOnFile": "NOT_STORED",
"save": false,
"agreement": null
}
},
"paymentOrigin": "PL-2435857566",
"paymentType": "paymentLink",
"apiKeyId": "YOUR_API_KEY_ID",
"apiKeyName": "your-key-name",
"paymentChannel": "online | e-commerce",
"originalAmount": "11179.51",
"authorizationAmount": "11179.51",
"installmentAmountPerMonth": "0",
"originDetails": {
"name": "page with reqular item",
"id": "PL-2435857566",
"customerName": "سلمى حسن عبدالله حسن"
},
"serverWebhook": "",
"transactions": [
{
"feeDetails": {
"transaction": 0,
"processing": 0
},
"reconcilation": "NA",
"merchantWebhookReconciliation": "NA",
"isCurrentlyReconciled": false,
"isPF": true,
"_id": "68235607d26af80012b574d0",
"operation": "3dsecure_verify",
"gateWayOperation": "INITIATE_AUTHENTICATION",
"transactionId": "TX-24358575398",
"amount": 11179.51,
"currency": "EGP",
"status": "SUCCESS",
"feeTrxAmount": 0,
"metaData": {
"userAgent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
},
"apmTraceId": "8ab33a4f6ebf919d28fdcf1ede7ff9b0",
"requestDate": "2025-05-13T14:24:07.194Z",
"responseDate": "2025-05-13T14:24:07.971Z",
"transactionResponseCode": "AUTHENTICATION_IN_PROGRESS",
"transactionResponseMessage": {
"en": "Authentication in progress",
"ar": "المصادقة قيد التقدم"
}
},
{
"feeDetails": {
"transaction": 0,
"processing": 0
},
"reconcilation": "NA",
"merchantWebhookReconciliation": "NA",
"isCurrentlyReconciled": false,
"isPF": true,
"_id": "68235609d26af80012b574e3",
"operation": "authenticate_payer",
"gateWayOperation": "AUTHENTICATE_PAYER",
"transactionId": "TX-24358575399",
"status": "SUCCESS",
"amount": 11179.51,
"currency": "EGP",
"feeTrxAmount": 0,
"requestDate": "2025-05-13T14:24:09.604Z",
"targetedTransaction": "TX-24358575398",
"targetedTransactionOperation": "3dsecure_verify",
"apmTraceId": "50e5e86fa5fdd1a5e177a415b0355e98",
"responseDate": "2025-05-13T14:24:10.267Z",
"transactionResponseCode": "APPROVED",
"transactionResponseMessage": {
"en": "Approved",
"ar": "تمت الموافقة"
}
},
{
"feeDetails": {
"transaction": 0,
"processing": 0
},
"reconcilation": "OK",
"merchantWebhookReconciliation": "Failed",
"isCurrentlyReconciled": false,
"isPF": true,
"_id": "6823560ad26af80012b574e8",
"operation": "pay",
"gateWayOperation": "PAY",
"transactionId": "TX-24358575400",
"status": "SUCCESS",
"amount": 11179.51,
"currency": "EGP",
"feeTrxAmount": 0,
"requestDate": "2025-05-13T14:24:10.283Z",
"targetedTransaction": "TX-24358575398",
"targetedTransactionOperation": "3dsecure_verify",
"bankSettlementDate": "2025-05-13T14:24:11.267Z",
"bankValueDate": "2025-05-14T07:00:00.000Z",
"gatewayTransactionReference": "228001",
"gatewayTransactionUniqueKey": "228001#3e9b9e27-9900-4b17-982e-763f6ffd106b",
"responseDate": "2025-05-13T14:24:11.267Z",
"settlementDate": "2025-05-13T14:24:11.267Z",
"transactionResponseCode": "00",
"transactionResponseMessage": {
"en": "Approved",
"ar": "تمت الموافقة"
}
}
],
"apiOperation": null,
"interChangeRate": "ON_US",
"createdAt": "2025-05-13T14:24:07.201Z",
"updatedAt": "2025-05-13T14:24:11.919Z",
"__v": 0
},
{
"_id": "68235370940bd50012e4e450",
"order": {
"amount": 11179.51,
"currency": "EGP",
"callbackURL": "https://checkouts.kashier.io/en/payment-request/68221102b463b10012cb80fb"
},
"status": "CAPTURED",
"isLockedForPaymentProcessing": false,
"provider": "mpgs",
"paymentAgreement": "regular",
"providerReconcilationResults": [],
"merchantId": "MID-123-123",
"merchantDetails": {
"storeName": "TEST-Demo",
"MCC": "1111",
"isLive": true,
"businessIndustry": "Airline",
"createdUserId": "5f8d0d55b54764421b7156c3",
"businessEmail": "demo@example.com",
"email": "merchant@example.com",
"businessContactInfo": {
"addressLine1": "cairo",
"addressLine2": "nasr city",
"governorate": "EG-C",
"zipcode": "12345",
"fax": "",
"hotlineNumber": "123456",
"landlineNumber": "123456"
},
"accountType": "PF",
"subMerchantName": "Demo",
"merchantIdentifier": "MID-123-123",
"tradingName": "Demo",
"merchantIdentifierBM": "1550000000",
"businessAddress": "cairo nasr city",
"apiKey": "YOUR_TEST_API_KEY",
"merchantId": "MID-123-123",
"webhook": {
"MID": "MID-123-123",
"apiKey": "YOUR_PAYMENT_API_KEY",
"isEnabled": true,
"url": "http://webhook_url.com"
},
"isTrustedMerchant": false,
"enableNotEnrolled3DSCaptcha": false
},
"date": "2025-05-13T14:13:04.956Z",
"orderReference": "TEST-ORD-193392643",
"lastModifiedDate": "2025-05-13T14:13:09.603Z",
"sessionId": "68235370940bd50012e4e44a",
"orderId": "d4240d56-49b7-44ca-a12d-e46ab1f22ca0",
"merchantOrderId": "68221102b463b10012cb80fb",
"totalRefundedAmount": 0,
"totalCapturedAmount": 11179.51,
"totalAuthorizedAmount": 11179.51,
"providerVersion": "68",
"method": "card",
"metaData": {
"kashierOriginType": "paymentLink",
"kashierOriginDetails": {
"name": "page with reqular item",
"id": "PL-2435857563",
"customerName": "سلمى حسن عبدالله حسن"
},
"termsAndConditions": {
"time": -3,
"ip": "197.37.152.242",
"userAgent": {
"browser": "Firefox",
"version": "138.0"
}
},
"kashier payment UI version": "V3",
"referral url": "https://checkouts.kashier.io/",
"merchantWebhook": "http://webhook_url.com"
},
"sourceOfFunds": {
"cardInfo": {
"maskedCard": "512345******2346",
"cardBrand": "Mastercard",
"cardHolderName": "John Doe",
"cardDataToken": "c9024ffb-bb25-4426-b793-954d89f6ff2b",
"ccvToken": "c29a5430-7077-41f2-832a-36ced1f1caf2",
"expiryYear": "25",
"expiryMonth": "06",
"storedOnFile": "NOT_STORED",
"save": false,
"agreement": null
}
},
"paymentOrigin": "PL-2435857563",
"paymentType": "paymentLink",
"apiKeyId": "YOUR_API_KEY_ID",
"apiKeyName": "your-key-name",
"paymentChannel": "online | e-commerce",
"originalAmount": "11179.51",
"authorizationAmount": "11179.51",
"installmentAmountPerMonth": "0",
"originDetails": {
"name": "page with reqular item",
"id": "PL-2435857563",
"customerName": "سلمى حسن عبدالله حسن"
},
"serverWebhook": "",
"transactions": [
{
"feeDetails": {
"transaction": 0,
"processing": 0
},
"reconcilation": "NA",
"merchantWebhookReconciliation": "NA",
"isCurrentlyReconciled": false,
"isPF": true,
"_id": "68235370940bd50012e4e451",
"operation": "3dsecure_verify",
"gateWayOperation": "INITIATE_AUTHENTICATION",
"transactionId": "TX-24358575395",
"amount": 11179.51,
"currency": "EGP",
"status": "SUCCESS",
"feeTrxAmount": 0,
"metaData": {
"userAgent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:138.0) Gecko/20100101 Firefox/138.0"
},
"apmTraceId": "2ba18c663f73da63d44ba2bda730e382",
"requestDate": "2025-05-13T14:13:04.961Z",
"responseDate": "2025-05-13T14:13:06.005Z",
"transactionResponseCode": "AUTHENTICATION_IN_PROGRESS",
"transactionResponseMessage": {
"en": "Authentication in progress",
"ar": "المصادقة قيد التقدم"
}
},
{
"feeDetails": {
"transaction": 0,
"processing": 0
},
"reconcilation": "NA",
"merchantWebhookReconciliation": "NA",
"isCurrentlyReconciled": false,
"isPF": true,
"_id": "68235373940bd50012e4e464",
"operation": "authenticate_payer",
"gateWayOperation": "AUTHENTICATE_PAYER",
"transactionId": "TX-24358575396",
"status": "SUCCESS",
"amount": 11179.51,
"currency": "EGP",
"feeTrxAmount": 0,
"requestDate": "2025-05-13T14:13:07.839Z",
"targetedTransaction": "TX-24358575395",
"targetedTransactionOperation": "3dsecure_verify",
"apmTraceId": "91cbdb5e9d8ae1c70c4c9aebc9ede652",
"responseDate": "2025-05-13T14:13:08.572Z",
"transactionResponseCode": "APPROVED",
"transactionResponseMessage": {
"en": "Approved",
"ar": "تمت الموافقة"
}
},
{
"feeDetails": {
"transaction": 0,
"processing": 0
},
"reconcilation": "OK",
"merchantWebhookReconciliation": "Failed",
"isCurrentlyReconciled": false,
"isPF": true,
"_id": "68235374940bd50012e4e469",
"operation": "pay",
"gateWayOperation": "PAY",
"transactionId": "TX-24358575397",
"status": "SUCCESS",
"amount": 11179.51,
"currency": "EGP",
"feeTrxAmount": 0,
"requestDate": "2025-05-13T14:13:08.585Z",
"targetedTransaction": "TX-24358575395",
"targetedTransactionOperation": "3dsecure_verify",
"bankSettlementDate": "2025-05-13T14:13:09.591Z",
"bankValueDate": "2025-05-14T07:00:00.000Z",
"gatewayTransactionReference": "226991",
"gatewayTransactionUniqueKey": "226991#d4240d56-49b7-44ca-a12d-e46ab1f22ca0",
"responseDate": "2025-05-13T14:13:09.591Z",
"settlementDate": "2025-05-13T14:13:09.591Z",
"transactionResponseCode": "00",
"transactionResponseMessage": {
"en": "Approved",
"ar": "تمت الموافقة"
}
}
],
"apiOperation": null,
"interChangeRate": "ON_US",
"createdAt": "2025-05-13T14:13:04.970Z",
"updatedAt": "2025-05-13T14:13:10.210Z",
"__v": 0
}
],
"pagination": {
"total": 2,
"page": 1,
"limit": 20,
"pages": 1
}
}
```
# Payment links (/docs/accept-payments/payment-links)
Create and share payment links your customers can open and pay — no integration required. This API lets you:
* **Create customer-specific links** — one link per customer.
* **Create links in bulk** — generate links for many customers at once.
* **Use currency conversion** — create links in a foreign "virtual" currency (e.g. `USD_VIRTUAL`) that settles in EGP; the customer pays the EGP equivalent at the applicable exchange rate. Requires the currency conversion feature to be enabled on your account.
## Get all payment links [#get-all-payment-links]
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link](https://test-api.kashier.io/v2/payment-link) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link](https://api.kashier.io/v2/payment-link) |
| Method | GET |
Full parameter and response reference → [List payment links](/docs/api-reference/payment-links/listPaymentLinks).
### Parameters [#parameters]
| Parameter | Type | Description |
| ---------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| currency | Query Parameter | Filter based on currency (example: EGP). If the currency conversion feature is enabled on your account, you can also filter by a virtual currency view code (example: `USD_VIRTUAL`) to get payment links created in that foreign currency. |
| state | Query Parameter | Payment link state. |
| startDueDate | Query Parameter | Start due date. |
| endDueDate | Query Parameter | End due date. |
| startAmountRange | Query Parameter | Start amount range. |
| endAmountRange | Query Parameter | End amount range. |
| startDate | Query Parameter | Start date. |
| endDate | Query Parameter | End date. |
| paymentStatus | Query Parameter | Payment state (example: PAID, UNPAID, OVERDUE, EXPIRED). |
| paymentType | Query Parameter | Payment type (example: FIXED\_AMOUNT, ITEMIZED). |
| search | Query Parameter | Search for a payment link by its id (example: PL-2348668602). |
| page | Query Parameter | Current page number (used for pagination). |
| limit | Query Parameter | Number of items per page (used for pagination). |
### Headers [#headers]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location 'https://test-api.kashier.io/v2/payment-link?
currency=EGP&state=&startAmountRange=&endAmountRange=&paymentStatus=paid&search=&page=1&limit=10'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--header 'Accept: application/json'
```
Full parameter and response reference → [Get all payment links](/docs/api-reference/payment-links/listPaymentLinks).
The virtual currency fields (virtualAmount, virtualCurrency, etc.) and exchangeRateServiceAvailable only apply to [currency conversion](#currency-conversion) payment links.
## Get payment link details [#get-payment-link-details]
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link/:paymentLinkId](https://test-api.kashier.io/v2/payment-link/:paymentLinkId) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link/:paymentLinkId](https://api.kashier.io/v2/payment-link/:paymentLinkId) |
| Method | GET |
Full parameter and response reference → [Get payment link details](/docs/api-reference/payment-links/getPaymentLink).
### Headers [#headers-1]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location 'https://test-api.kashier.io/v2/payment-link/:paymentLinkId'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--header 'Accept: application/json'
```
Full parameter and response reference → [Get payment link details](/docs/api-reference/payment-links/getPaymentLink).
The virtual currency fields are null unless the link was created with a [currency conversion](#currency-conversion) currency.
## Create a payment link [#create-a-payment-link]
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link](https://test-api.kashier.io/v2/payment-link) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link](https://api.kashier.io/v2/payment-link) |
| Method | POST |
Full parameter and response reference → [Create a payment link](/docs/api-reference/payment-links/createPaymentLink).
### Headers [#headers-2]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location 'https://test-api.kashier.io/v2/payment-link'
--header 'Content-Type: application/json'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--data '{
"customer":{
"name":"John Doe"
},
"totalAmount": 200,
"description": "",
"isManualCapture": false,
"paymentType": "simple",
"currency": "EGP",
"state": "submitted",
"extraFees": [
{
"name": "VAT",
"flatFee": 0,
"rate": 5
},
{
"name": "annualFee",
"flatFee": 500,
"rate": 0
}
],
"dueDate":"2027-12-27T21:59:00.000Z",
"isSuspendedPayment": false,
"referenceId":"123ss456"
}'
```
Full parameter and response reference → [Create a payment link](/docs/api-reference/payment-links/createPaymentLink).
### Currency conversion [#currency-conversion]
Set currency to a virtual currency code (`USD_VIRTUAL`, `EUR_VIRTUAL`, `GBP_VIRTUAL`, `SAR_VIRTUAL`, `AED_VIRTUAL`) with totalAmount expressed in that currency to collect payment in a foreign currency while settling in EGP.
Requires the currency conversion feature — contact Kashier's backoffice/support to enable it. Without it, a virtual currency returns a 400: "Currency conversion feature is not enabled."
```bash
curl --location 'https://test-api.kashier.io/v2/payment-link'
--header 'Content-Type: application/json'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--data '{
"customer":{
"name":"John Doe"
},
"totalAmount": 40,
"description": "",
"isManualCapture": false,
"paymentType": "simple",
"currency": "USD_VIRTUAL",
"state": "submitted",
"extraFees": [],
"isSuspendedPayment": false,
"referenceId":"123ss456"
}'
```
## Payment links bulk upload [#payment-links-bulk-upload]
Bulk uploading payment links consists of two steps:
1. Upload the data for review using the /import endpoint.
2. Save the data if it is valid using the correlationId returned from the /import step.
* If errors are found in the data, they will be returned for correction.
* If validation passes, the API returns a correlationId.
* The correlationId serves as a reference to your validated data.
### Uploading the sheet for review [#uploading-the-sheet-for-review]
First, you need to upload the Excel sheet for review:
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/paymentRequest/import?currency=EGP](https://test-api.kashier.io/v2/paymentRequest/import?currency=EGP) |
| LIVE-URL | [https://api.kashier.io/v2/paymentRequest/import?currency=EGP](https://api.kashier.io/v2/paymentRequest/import?currency=EGP) |
| Method | POST |
Replace currency=EGP with a virtual currency code (e.g. currency=USD\_VIRTUAL) to bulk-upload payment links in a foreign currency — see [currency conversion](#currency-conversion). This requires the currency conversion feature to be enabled on your account.
#### Headers [#headers-3]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl 'https://test-api.kashier.io/v2/paymentRequest/import?currency=EGP'
-X POST
-H 'Authorization: YOUR_TEST_SECRET_KEY'
-H 'Access-Control-Allow-Origin: *'
-H 'Content-Type: multipart/form-data; boundary=----geckoformboundary9c673decdbea595725978e0df26aa91b'
--data-binary
$'------geckoformboundary9c673decdbea595725978e0df26aa91b
Content-Disposition: form-data; name="file"; filename="payment-link-temp-updated.xlsx"
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
------geckoformboundary9c673decdbea595725978e0df26aa91b--
'
```
The **Content-Type** in the request header must be set to multipart/form-data, since you are uploading an Excel sheet in the request body.
#### Response structure [#response-structure]
```json
{
"body": {
"paymentRequests": [
{
"isSuspendedPayment": false,
"merchantId": "MID-XXXXX-XXX",
"storeName": "TEST-Demo",
"invoiceReferenceId": "Abc_123456",
"paymentType": "professional",
"totalAmount": "1102.50",
"availableAmountForRefund": "0.00",
"totalAmountWithoutFees": "1050.00",
"description": "",
"creationDate": "2025-08-26T10:01:58.991Z",
"dueDate": null,
"invoiceItems": [
{
"description": "product 1 name",
"quantity": "3",
"unitPrice": "100.00",
"subTotal": "300.00"
},
{
"description": "product 2 name",
"quantity": "5",
"unitPrice": "150.00",
"subTotal": "750.00"
}
],
"customerName": "Ahmed Mohamed",
"extraFees": [
{
"name": "Fee1 Name",
"flatFee": 0,
"rate": 2
},
{
"name": "Fee 2 name",
"flatFee": 10,
"rate": 0
},
{
"name": "vat",
"flatFee": 0,
"rate": 5
}
],
"currency": "EGP",
"state": "submitted",
"paymentStatus": "unpaid",
"paymentRequestId": "INV-24358575125",
"paymentLinkId": "PL-24358575125",
"merchantInfo": {
"storeName": "TEST-Demo"
},
"createdByUserId": "5f8d0d55b54764421b7156c3",
"isPaymentLink": true,
"isBulkCreated": true,
"referenceId": "Abc_123456",
"labels": [
"Label 1",
" Label 2",
" Label 3"
],
"errors": null
}
],
"correlationId": "508cf034-447a-4a14-8154-09b50c14a6c6"
},
"message": "Imported successfully"
}
```
### Saving the uploaded payment links [#saving-the-uploaded-payment-links]
If the uploaded data passes validation during the review step, you can save it to the database using the correlationId returned in the /import response.
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save](https://test-api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save) |
| LIVE-URL | [https://api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save](https://api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId\&operation=save) |
| Method | POST |
Full parameter and response reference → [Save bulk-uploaded payment links](/docs/api-reference/payment-links/saveImportedPaymentLinks).
#### Headers [#headers-4]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
The request carries no body — the `correlationId` from the import step identifies the validated rows, and `operation=save` says what to do with them.
```bash
curl 'https://test-api.kashier.io/v2/paymentRequest/saveInvoice?correlationId=:correlationId&operation=save'
-X POST
-H 'Accept: application/json, text/plain, */*'
-H 'Authorization: YOUR_TEST_SECRET_KEY'
```
Full parameter and response reference → [Save the uploaded payment links](/docs/api-reference/payment-links/saveImportedPaymentLinks).
## Update a payment link [#update-a-payment-link]
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link/paymentLinkId](https://test-api.kashier.io/v2/payment-link/paymentLinkId) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link/paymentLinkId](https://api.kashier.io/v2/payment-link/paymentLinkId) |
| Method | PUT |
Full parameter and response reference → [Update payment link](/docs/api-reference/payment-links/updatePaymentLink).
### Headers [#headers-5]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location --request PUT 'https://test-api.kashier.io/v2/payment-link/{{paymentLinkId}}'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--header 'Content-Type: application/json'
--data '{
"paymentLink":{
"customer":{
"name":"John doe"
},
"totalAmount": 200,
"description": "",
"isManualCapture": false,
"paymentType": "simple",
"currency": "EGP",
"state": "submitted",
"extraFees": [
{ "name": "tax", "flatFee": 0, "rate": 5 },
{ "name": "taxen", "flatFee": 0, "rate": 10 }
]
}
}'
```
## Delete a payment link [#delete-a-payment-link]
| Endpoint | Value |
| -------- | ---------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link/paymentLinkId](https://test-api.kashier.io/v2/payment-link/paymentLinkId) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link/paymentLinkId](https://api.kashier.io/v2/payment-link/paymentLinkId) |
| Method | DELETE |
Full parameter and response reference → [Delete payment link](/docs/api-reference/payment-links/deletePaymentLink).
### Headers [#headers-6]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location --request DELETE 'https://test-api.kashier.io/v2/payment-link/{{paymentLinkId}}'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
## Share a payment link [#share-a-payment-link]
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------------------------------ |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link/share](https://test-api.kashier.io/v2/payment-link/share) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link/share](https://api.kashier.io/v2/payment-link/share) |
| Method | POST |
Full parameter and response reference → [Share payment link](/docs/api-reference/payment-links/sharePaymentLink).
### Headers [#headers-7]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location 'https://test-api.kashier.io/v2/payment-link/share'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--header 'Content-Type: application/json'
--data-raw '{
"operation": "email",// sms, email
"urlIdentifier": "PL-ABC123DEF456",
"key": "customer@example.com"
}'
```
### Body parameters [#body-parameters]
| Key | Required | Description |
| --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `operation` | yes | `email` or `phone` — how the link is delivered. |
| `key` | yes | The email address or phone number to send it to. |
| `urlIdentifier` | yes | The payment link's identifier, in the `PL-…` form returned by [Create a payment link](#create-a-payment-link) — not the internal `_id`. |
| `countryCode` | no | Country code for `phone`, in `+XXX` form (for example `+20`). |
Replace both `urlIdentifier` and `key` below before sending — `PL-ABC123DEF456` is a placeholder and will be rejected, and a valid link plus a real address genuinely emails or texts the recipient.
Full parameter and response reference → [Share a payment link](/docs/api-reference/payment-links/sharePaymentLink).
## Export payment links [#export-payment-links]
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v2/payment-link/export](https://test-api.kashier.io/v2/payment-link/export) |
| LIVE-URL | [https://api.kashier.io/v2/payment-link/export](https://api.kashier.io/v2/payment-link/export) |
| Method | POST |
Full parameter and response reference → [Export payment links](/docs/api-reference/payment-links/exportPaymentLinks).
This endpoint does not stream a file back. It queues the export and emails it — the response only acknowledges that the email was sent. Requires the payment-link export permission on your key.
### Query parameters [#query-parameters]
The export takes the same filters as [Get all payment links](#get-all-payment-links), narrowing which links end up in the file, plus:
| Key | Description |
| ----------- | ------------------------------------- |
| `email` | Address to send the export to. |
| `branchIds` | Comma-separated branch IDs to export. |
The export takes no request body — the filters above are the whole request.
### Headers [#headers-8]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location --request POST 'https://test-api.kashier.io/v2/payment-link/export?limit=20&page=1&sortType=-1&paymentType=all¤cy=EGP'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
```
Full parameter and response reference → [Export payment links](/docs/api-reference/payment-links/exportPaymentLinks).
## Mark as paid [#mark-as-paid]
| Endpoint | Value |
| -------- | ------------------------------------------------------------------------------ |
| TEST-URL | [https://test-fep.kashier.io/v3/orders](https://test-fep.kashier.io/v3/orders) |
| LIVE-URL | [https://fep.kashier.io/v3/orders](https://fep.kashier.io/v3/orders) |
| Method | POST |
Full parameter and response reference → [Pay with a card token, or mark a payment link as paid](/docs/api-reference/tokens/payWithToken).
### Headers [#headers-9]
| Key | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
```bash
curl --location 'https://test-fep.kashier.io/v3/orders'
--header 'Authorization: YOUR_TEST_SECRET_KEY'
--header 'Content-Type: application/json'
--data '
{
"order":{
"reference":"67c59e14e626cf0012fdb9de",
"amount":65,
"currency":"EGP"
},
"origin":{
"id":"PL-XXXXXXXXXX"
},
"apiOperation":"PAY",
"interactionSource":"ECOMMERCE",
"paymentMethod":{
"type":"cash"
},
"metaData":{
"kashierOriginType":"paymentLink",
"kashierOriginDetails":{
"name":"",
"id":"PL-XXXXXXXXXX",
"customerName":"bashar",
"createdUserId":"5f8d0d55b54764421b7156c3"
}
}
}'
```
Every identifier in this body has to come from your own account — the `PL-…` link ID in `origin.id`, the order reference, and the `createdUserId` of the dashboard user recording the payment. There's no placeholder set that produces a valid request, so a runnable panel here would only ever return a `400` — "This request is invalid". Fill the values above into the curl and run it against your own test account instead. Note that a successful call records a real cash payment against the link and moves it to `paid`.
### Body description [#body-description]
| Key | Description |
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| order.reference | A unique identifier for the order (e.g., "6773fa351652132156d37f7b"). |
| order.amount | The total amount of the order (e.g., 200). |
| order.currency | The currency in which the transaction is processed (e.g., 'EGP' for Egyptian Pounds). |
| origin.id | A unique identifier for the origin of the payment request, such as an invoice number (e.g., "INV-2334424102"). |
| apiOperation | Specifies the operation being performed. 'PAY' indicates that this is a payment request. |
| interactionSource | Defines where the payment is initiated from. "ECOMMERCE" indicates that the payment is processed online via an e-commerce platform. |
| paymentMethod | The payment method type (e.g., "cash"). |
| metaData.kashierOriginType | The type of origin associated with the payment (e.g., "paymentLink"). |
| metaData.kashierOriginDetails.id | The unique identifier of the origin (e.g., "PL-2435857557"). |
| metaData.kashierOriginDetails.customerName | The name of the customer associated with this payment (e.g., "John doe"). |
| metaData.kashierOriginDetails.createdUserId | The user ID of the person who created this transaction (e.g., "5f8d0d55b54764421b7156c3"). |
### Response structure [#response-structure-1]
```json
{
"response":{
"apiOperation":"PAY",
"operation":"pay",
"currency":"EGP",
"result":"SUCCESS",
"status":"SUCCESS",
"authorizationNumber":"",
"authentication":{
},
"paymentMethod":{
"type":"cash"
},
"metaData":{
"kashierOriginType":"paymentLink",
"kashierOriginDetails":{
"name":"",
"id":"PL-XXXXXXXXXX",
"customerName":"John",
"createdUserId":"5f8d0d55b54764421b7156c3"
},
"termsAndConditions":{
"ip":"197.37.122.82"
},
"kashier_user":{
"id":"5f8d0d55b54764421b7156c3",
"fullName":"John Doe",
"email":"john.doe@example.com",
"selectedMID":"MID-XXXXX-XXX"
},
"merchantWebhook":"https://your-website.com/kashier-webhook"
},
"origin":{
"name":"",
"id":"PL-XXXXXXXXXX",
"customerName":"John Doe",
"createdUserId":"5f8d0d55b54764421b7156c3"
},
"reconciliation":{
"webhookUrl":"",
"redirect":false
},
"merchantId":"MID-XXXXX-XXX",
"order":{
"amount":65,
"currency":"EGP",
"systemOrderId":"d2e68974-5a5c-48eb-8f5f-6bd54ccbe830",
"reference":"67c59e14e626cf0012fdb9de"
},
"amount":65,
"totalRefundedAmount":0,
"totalCapturedAmount":65,
"totalAuthorizedAmount":65,
"method":"cash",
"creationDate":"2025-03-03T14:20:27.492Z",
"orderId":"d2e68974-5a5c-48eb-8f5f-6bd54ccbe830",
"provider":"cash",
"merchantOrderId":"67c59e14e626cf0012fdb9de",
"orderReference":"TEST-ORD-193387945",
"paymentType":"paymentLink",
"interactionSource":"Online",
"device":{
"ipAddress":"197.37.122.82"
},
"transactionId":"TX-24358575179",
"transactionResponseCode":"00",
"transactionResponseMessage":{
"en":"Approved",
"ar":"تمت الموافقة"
}
},
"messages":{
"en":"Approved",
"ar":"تمت الموافقة"
},
"status":"SUCCESS",
"showCaptcha":false
}
```
# Payment reason codes (/docs/accept-payments/payment-reason-codes)
Every payment outcome carries a `transactionResponseCode` and a bilingual `transactionResponseMessage` object (`{ en, ar }`). Both appear on webhook payloads, in [order reconciliation](/docs/accept-payments/order-reconciliation) responses, and in transaction responses — a declined authorisation still returns HTTP 200 with the reason in the code.
Branch on both `status` and `transactionResponseCode`. Matching is exact and case-sensitive: compare against the codes exactly as written here, including lowercase `k_*`. Blank Arabic cells mean no Arabic message is published for that code.
## ISO numeric codes [#iso-numeric-codes]
Each numeric code is stored and returned individually. There are no range values — you will receive `47`, never `46-50`.
| transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `0` | Success | |
| `00` | Approved | تمت الموافقة |
| `01` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة |
| `02` | Refer to card issuer's special conditions | يرجى الرجوع إلى جهة إصدار البطاقة |
| `03` | Invalid merchant | تاجر غير صالح |
| `04` | Pick-up lost card detected | إلتقط البطاقة ، تم الإبلاغ عن سرقتها أو ضياعها |
| `05` | Do not honor, please retry or contact your issuer bank. | غير مقبول، يرجى إعادة المحاولة أو الاتصال بالبنك المصدر بالبطاقة |
| `06` | Error | خطأ في الدفع |
| `07` | Pick-up card, special condition | إلتقط البطاقة ، تم الإبلاغ عن سرقتها أو ضياعها |
| `08` | Honour with identification | مقبول مع تحديد الهوية |
| `09` | Request in progress | طلب قيد التقدم |
| `10` | Approved for partial amount | تمت الموافقة على المبلغ الجزئي |
| `11` | Approved (VIP) | موافقة كبار الشخصيات |
| `12` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة |
| `13` | Invalid amount, please contact the merchant | مبلغ غير صحيح ، يرجى الاتصال بالتاجر |
| `14` | Invalid card information. | بيانات البطاقة غير صحيحة. |
| `15` | No such issuer | لا يوجد مثل هذا المصدر |
| `16` | Approved | عملية مقبولة |
| `17` | Customer cancellation | إلغاء العميل |
| `18` | Customer dispute | نزاع العميل |
| `19` | Re-enter transaction | أعد إدخال المعاملة |
| `20` | Invalid response | استجابة غير صالحة |
| `21` | No action taken | لم يتم اتخاذ أي إجراء |
| `22` | Suspected malfunction | عطل مشتبه به |
| `23` | Unacceptable transaction fee | رسوم المعاملات غير مقبولة |
| `24` | File update not supported by receiver | تم رفض الدفع |
| `25` | Unable to locate record on file | تم رفض الدفع |
| `26` | Duplicate file update record, old record replaced | تم رفض الدفع |
| `27` | File update field edit error | تم رفض الدفع |
| `28` | File update file locked out | تم رفض الدفع |
| `29` | File update not successful, contact acquirer | تم رفض الدفع |
| `30` | Format error | خطأ في التنسيق |
| `31` | Bank not supported by switch | البنك غير مدعوم |
| `32` | Completed partially | اكتمل جزئيا |
| `33` | Expired card | بطاقة منتهية الصلاحية |
| `34` | Suspected fraud | يشتبه في الاحتيال |
| `35` | Card acceptor contact acquirer | عملية مرفوضة |
| `36` | Restricted card, please contact your bank. | بطاقة محظورة ، يرجى الاتصال بالبنك المصدر للبطاقة. |
| `37` | Card acceptor call acquirer security | عملية مرفوضة |
| `38` | Allowable PIN tries exceeded | تم تجاوز عدد محاولات PIN المسموح بها |
| `39` | No credit account | لا يوجد حساب ائتمان |
| `40` | Requested function not supported | الوظيفة المطلوبة غير مدعومة |
| `41` | Lost card | البطاقة مفقودة |
| `42` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة |
| `43` | Stolen card, pick-up | بطاقة مسروقة |
| `44` | No investment account | لا يوجد حساب استثماري |
| `45` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. |
| `46` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. |
| `47` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. |
| `48` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. |
| `49` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. |
| `50` | Declined, Contact card issuer. | عملية مرفوضة ، يرجى الاتصال بجهة مصدر البطاقة. |
| `51` | Insufficient funds | رصيد البطاقة غير كاف |
| `52` | No checking account | لا يوجد حساب جاري |
| `53` | No savings account | لا يوجد حساب توفير |
| `54` | Expired card | بطاقة منتهية الصلاحية |
| `55` | Declined - Wrong PIN Entered by Card Holder | مرفوض - إدخال خاطئ لرقم التعريف الشخصي بواسطة حامل البطاقة |
| `56` | No card record | لا يوجد سجل للبطاقة |
| `57` | Online transactions are not permitted to this card, please contact the card issuer. | المعاملات عبر الإنترنت غير مسموح بها لهذه البطاقة ، يرجى الاتصال بالبنك مصدر البطاقة. |
| `58` | Online transactions are not permitted to this card, please contact the card issuer. | المعاملات عبر الإنترنت غير مسموح بها لهذه البطاقة ، يرجى الاتصال بالبنك مصدر البطاقة. |
| `59` | Suspected fraud | تم رفض الدفع بسبب إشتباه في إحتيال |
| `60` | Card acceptor contact acquirer | تم رفض الدفع |
| `61` | Exceeds withdrawal amount limit | تم رفض الدفع بسبب تجاوز حدود المبلغ المسموح للدفع |
| `62` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة |
| `63` | The three-digit CVV or CVC security code … was incorrect. | |
| `64` | Original amount incorrect | المبلغ الأصلي غير صحيح |
| `65` | Exceeds withdrawal frequency limit | تم إجتياز حد تردد السحب |
| `66` | Card acceptor call acquirer's security department | التقط البطاقة من الجهاز |
| `67` | Hard capture (requires that card be picked up at ATM) | الالتقاط الثابت (يتطلب استلام البطاقة من جهاز الصراف الآلي) |
| `68` | Response received too late | رد متأخر |
| `69` | Reserved for ISO use | عملية مرفوضة |
| `70` | Reserved for ISO use | عملية مرفوضة |
| `71` | Reserved for ISO use | عملية مرفوضة |
| `72` | Reserved for ISO use | عملية مرفوضة |
| `73` | Reserved for ISO use | عملية مرفوضة |
| `74` | Reserved for ISO use | عملية مرفوضة |
| `75` | Allowable number of PIN tries exceeded | تم تجاوز العدد المسموح به من محاولات PIN |
| `76` | Reserved for private use | عملية مرفوضة |
| `77` | Reserved for private use | عملية مرفوضة |
| `78` | Declined - No Account | مرفوض - لا يوجد حساب |
| `79` | The card issuer suspects this payment to be fraudulent. | |
| `80` | Reserved for private use | عملية مرفوضة |
| `81` | Reserved for private use | عملية مرفوضة |
| `82` | CVV Validation Error | |
| `83` | CVV Validation Error | |
| `84` | Reserved for private use | عملية مرفوضة |
| `85` | Reserved for private use | عملية مرفوضة |
| `86` | Reserved for private use | عملية مرفوضة |
| `87` | Reserved for private use | عملية مرفوضة |
| `88` | Reserved for private use | عملية مرفوضة |
| `89` | Reserved for private use | عملية مرفوضة |
| `90` | Cutoff is in process (switch ending a day's business and starting the next. Transaction can be sent again in a few minutes) | قطع قيد التنفيذ |
| `91` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة |
| `92` | Financial institution or intermediate network facility cannot be found for routing | لا يمكن العثور على مؤسسة مالية أو مرفق شبكة وسيطة للتوجيه |
| `93` | Transaction cannot be completed. Violation of law | لا يمكن إتمام العملية بسبب انتهاك للقانون |
| `94` | Duplicate transmission | انتقال مكرر |
| `95` | Reconcile error | خطأ في التصالح |
| `96` | Your bank was not able to process the transaction | البنك المصدر للبطاقة غير قادرًا على معالجة المعاملة |
| `97` | Declined - CVV MisMatch | عملية مرفوضة، رمز الأمان غير مطابق. |
| `98` | Declined | عملية مرفوضة |
| `99` | Declined | عملية مرفوضة |
## Authentication and 3DS codes [#authentication-and-3ds-codes]
Returned by the 3D Secure and authentication step.
| transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar |
| ----------------------- | --------------------------------------------------- | ------------------------------------ |
| `AA` | UNSPECIFIED FAILURE | فشل غير محدد |
| `BB` | ACQUIRER SYSTEM ERROR | خطأ في نظام البنك |
| `CC` | UNKNOWN | غير معلوم |
| `U` | Authentication is not available | المصادقة غير متوفرة |
| `X` | Authentication is not available | المصادقة غير متوفرة |
| `P` | Authentication failed, please check your card info. | المصادقة فشلت، خطأ في بيانات الكارت. |
| `N` | Authentication failed | المصادقة فشلت |
| `S` | Invalid Signature on Authentication Response | توقيع غير صالح في استجابة المصادقة |
| `I` | MPI Processing Error | MPI Processing Error |
| `M` | Authentication Attempted (No CAVV) | Authentication Attempted (No CAVV) |
Card enrolment and ACS outcomes can also arrive as a `transactionResponseCode`: `CARD_ENROLLED`, `CARD_NOT_ENROLLED`, `ACS_ERROR`, `ACS_SUCCESS`, `ACS_FAILED`, `AUTHENTICATION_SUCCESSFUL`, `AUTHENTICATION_ATTEMPTED`, `AUTHENTICATION_FAILED`, `CARD_ENROLLEMENT_UNSPECIFIED`, `INVALID_CARD_NUMBER`, `REJECTED_CARD`.
## Gateway codes [#gateway-codes]
When a card is processed through the MPGS gateway, the gateway result word becomes the `transactionResponseCode` verbatim. `APPROVED` and `AUTHENTICATION_IN_PROGRESS` are returned on the intermediate 3DS step of a card order, so expect them in reconciliation responses.
| transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar |
| ------------------------------- | ---------------------------------------------------------------- | ------------------------------- |
| `gw_0` | Declined. | |
| `ABORTED` | Transaction aborted by payer | |
| `ACQUIRER_SYSTEM_ERROR` | Acquirer system error occurred processing the transaction | |
| `APPROVED` | Approved | |
| `APPROVED_AUTO` | The transaction was automatically approved by the gateway … | |
| `APPROVED_PENDING_SETTLEMENT` | Transaction Approved - pending batch settlement | |
| `AUTHENTICATION_IN_PROGRESS` | Authentication in progress | المصادقة قيد التقدم |
| `BALANCE_AVAILABLE` | Balance / points-redemption hints | |
| `BALANCE_UNKNOWN` | Balance / points-redemption hints | |
| `BLOCKED` | Your card is blocked, Kindly contact your issuer bank | |
| `CANCELLED` | Transaction cancelled by payer | |
| `DECLINED` | Payment was declined by issuer or payer authentication … | |
| `DECLINED_AVS` | Address / CSC verification declines | |
| `DECLINED_AVS_CSC` | Address / CSC verification declines | |
| `DECLINED_CSC` | Address / CSC verification declines | |
| `DECLINED_DO_NOT_CONTACT` | Transaction declined - do not contact issuer | |
| `DECLINED_INVALID_PIN` | PIN declines | |
| `DECLINED_PIN_REQUIRED` | PIN declines | |
| `DECLINED_PAYMENT_PLAN` | Transaction declined due to payment plan | |
| `DEFERRED_TRANSACTION_RECEIVED` | Deferred transaction received and awaiting processing | |
| `DUPLICATE_BATCH` | Transaction declined due to duplicate batch | |
| `EXCEEDED_RETRY_LIMIT` | Transaction retry limit exceeded | |
| `EXPIRED_CARD` | Transaction declined due to expired card | |
| `INSUFFICIENT_FUNDS` | Transaction declined due to insufficient funds | |
| `INVALID_CSC` | Invalid card security code | |
| `LOCK_FAILURE` | Order locked - another transaction is in progress for this order | |
| `NO_BALANCE` | No balance available on the card | |
| `NO_MATCH` | CVV Validation Error | |
| `NOT_ENROLLED_3D_SECURE` | Authentication not supported. | |
| `NOT_SUPPORTED` | Transaction type not supported, please refer to your bank. | |
| `PARTIALLY_APPROVED` | Partially Approved transaction | |
| `PENDING` | Transaction is pending | |
| `REFERRED` | Transaction declined - refer to issuer | |
| `SUBMITTED` | Transaction submitted without a known response | |
| `SYSTEM_ERROR` | System error | |
| `TIMED_OUT` | Transaction timeout | |
| `UNKNOWN` | Unkown Result | |
| `UNSPECIFIED_FAILURE` | unspecified failure | |
## Kashier codes [#kashier-codes]
Kashier-internal outcomes covering tokenization, refunds, wallets, QR, and payouts. These keys are lowercase — an uppercase `K_4` will not match.
| transactionResponseCode | transactionResponseMessage - en | transactionResponseMessage - ar |
| ----------------------- | ------------------------------------------------------------------------------------------------------ | ---------------------------------------------- |
| `k_default` | A General Error Occured, please contact support. | حدث خطأ عام ، يرجى الاتصال بالدعم. |
| `k_0` | Invalid detoken parameter. | رمز غير صالح |
| `k_1` | Token not found. | لم يتم العثور على الرمز |
| `k_2` | Token expired after 10 minutes of inactivity. | انتهت صلاحية الرمز بعد 10 دقائق من عدم النشاط. |
| `k_3` | card security code does not exist. | رمز أمان البطاقة غير موجود. |
| `k_4` | Request to pay message send successfully. | |
| `k_5` | The Mobile number is not registered on any provider. | |
| `k_6` | Approved | |
| `k_7` | Order not paid | |
| `k_8` | Transaction refunded successfully | |
| `k_9` | Invalid refund body, please check your inputs | |
| `k_10` | Transaction already refunded | |
| `k_11` | QR code generated successfully | |
| `k_12` | Payment not completed, OTP page did not displayed to the user | |
| `k_13` | Transaction not found on provider | |
| `k_14` | Expired | |
| `k_15` | Self-transfer is not allowed | |
| `k_16` | Internal account credit failed | |
| `k_17` | Recipient credit not found | |
| `k_101` | Transaction In Progress | |
| `REFUND_DISABLED` | The bank has disabled refund operations for this merchant. Please contact your bank to enable refunds. | |
`k_default` is also the fallback message for any code Kashier does not recognise, so it can accompany a code that is not in these tables.
Two further codes reach you without a dictionary entry of their own, and both carry the `k_default` message: `k_timeout`, returned when the gateway sends no response, and `k_risk`, a retry-guard decline. Treat `k_timeout` as an unknown outcome and reconcile it against the order — never read it as a success or a failure.
## Installment and OTP codes [#installment-and-otp-codes]
Returned by the buy-now-pay-later and OTP flows, such as valU and Souhoola.
| transactionResponseCode | Meaning |
| ----------------------- | ---------------------------------------------- |
| `ERR_00` | Technical Failure, Please restart the process. |
| `ERR_CST_01` | Not enrolled |
| `ERR_CST_02` | No limit or inactive |
| `ERR_CST_03` | Amount exceeds limit |
| `ERR_CST_04` | Not eligible |
| `ERR_DTA_01` | Invalid request |
| `ERR_DTA_02` | OTP not found |
| `ERR_DTA_03` | OTP expired |
| `ERR_DTA_04` | Invalid OTP |
| `ERR_DTA_05` | Already verified |
| `ERR_DTA_06` | Max OTP attempts |
| `ERR_DTA_07` | Invalid downpayment |
| `ERR_DTA_08` | Invalid financed amount |
| `ERR_DTA_09` | Wrong payment plan |
| `ERR_ORD_01` | Order not found |
| `ERR_ORD_02` | Duplicate order. |
| `ERR_PRD_01` | Product not found |
| `ERR_PRD_02` | Product Already refunded |
| `ERR_VDR_01` | Vendor not found |
| `ERR_VDR_02` | Store details not found |
| `ERR_VDR_03` | Vendor not allowed to redeem discount |
# Payment sessions (/docs/accept-payments/payment-sessions)
Kashier's payment sessions make transactions more secure and efficient. With a single API call, you can create payments without exposing sensitive data in query strings, and session history tracking lets you monitor every action in real time. This page walks through creating a session, sending the customer to pay, and confirming the result.
## Step 1: Create a payment session [#step-1-create-a-payment-session]
| Endpoint | Value |
| -------- | -------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v3/payment/sessions](https://test-api.kashier.io/v3/payment/sessions) |
| LIVE-URL | [https://api.kashier.io/v3/payment/sessions](https://api.kashier.io/v3/payment/sessions) |
| Method | POST |
```bash
curl --location 'https://test-api.kashier.io/v3/payment/sessions' \
--header 'Authorization: YOUR_TEST_SECRET_KEY' \
--header 'api-key: YOUR_TEST_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"expireAt": "2030-01-28T17:27:32.359Z",
"maxFailureAttempts": 3,
"paymentType": "credit",
"amount": "100.00",
"currency": "EGP",
"order": "8f64saf6sa4",
"merchantRedirect": "https://your-website.com/redirect",
"display": "en",
"type": "one-time",
"allowedMethods": "card,wallet",
"redirectMethod": null,
"iframeBackgroundColor": "#FFFFFF",
"metaData": {
"customKey": "customValue",
"displayNotes": {"key": "value"}
},
"merchantId": "MID-XXXX-XXX",
"failureRedirect": false,
"brandColor": "#FF5733",
"defaultMethod": "card",
"description": "Payment for order ORD123456",
"manualCapture": false,
"customer": {
"email": "john@example.com",
"reference": "894321"
},
"saveCard": "optional",
"retrieveSavedCard": true,
"interactionSource": "ECOMMERCE",
"enable3DS": true,
"serverWebhook": "https://your_webhook_url",
"notes": "Special handling required"
}'
```
`merchantId` is required and the panel cannot guess it. Save your test **merchant ID** next to your keys in the banner above and it is substituted into the `MID-XXXX-XXXX` placeholder automatically. Until you do, the panel treats the placeholder as an unfilled parameter and keeps Send disabled rather than firing a request the API is certain to reject.
Change `order` as well. It is your own reference for the payment and Kashier rejects a duplicate order reference for the same merchant (`ERR_ORD_02`), so leaving the placeholder in place collides with everyone else who left it alone.
### Headers [#headers]
| Key | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |
| api-key | You can obtain your `api-key` from the merchant dashboard under the **Integrations** section. Please note that the API key is different for the *live* and *test* environments, so make sure to use the correct one for each. |
Full parameter and response reference → [Create payment session](/docs/api-reference/payment-sessions/createPaymentSession).
## Step 2: Send the customer to pay [#step-2-send-the-customer-to-pay]
sessionUrl is the URL that will be used to redirect the customer to the payment page. You can use it as the src attribute in a link or `
The response to the create call includes a `sessionUrl`. You have two options for getting the customer to it:
* **Redirect** — send the customer's browser straight to `sessionUrl`. Kashier handles the checkout UI and returns them to your `merchantRedirect` URL afterward.
* **Embed** — render `sessionUrl` inside your own page in an iframe instead of redirecting away:
```html
```
This embed flow was formerly documented as the "Payment UI Builder".
The legacy documentation page hosted an interactive test builder that generated a checkout from the fields below. Use the [create payment session](#step-1-create-a-payment-session) call with these fields to build your checkout.
#### Merchant details [#merchant-details]
* **Merchant ID**
* **API key**
* **Secret key**
#### Order details [#order-details]
* **Order ID** — unique order reference between merchant and Kashier
* **Amount**
* **Currency**
#### Payment method [#payment-method]
Select the payment methods to enable:
* Card
* Wallet
* Bank installment
* Buy now pay later
#### Redirect settings [#redirect-settings]
* **Redirect URL**
* **Redirect method** — Get or Post
* **Redirect failure** — True or False
#### Display settings [#display-settings]
* **Display language** — English or Arabic
* **Display mode** — Test or Live
* **Brand color** — e.g. `#00bcbc`
### Getting the result out of an embedded checkout [#getting-the-result-out-of-an-embedded-checkout]
When you embed rather than redirect, the customer never leaves your page, so there is no redirect landing you can read the outcome from. The checkout talks back to the page that hosts it with `window.postMessage`. Add a `message` listener on your page and switch on `e.data.message`.
What you receive depends on how you embedded it.
**If you embed with Kashier's checkout script**, the script sits between the checkout and your page and re-emits a small, clean set:
| `e.data.message` | Emitted when | What you do |
| ---------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iframeLoaded` | The checkout is loaded and its pay button is ready. | Drop your own loading state. |
| `paymentSuccess` | The payment succeeded. | Show your success state. |
| `iframeHide` | The customer closed or dismissed the checkout. | Remove your overlay and re-enable the page. |
| `urlRedirection` | The checkout is ready to return to your redirect URL. | Nothing. The script redirects the top window for you — it builds an auto-submitting form when `redirectMethod` is `post`, and otherwise replaces the location. |
**If you render `sessionUrl` in your own `