ioka Elements
ioka Elements - это JavaScript SDK для встраивания карточных полей ioka в вашу checkout-страницу. Вы управляете внешним интерфейсом формы: версткой, labels, кнопкой оплаты, checkbox сохранения карты, ошибками и статусами. Карточные данные вводятся внутри защищенных iframe-полей ioka и не попадают в DOM вашего сайта.
Как работает оплата
- Ваш backend создает заказ в ioka.
- Backend возвращает frontend-у
orderIdи, если требуется для сценария с сохраненными картами,customerIdиcustomerAccessToken. - Frontend подключает
ioka.js. - Frontend создает checkout через
IokaCheckout.create(...). - Frontend монтирует поля
pan,holder,expiry,cvcв ваши HTML-контейнеры. - Покупатель вводит данные карты.
- Frontend вызывает
checkout.pay(...). - SDK валидирует поля, создает платеж и открывает 3DS, если он нужен.
- SDK вызывает
onSuccessилиonFail. - Backend подтверждает финальный статус заказа через webhook или API.
Важно
Frontend не должен создавать заказ с секретным API key. Создавайте заказ только на backend-е и возвращайте в браузер только параметры, необходимые для оплаты.
1. Создайте заказ на backend-е
Пример ответа вашего backend-а:
{
"orderId": "ord_123",
"amountLabel": "12 500 ₸"
}
amountLabel нужен только для отображения суммы в интерфейсе. Источником истины должна быть сумма заказа, созданного в ioka.
Если на форме есть checkbox Сохранить карту, backend должен создать или найти Customer и создать Order с customer_id. На frontend по-прежнему достаточно вернуть orderId: SDK отправит save: true, а ioka сохранит карту к Customer, который привязан к Order.
Разные сценарии используют разные параметры:
| Сценарий | Что делает backend | Что передать в IokaCheckout.create(...) |
|---|---|---|
| Разовая оплата новой картой | Создать Order | orderId |
Оплата новой картой с checkbox Сохранить карту | Создать или найти Customer, создать Order с customer_id | orderId |
| Отдельное сохранение карты без оплаты | Создать или найти Customer | mode: "save", customerId, customerAccessToken |
| Оплата сохраненной картой | Создать Order с customer_id и/или card_id | orderId, cardId; customerAccessToken нужен только если frontend сам получает список карт или события Customer |
customerAccessToken не нужен для разовой оплаты и для checkbox saveCard. Он нужен только для отдельных customer-операций на frontend-е: например, standalone-сохранения карты, получения списка сохраненных карт или проверки событий карты.
2. Подключите SDK
Для production:
<script src="https://elements.ioka.kz/ioka.js"></script>
Для тестового магазина:
<script src="https://dev-elements.ioka.kz/ioka.js"></script>
| Окружение | Script | apiBase |
|---|---|---|
| Production | https://elements.ioka.kz/ioka.js | https://api.ioka.kz |
| Тестовый магазин | https://dev-elements.ioka.kz/ioka.js | https://stage-api.ioka.kz |
Если SDK загружен с нужного домена, elementsOrigin передавать не нужно. SDK определит origin из script src.
3. Добавьте контейнеры для полей
<form id="checkout-form">
<label>
Номер карты
<div id="card-pan" class="ioka-field"></div>
<span id="error-pan" class="field-error"></span>
</label>
<label>
Владелец карты
<div id="card-holder" class="ioka-field"></div>
<span id="error-holder" class="field-error"></span>
</label>
<label>
Срок действия
<div id="card-expiry" class="ioka-field"></div>
<span id="error-expiry" class="field-error"></span>
</label>
<label>
CVC
<div id="card-cvc" class="ioka-field"></div>
<span id="error-cvc" class="field-error"></span>
</label>
<label>
<input id="save-card" type="checkbox" />
Сохранить карту на этом сайте
</label>
<button id="pay" type="button">Оплатить</button>
<div id="status" role="status" aria-live="polite"></div>
</form>
4. Создайте checkout и смонтируйте поля
<script>
const payButton = document.getElementById("pay");
const statusNode = document.getElementById("status");
const saveCardInput = document.getElementById("save-card");
function setStatus(message) {
statusNode.textContent = message || "";
}
function setLoading(loading) {
payButton.disabled = loading;
payButton.textContent = loading ? "Обработка..." : "Оплатить";
}
function renderFieldState(state) {
if (!state || !state.field) return;
const field = document.getElementById(`card-${state.field}`);
const error = document.getElementById(`error-${state.field}`);
field?.classList.toggle("is-focused", Boolean(state.focused));
field?.classList.toggle("is-invalid", Boolean(state.error));
if (error) error.textContent = state.error?.message || "";
}
function renderValidation(result) {
Object.values(result.fields || {}).forEach(renderFieldState);
}
async function createCheckoutOnMerchantBackend() {
const response = await fetch("/api/create-ioka-checkout", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ cartId: "cart_123" }),
});
if (!response.ok) {
throw new Error("Не удалось подготовить оплату");
}
return response.json();
}
async function initPaymentForm() {
setLoading(true);
setStatus("Подготовка оплаты...");
const params = await createCheckoutOnMerchantBackend();
const checkout = IokaCheckout.create({
apiBase: "https://api.ioka.kz",
orderId: params.orderId,
locale: "ru",
onFieldChange: renderFieldState,
onValidation: renderValidation,
onProcessing() {
setLoading(true);
setStatus("Обработка платежа...");
},
onThreeDSOpen() {
setStatus("Подтвердите оплату в окне 3DS.");
},
onThreeDSClose() {
setLoading(false);
setStatus("Подтверждение 3DS было закрыто.");
},
onPending() {
setStatus("Проверяем статус платежа...");
},
onSuccess() {
setLoading(false);
setStatus("Оплата прошла успешно.");
},
onFail(error) {
setLoading(false);
setStatus(error?.message || "Не удалось оплатить.");
},
});
const elements = checkout.elements({ height: "24px" });
elements.create("pan").mount("#card-pan");
elements.create("holder").mount("#card-holder");
elements.create("expiry").mount("#card-expiry");
elements.create("cvc").mount("#card-cvc");
payButton.addEventListener("click", async () => {
setLoading(true);
setStatus("");
try {
await checkout.pay({ saveCard: saveCardInput.checked });
} catch (error) {
setLoading(false);
if (error?.validation) renderValidation(error.validation);
setStatus(error?.message || "Проверьте данные карты.");
}
});
window.addEventListener("beforeunload", () => checkout.destroy());
setLoading(false);
setStatus("");
}
initPaymentForm().catch((error) => {
setLoading(false);
setStatus(error?.message || "Не удалось открыть оплату.");
});
</script>
Кастомизация полей
Внешний вид формы контролируется вашим CSS. ioka Elements монтирует iframe внутрь контейнера, а контейнер остается частью вашей страницы.
.ioka-field {
height: 46px;
padding: 11px 12px;
border: 1px solid #cfd6df;
border-radius: 10px;
background: #ffffff;
}
.ioka-field.is-focused {
border-color: #1f6feb;
}
.ioka-field.is-invalid {
border-color: #d92d20;
background: #fff8f7;
}
.field-error {
min-height: 18px;
margin-top: 5px;
color: #d92d20;
font-size: 12px;
}
Текст внутри iframe-полей настраивается через appearance.field:
const checkout = IokaCheckout.create({
apiBase: "https://api.ioka.kz",
orderId: params.orderId,
appearance: {
field: {
fontSize: 18,
fontFamily: "Inter, system-ui, sans-serif",
textColor: "#172033",
placeholderColor: "#7b8794",
bgColor: "transparent",
border: "none",
paddingX: 0,
paddingY: 0,
},
},
});
Доступные параметры appearance.field:
| Параметр | Описание |
|---|---|
fontSize | Размер текста внутри поля |
fontFamily | Шрифт внутри поля |
fontWeight | Насыщенность шрифта |
textColor | Цвет введенного текста |
placeholderColor | Цвет placeholder |
bgColor | Фон iframe-поля |
border | Border внутри iframe-поля |
paddingX | Горизонтальный padding внутри iframe-поля |
paddingY | Вертикальный padding внутри iframe-поля |
Checkbox сохранения карты - обычный HTML input. Для него не нужен отдельный ioka Element, потому что checkbox не собирает карточные данные.
await checkout.pay({ saveCard: saveCardInput.checked });
API
IokaCheckout.create(options)
Создает checkout instance.
| Параметр | Обязателен | Описание |
|---|---|---|
apiBase | Да | Host ioka API. |
orderId | Да | ID заказа, созданного вашим backend-ом. |
customerId | Для отдельных customer-сценариев | ID плательщика. Не нужен для обычной оплаты и checkbox saveCard, если Order уже создан с customer_id. |
customerAccessToken | Для отдельных customer-операций | access_token плательщика. Не нужен для обычной оплаты и checkbox saveCard. |
locale | Нет | Локаль сообщений: ru, kz или en. По умолчанию ru. |
appearance | Нет | Стилизация текста внутри iframe-полей. |
elementsOrigin | Нет | Origin Elements. Обычно определяется автоматически из script src. |
checkout.elements(options?)
Создает контроллер полей.
const elements = checkout.elements({ height: "24px" });
elements.create(type).mount(target)
Монтирует поле в DOM-контейнер.
elements.create("pan").mount("#card-pan");
elements.create("holder").mount("#card-holder");
elements.create("expiry").mount("#card-expiry");
elements.create("cvc").mount("#card-cvc");
Доступные типы:
| Тип | Поле |
|---|---|
pan | Номер карты |
holder | Владелец карты |
expiry | Срок действия |
cvc | CVC |
checkout.pay(options?)
Запускает оплату.
await checkout.pay();
Для оплаты с сохранением новой карты:
await checkout.pay({ saveCard: true });
Перед отправкой платежа SDK сам валидирует поля. Если форма невалидна, promise отклонится с code: "validation_error" и объектом validation.
checkout.validate()
Проверяет смонтированные поля без запуска оплаты.
const result = checkout.validate();
if (!result.valid) {
renderValidation(result);
}
checkout.update(options)
Обновляет параметры checkout, например при создании нового заказа без полной перезагрузки страницы.
checkout.update({
orderId: "ord_new",
customerAccessToken: "customer_access_token_new",
});
checkout.destroy()
Удаляет iframe-поля, 3DS overlay и listeners. Вызывайте destroy() при размонтировании страницы или компонента.
checkout.destroy();
3DS и финальный статус
Если для платежа нужен 3DS, SDK сам откроет fullscreen overlay поверх сайта, покажет банковское подтверждение и продолжит проверять статус заказа. Вам не нужно открывать action_url вручную.
Закрытие 3DS покупателем не является финальным платежным статусом. Финальный результат оплаты подтверждайте на backend-е через webhook или запрос заказа по API.
Безопасность
- Не собирайте PAN, CVC и срок действия карты своими input-ами.
- Не логируйте карточные данные.
- Не передавайте секретный API key в браузер, HTML, JavaScript bundle или localStorage.
- Используйте HTTPS.
- Подтверждайте финальный статус заказа на backend-е.
- Если на сайте настроен CSP, разрешите загрузку SDK и iframe ioka.
Пример CSP для production:
Content-Security-Policy:
script-src 'self' https://elements.ioka.kz;
frame-src 'self' https://elements.ioka.kz https://*.ioka.kz https:;
connect-src 'self' https://api.ioka.kz;
Для 3DS точный frame-src зависит от банка. Если окно 3DS не открывается, проверьте ошибки CSP в browser console.
Тестирование
Для тестового магазина используйте:
<script src="https://dev-elements.ioka.kz/ioka.js"></script>
const checkout = IokaCheckout.create({
apiBase: "https://stage-api.ioka.kz",
orderId: params.orderId,
});
Тестовые карты и пароль для 3DS указаны в разделе Тестирование.