Web SDK Payment
Описание
JavaScript-библиотека для отображения платежной формы на странице мерчанта, у которого нет PCI DSS.
Сценарий работы:
- Мерчант регистрирует заказ через REST API в платежном шлюзе
- Полученный mdOrder мерчант передает на страницу, где используется эта js-библиотека
SDK предоставляет возможность добавлять на свою платежную страницу поля ввода платежных данных через iframe на стороне платежного шлюза.
Плюсы:
- Безопасно: Платежная страница мерчанта и скрипты мерчанта на ней не имеют доступа к платежным полям в iframe. Данные карт не могут быть собраны внешними скриптами.
- Просто: Скрипт легко интегрируется на страницу. Чтобы соответствовать общему стилю сайта, требуется минимальная кастомизация.
- Со стороны мерчанта не требуется PCI DSS.
- Удобно: На сервер могут быть переданы дополнительные поля, такие как
email
,language
,phone
иjsonParams
.
Минусы:
- На данный момент этот SDK не совместим с функцией множественных попыток оплаты.
Библиотека помогает в сборе данных карты, их валидации и проверке, совершении платежа и автоматическом перенаправлении покупателя на указанный в настройках returnUrl
(конечная страница).
Как использовать
Подключить скрипт
Тестовая среда
<script src="https://3dsec.berekebank.kz/payment/modules/multiframe/main.js"></script>
Боевая среда
<script src="https://securepayments.berekebank.kz/payment/modules/multiframe/main.js"></script>
Подготовка
Во-первых, необходимо создать HTML-форму для приема платежей. Форма должна содержать блоки #pan
, #expiry
, #cvc
и кнопку #pay
. Необязательно использовать именно эти имена полей. Вы сможете настроить нужные вам имена во время инициализации.
Вот пример HTML-формы, которая не поддерживает связки:
<div class="card-body">
<div class="col-12">
<label for="pan" class="form-label">Card number</label>
<!-- Container for card number field -->
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Expiry</label>
<!-- Container for expiry card field -->
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<!-- Container for CVC/CVV field -->
<div id="cvc" class="form-control"></div>
</div>
</div>
<!-- Pay button -->
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<!-- Payment loader -->
<span class="spinner-border spinner-border-sm visually-hidden" id="pay-spinner"></span>
<span>Pay</span>
</button>
<!-- Container for errors -->
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>
<div class="card-body">
<div class="col-12" id="select-binding-container" style="display: none">
<!-- Select for bindings -->
<select class="form-select" id="select-binding" aria-label="Default select example">
<option selected value="new_card">Pay with a new card</option>
</select>
</div>
<div class="col-12">
<label for="pan" class="form-label">Card number</label>
<!-- Container for card number field -->
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Expiry</label>
<!-- Container for expiry card field -->
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<!-- Container for cvc/cvv field -->
<div id="cvc" class="form-control"></div>
</div>
<label class="col-12" id="save-card-container">
<!-- Save card checkbox -->
<input class="form-check-input" type="checkbox" value="" id="save-card" />
Save card
</label>
</div>
<!-- Pay button -->
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<!-- Payment loader -->
<span class="spinner-border spinner-border-sm visually-hidden" id="pay-spinner"></span>
<span>Pay</span>
</button>
<!-- Container for errors -->
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>
document.querySelector("#pan")
|
| Необязательно | placeholder | String | Замещающий текст для поля |
| Обязательно | expiry | object | Срок действия карты |
| Обязательно | container | html element | Элемент-контейнер, например, document.querySelector("#expiry")
|
| Необязательно | placeholder | String | Замещающий текст для поля |
| Обязательно | cvc | object | Срок действия карты |
| Обязательно | container | html element | Элемент-контейнер, например, document.querySelector("#cvc")
|
| Необязательно | placeholder | String | Замещающий текст для поля |
| Необязательно | apiContext | String | Контекст (часть URL-адреса после домена) для запросов API. По умолчанию apiContext автоматически берется из ссылки, используемой для подключения скрипта `modules/multiframe/main.js`. | | Необязательно | language | String | Язык, используемый для локализации ошибок и плейсхолдеров.
Значение по умолчанию: `en` | | Необязательно | autofocus | boolean | Автоматическое переключение фокуса при заполнении полей. Значение по умолчанию - `true` | | Необязательно | showPanIcon | boolean | Показывать иконку платежной системы. По умолчанию - `true` | | Необязательно | panIconStyle | CSSStyleDeclaration | Номер | | Необязательно | styles | object | Кастомные стили для иконки платежной системы | | Необязательно | base | object | Базовый стиль. Например: ```base: { color: 'black' }``` | | Необязательно | focus | object | Стиль фокуса | | Необязательно | valid | object | Стиль валидного значения поля | | Необязательно | invalid | object | Стиль невалидного значения поля | | Необязательно | placeholder | object | Стилизация заполняющего текста | | Необязательно | base | object | Базовый стиль | | Необязательно | focus | object | Стиль фокуса | | Необязательно | containerClassName | String | Имя класса выставляемое дополнительно для контейнера.
Значение по умолчанию: ``field-container`` | | Необязательно | onFormValidate | boolean | Callback для обработки изменения валидации формы.
Например: ```js onFormValidate: (isValid) => { alert(isValid ? 'Congratulations!' : 'Oops! We regret.'); }``` | ### Пример инициализации Web SDK
const webSdkPaymentForm = new window.PaymentForm({
// Order number (order registration happens before initialization of the form)
mdOrder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
// Name of the class that will be set for containers with frames
containerClassName: "field-container",
onFormValidate: (isValid) => {
// Handling form validation
},
// Context for API queries
apiContext: "/payment",
// Language - is used for localization of errors and placeholder names.
// The language must be supported in Merchant settings
language: "en",
// Automatically shift focus as fields are filled out
autoFocus: true,
// Show payment system icon
showPanIcon: true,
// Custom styles for payment system icon
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: document.querySelector("#pan"),
onFocus: (containerElement) => {
// Action when field gets focus
// (containerElement contains link to field container element)
},
onBlur: (containerElement) => {
// Action when field gets focus off it
// (containerElement contains link to field container element)
},
onValidate: (isValid, containerElement) => {
// Action when field is valid
// (isValid is true if field is valid, otherwise is false)
// (containerElement contains link to field container element)
},
},
expiry: {
container: document.querySelector("#expiry"),
// ...
},
cvc: {
container: document.querySelector("#cvc"),
// ...
},
},
// Style for input fields
styles: {
// Base state
base: {
color: "black",
padding: '0px 16px',
fontSize: '18px',
fontFamily: 'monospace',
},
// Focused state
focus: {
color: "blue",
},
// Disabled state
disabled: {
color: "gray",
},
// Has valid value
valid: {
color: "green",
},
// Has invalid value
invalid: {
color: "red",
},
// Style for placeholder
placeholder: {
// Base style
base: {
color: "gray",
},
// Style when focused
focus: {
color: "transparent",
},
},
},
});
document.querySelector("#destroy").addEventListener("click", function () {
webSdkPaymentForm.destroy();
});
webSdkFormWithoutBindings
.init()
.then((success) => {
console.log('success', success)
// Скрипт успешно инициализирован. Promise возвращает объект, содержащий некоторую полезную инфромацию о зарегистрированном в платежном шлюзе заказе. После этого можно убрать лоадер или выполнить другие действия:
document
.querySelector(".payment-form-loader")
.classList.remove("payment-form-loader--active");
})
.catch((error) => {
// При инициализации скрипта возникли ошибки. Дальнейшее выполнение невозможно.
// Promise возвращает сообщение об ошибке, которое мы можем отобразить на странице:
const errorEl = document.querySelector('#error_1');
errorEl.innerHTML = e.message;
errorEl.classList.remove('visually-hidden');
})
.finally(() => {
// Действия выполняемые после инициализации, независимо от её успешного или неуспешного выполнения.
});
webSdkFormWithoutBindings
.doPayment({
// Дополнительные параметры
email: "foo@bar.com",
phone: "4420123456789",
cardholderName: "JOHN DOE",
jsonParams: { foo: "bar" },
})
.then((result) => {
console.log("result", result);
})
.catch((e) => {
// Обработка ошибок. Для примера покажем блок с ошибкой
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Выполняется в любом случае. Например, сделать кнопку "Оплатить" снова активной.
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});
Для рабочих целей - регистрируйте заказ через API.
Эта форма используется только для демонстрационных целей - используйте значение Order ID =
xxxxx-xxxxx-xxxxx-xxxxx
Весь демонстрационный код
<div class="container_demo">
<div class="about">
<form name="formRunTest">
<label for="mdOrder"> Order ID (mdOrder) <br>
<span class="label__desc">(Должен поступать из бэкенда. Данный ввод предназначен только для демонстрации)</span>
</label>
<div class="run-test">
<input id="mdOrder" type="text" placeholder="Paste the mdOrder registered for sandbox"/>
<button class="btn-mini" id="load" type="submit">Load</button>
</div>
</form>
</div>
<div class="payment-form">
<div class="payment-form-loader payment-form-loader--active">
Web SDK Payment требует наличия mdOrder (предварительно зарегистрированного в шлюзе заказа).<br>
Зарегистрировать заказ можно через Merchant Portal или через API. <br><br>
Или попробуйте использовать <code>xxxxx-xxxxx-xxxxx-xxxxx</code> если вы хотите получить только платежную форму.
</div>
<div class="card-body">
<div class="col-12">
<label for="pan" class="form-label">Номер карты</label>
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Срок действия</label>
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<div id="cvc" class="form-control"></div>
</div>
<!-- Дополнительные поля для Visa Mandatory -->
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Cardholder</label>
<div id="cardholder" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="Surname"value="JOHN DOE">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Mobile phone</label>
<div id="mobile" class="additional-field-container">
<input type="tel" class="additional-field-input" placeholder="+4915112345" value="+4915112345678">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Email address</label>
<div id="email" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="address@mail" value="address@mail.com">
</div>
</div>
</div>
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<span
class="spinner-border spinner-border-sm me-2 visually-hidden"
role="status"
aria-hidden="true"
id="pay-spinner">
</span>
<span>Оплатить</span>
</button>
<!-- Эти кнопки нужны только для демонстрации работы метода destroy -->
<button class="btn btn-secondary" type="button" id="destroyFormWithoutCredentials">
<span>Уничтожить</span>
</button>
<button class="btn btn-secondary" type="button" id="reinitFormWithoutCredentials" style="display: none;">
<span>Инициализировать</span>
</button>
<div class="error my-2 text-center text-danger visually-hidden" id="error"></div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Функция для инициализации платежной формы с возможностью повторного использования после уничтожения
function initPaymentForm() {
const mrOrderInput = document.getElementById("mdOrder");
mrOrderInput.classList.remove("invalid");
if (!/(\w+-){3,10}\w+/g.test(mrOrderInput.value)) {
mrOrderInput.classList.add("invalid");
return;
}
// Инициализация Web SDK. Необходим идентификатор заказа mdOrder.
initPayment(mrOrderInput.value);
}
document.formRunTest.addEventListener("submit", function (e) {
e.preventDefault();
// Инициализация платежной формы
initPaymentForm();
});
let webSdkFormWithoutBindings;
// Массив объектов для дополнительных полей, которые содержат id поля, его шаблон валидации и допустимые символы для ввода
const mandatoryFieldsWithoutBinding = [
{
id: '#cardholder',
template: /^[a-zA-Z '`.\-]{4,24}$/,
replace: /[^a-zA-Z ' \-`.]/g,
},
{
id: '#mobile',
template: /^\+?[1-9][0-9]{7,14}$/,
replace: /[^0-9\+]/g,
},
{
id: '#email',
template: /^[a-zA-Z0-9._-]{1,64}@([a-zA-Z0-9.-]{2,255})\.[a-zA-Z]{2,255}$/,
replace: /[^a-zA-Z0-9@._-]/g,
}
]
function initPayment(mdOrder) {
webSdkFormWithoutBindings = new window.PaymentForm({
mdOrder: mdOrder,
onFormValidate: () => {},
language: "en", // Язык (английский)
containerClassName: "field-container",
autoFocus: true,
showPanIcon: true,
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: document.querySelector("#pan"),
},
expiry: {
container: document.querySelector("#expiry"),
},
cvc: {
container: document.querySelector("#cvc"),
},
},
styles: {
base: {
padding: "0px 16px",
color: "black",
fontSize: "18px",
fontFamily: 'monospace',
},
invalid: {
color: "red",
},
placeholder: {
base: {
color: "gray",
},
focus: {
color: "transparent",
},
},
},
});
// Действие после инициализации
webSdkFormWithoutBindings.init().then(() => {
document
.querySelector(".payment-form-loader")
.classList.remove("payment-form-loader--active");
})
.catch((e) => {
// Отображение ошибки при инициализации webSDK
const errorEl = document.querySelector('#error_1');
errorEl.innerHTML = e.message;
errorEl.classList.remove('visually-hidden');
}
.finally(() => {
// Валидация и автозамена недопустимых символов
mandatoryFieldsWithBinding.forEach(item => {
const field = document.querySelector(item.id)
field.closest(".additional-field").style.display = '';
field.addEventListener('input', () => {
let inputValue = field.querySelector('input')
inputValue.value = item.replace ? inputValue.value.replace(item.replace,'') : inputValue.value;
if (item.id.includes("#cardholder")) {
inputValue.value = inputValue.value.toUpperCase()
}
if (item.template) {
// CSS класс ".additional-field-invalid" для отображения невалидных полей
if (item.template.test(inputValue.value)) {
field.classList.remove("additional-field-invalid")
} else {
field.classList.add("additional-field-invalid")
}
}
})
})
})
}
// Обработчик "Оплатить"
document.querySelector("#pay").addEventListener("click", () => {
const payButton = document.querySelector("#pay");
// Делаем кнопку "Оплатить" неактивной, чтобы избежать двойных платежей
payButton.disabled = true;
// Показываем загрузчик пользователю
const spinnerEl = document.querySelector("#pay-spinner");
spinnerEl.classList.remove("visually-hidden");
// Скрываем контейнер с ошибкой
const errorEl = document.querySelector("#error");
errorEl.classList.add("visually-hidden");
// Валидация дополнительных полей Visa Mandatory
if (document.querySelectorAll('.additional-field-invalid').length) {
errorEl.innerHTML = "Form is not valid";
errorEl.classList.remove('visually-hidden');
spinnerEl.classList.add('visually-hidden');
return
}
// Начинаем процесс оплаты
webSdkFormWithoutBindings
.doPayment({
// Дополнительные параметры
email: document.querySelector('#email input').value,
phone: document.querySelector('#mobile input').value,
cardholderName: document.querySelector('#cardholder input').value,
jsonParams: { size: "L" },
})
.then((result) => {
console.log("result", result);
})
.catch((e) => {
// Выполняется при ошибке
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Выполняется в любом случае, например, делаем кнопку "Оплатить" снова активной.
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});
});
// Обработчик "Уничтожить"
document
.querySelector("#destroyFormWithoutCredentials")
.addEventListener("click", function () {
// Убираем кнопку "Уничтожить" и отображаем кнопку "Инициализировать" для демонстрации
this.style.display = "none";
document.querySelector("#reinitFormWithoutCredentials").style.display =
"";
// Уничтожаем платежную форму Web SDK
webSdkFormWithoutBindings.destroy();
});
// Обработчик "Инициализировать форму"
document
.querySelector("#reinitFormWithoutCredentials")
.addEventListener("click", function () {
// Убираем кнопку "Инициализировать" и отображаем кнопку "Уничтожить" для демонстрации
this.style.display = "none";
document.querySelector("#destroyFormWithoutCredentials").style.display =
"";
// Инициализация платежной формы
initPaymentForm();
});
});
</script>
// Инициализация
webSdkFormWithBindings
.init()
.then(({ orderSession }) => {
// Объект `orderSession` содержит всю информацию о заказе, включая информацию о сохраненных учетных данных (о связке).
console.info("orderSession", orderSession);
// Показать элемент выбора сохраненной карты
document.querySelector("#select-binding-container").style.display =
orderSession.bindings.length ? "" : "none";
// Заполнить выбор сохраненными учетными данными
orderSession.bindings.forEach((binding) => {
document
.querySelector("#select-binding")
.options.add(new Option(binding.pan, binding.id));
});
// Обработка выбора сохраненных учетных данных или новой карты
document
.querySelector("#select-binding")
.addEventListener("change", function () {
const bindingId = this.value;
if (bindingId !== "new_card") {
webSdkFormWithBindings.selectBinding(bindingId);
// Скрыть флажок "Сохранить карту"
document.querySelector("#save-card-container").style.display = "none";
} else {
// Выбор связки с null означает переход к новой карте
webSdkFormWithBindings.selectBinding(null);
// Показать флажок "Сохранить карту"
document.querySelector("#save-card-container").style.display = "";
}
});
// Когда форма готова, можем скрыть загрузчик
document.querySelector("#pay-form-loader").classList.add("visually-hidden");
})
.catch((error) => {
// Произошли ошибки при инициализации скрипта. Дальнейшее выполнение невозможно.
// Promise возвращает сообщение об ошибке, которое мы можем отобразить на странице:
const errorEl = document.querySelector('#error_1');
errorEl.innerHTML = e.message;
errorEl.classList.remove('visually-hidden');
});
document.querySelector('#save-card').checked
|
| Необязательно | email | String | Email клиента |
| Необязательно | cardholderName | String | Имя держателя карты |
| Необязательно | jsonParams | Object | Дополнительные поля. Например, вы можете отправить дополнительную информацию о заказе или любую другую полезную для вас информацию. Например, `jsonParams: { "t-shirt-color": "черный", "size": "M" }` |
### Применение сохраненных карт
Чтобы оплатить с помощью сохраненных учетных данных, вам необходимо передать выбранный `bindingId` в форму перед вызовом `doPayment`: `webSdkFormWithBindings.selectBinding('bindingId');` Если вы передумали и хотите заплатить новой картой, не забудьте удалить `bindingId` из формы:
`webSdkFormWithBindings.selectBinding(null);` ### Пример вызова:
webSdkFormWithBindings
.doPayment({
// Дополнительные параметры
email: "foo@bar.com",
phone: "4420123456789",
saveCard: document.querySelector("#save-card").checked,
cardholderName: "JOHN DOE",
jsonParams: { foo: "bar" },
})
.then((result) => {
console.log("result", result);
})
.catch((e) => {
// Обработка ошибок. Для примера покажем блок с ошибкой
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Выполнить в любом случае. Например, сделать кнопку "Оплатить" снова активной.
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});
Для рабочих целей - регистрируйте заказ через API.
Эта форма используется только для демонстрационных целей - используйте значение Order ID =
xxxxx-xxxxx-xxxxx-xxxxx
Весь демонстрационный код
<div class="container_demo">
<div class="about">
<form name="formRunTest">
<label for="mdOrder"> Идентификатор заказа (mdOrder) <br/>
<span class="label__desc">(Приводится только для демонстрационных целей. Идентификатор заказа должен приходить с бэкенда.)</span>
</label>
<div class="run-test">
<input id="mdOrder" type="text" placeholder="Вставьте mdOrder, зарегистрированный в Sandbox"/>
<button class="btn-mini" id="load" type="submit">Загрузить</button>
</div>
</form>
</div>
<div class="payment-form">
<div class="payment-form-loader payment-form-loader--active">
Для Web SDK Payment требуется mdOrder (предварительно зарегистрированный заказ в шлюзе).<br/>
Вы можете зарегистрировать заказ через личный кабинет или через API. <br/><br/>
Или попробуйте использовать <code>ххххх-ххххх-ххххх-ххххх</code> если вы хотите проверить только форму оплаты.
</div>
<div id="pay-form-loader" class="spinner-container visually-hidden">
<div class="spinner-border" role="status"></div>
</div>
<div class="card-body">
<div class="col-12" id="select-binding-container" style="display: none">
<select class="form-select" id="select-binding" aria-label="Default select example">
<option selected value="new_card">Оплата новой картой</option>
</select>
</div>
<div class="col-12">
<label for="pan" class="form-label">Номер карты</label>
<div id="pan" class="form-control"></div>
</div>
<div class="col-6 col-expiry">
<label for="expiry" class="form-label">Срок действия</label>
<div id="expiry" class="form-control"></div>
</div>
<div class="col-6 col-cvc">
<label for="cvc" class="form-label">CVC / CVV</label>
<div id="cvc" class="form-control"></div>
</div>
<label class="col-12" id="save-card-container">
<input class="form-check-input" type="checkbox" value="" id="save-card" />
Сохранить карту
</label>
<!--Дополнительные поля Visa Mandatory -->
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Владелец карты</label>
<div id="cardholder" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="NAME SURNAME"value="JOHN DOE">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Телефон</label>
<div id="mobile" class="additional-field-container">
<input type="tel" class="additional-field-input" placeholder="+4915112345" value="+4915112345678">
</div>
</div>
<div class="col-12 additional-field" style="display:none;">
<label for="" class="form-label">Электронная почта</label>
<div id="email" class="additional-field-container">
<input type="text" class="additional-field-input" placeholder="address@mail" value="address@mail.com">
</div>
</div>
</div>
<button class="btn btn-primary btn-lg" type="submit" id="pay">
<span
class="spinner-border spinner-border-sm me-2 visually-hidden"
role="status"
aria-hidden="true"
id="pay-spinner">
</span>
<span>Оплатить</span>
</button>
<!-- Эти кнопки нужны только для демонстрации работы метода destroy -->
<button class="btn btn-secondary" type="button" id="destroyFormWithoutCredentials">
<span>Уничтожить</span>
</button>
<button class="btn btn-secondary" type="button" id="reinitFormWithoutCredentials" style="display: none;">
<span>Инициализировать</span>
</button>
<div class="error my-2 visually-hidden" id="error"></div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", () => {
// Функция для инициализации платежной формы для ее повторного использования после уничтожения
function initPaymentForm() {
const mrOrderInput = document.getElementById("mdOrder");
mrOrderInput.classList.remove("invalid");
if (!/(\w+-){3,10}\w+/g.test(mrOrderInput.value)) {
mrOrderInput.classList.add("invalid");
return;
}
// Удаление плейсхолдера
document
.querySelector(".payment-form-loader")
.classList.remove("payment-form-loader--active");
// Добавляем загрузчик платежных форм
document
.querySelector("#pay-form-loader")
.classList.remove("visually-hidden");
// Инициализация Web SDK. Требуется обязательный mdOrder (идентификатор заказа).
initPayment(mrOrderInput.value);
}
// Инициализация обработчика для тестовых данных
function handleSubmit(e) {
e.preventDefault();
// Инициализировать платежную форму
initPaymentForm();
}
// Регистрирация события для примера ввода
document.formRunTest.addEventListener("submit", handleSubmit);
let webSdkFormWithBindings;
// Массив объектов для дополнительных полей, которые содержат id поля, его шаблон валидации и допустимые символы для ввода
const mandatoryFieldsWithoutBinding = [
{
id: '#cardholder',
template: /^[a-zA-Z '`.\-]{4,24}$/,
replace: /[^a-zA-Z ' \-`.]/g,
},
{
id: '#mobile',
template: /^\+?[1-9][0-9]{7,14}$/,
replace: /[^0-9\+]/g,
},
{
id: '#email',
template: /^[a-zA-Z0-9._-]{1,64}@([a-zA-Z0-9.-]{2,255})\.[a-zA-Z]{2,255}$/,
replace: /[^a-zA-Z0-9@._-]/g,
}
]
function initPayment(mdOrder) {
webSdkFormWithBindings = new window.PaymentForm({
// Номер заказа (регистрация заказа происходит до инициализации формы)
mdOrder: mdOrder,
// Обработка валидации формы
onFormValidate: (isValid) => {
// Например, вы можете отключить кнопки «Оплатить» и «Получить токен», если форма не валидна, например:
// const payButton = document.querySelector('#pay');
// payButton.disabled = !isValid;
},
// Контекст для API вызовов
apiContext: "/payment",
// Язык используется для локализации ошибок и названий для плейсхолдеров.
// Язык должен поддерживаться в настройках мерчанта
language: "en",
// Имя класса для элементов контейнера, содержащих iframe
containerClassName: "field-container",
// Автоматическое переключение фокуса при заполнении полей
autoFocus: true,
// Показать иконку платежной системы
showPanIcon: true,
// Дополнительные стили для иконки платежной системы
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
// Настройки поля
fields: {
// Элемент-контейнер, в который будет помещен iframe с полем
pan: {
container: document.querySelector("#pan"),
},
// Срок действия карты
expiry: {
container: document.querySelector("#expiry"),
},
// CVC/CVV-код
cvc: {
container: document.querySelector("#cvc"),
},
},
// Дополнительные стили для настройки внешнего вида полей ввода в iframes
styles: {
base: {
padding: "0px 16px",
color: "black",
fontSize: "18px",
fontFamily: 'monospace',
},
disabled: {
backgroundColor: "#e9ecef",
},
invalid: {
color: "red",
},
placeholder: {
base: {
color: "gray",
},
focus: {
color: "transparent",
},
},
},
});
// Действие после инициализации
webSdkFormWithBindings
.init()
.then(({ orderSession }) => {
// Объект `orderSession` содержит всю информацию о заказе, включая информацию о сохраненных учетных данных (о связке).
console.info("orderSession", orderSession);
// Показать выбранную связку
document.querySelector("#select-binding-container").style.display =
orderSession.bindings.length ? "" : "none";
// Заполнить выбор сохраненными учетными данными
orderSession.bindings.forEach((binding) => {
document
.querySelector("#select-binding")
.options.add(new Option(binding.pan, binding.id));
});
// Обработка выбора сохраненных учетных данных или новой карты
document
.querySelector("#select-binding")
.addEventListener("change", function () {
const bindingId = this.value;
if (bindingId !== "new_card") {
// Задать идентификатор связки
webSdkFormWithBindings.selectBinding(bindingId);
// Hide the 'Save card' checkbox
document.querySelector("#save-card-container").style.display =
"none";
} else {
// Выбор связки с null означает переход к новой карте
webSdkFormWithBindings.selectBinding(null);
// Показать флажок "Сохранить карту"
document.querySelector("#save-card-container").style.display = "";
}
});
// Когда форма готова, можем скрыть загрузчик
document
.querySelector("#pay-form-loader")
.classList.add("visually-hidden");
// Удалить событие для примера ввода
document.formRunTest.removeEventListener("submit", handleSubmit);
})
.catch((error) => {
// Выполнить при ошибке
const errorEl = document.querySelector("#error");
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Валидация и автозамена недопустимых символов
mandatoryFieldsWithBinding.forEach(item => {
const field = document.querySelector(item.id)
field.closest(".additional-field").style.display = '';
field.addEventListener('input', () => {
let inputValue = field.querySelector('input')
inputValue.value = item.replace ? inputValue.value.replace(item.replace,'') : inputValue.value;
if (item.id.includes("#cardholder")) {
inputValue.value = inputValue.value.toUpperCase()
}
if (item.template) {
// CSS класс ".additional-field-invalid" для отображения невалидных полей
if (item.template.test(inputValue.value)) {
field.classList.remove("additional-field-invalid")
} else {
field.classList.add("additional-field-invalid")
}
}
})
})
});
}
// Обработчик платежа
document.querySelector("#pay").addEventListener("click", () => {
// Делаем кнопку "Оплатить" неактивной, чтобы избежать двойных платежей
const payButton = document.querySelector("#pay");
payButton.disabled = true;
// Показать загрузчик для пользователя
const spinnerEl = document.querySelector("#pay-spinner");
spinnerEl.classList.remove("visually-hidden");
// Скрыть контейнер ошибок
const errorEl = document.querySelector("#error");
errorEl.classList.add("visually-hidden");
// Валидация дополнительных полей Visa Mandatory
if (document.querySelectorAll('.additional-field-invalid').length) {
errorEl.innerHTML = "Form is not valid";
errorEl.classList.remove('visually-hidden');
spinnerEl.classList.add('visually-hidden');
return
}
// Начать оплату
webSdkFormWithBindings
.doPayment({
// Дополнительные параметры
email: document.querySelector('#email input').value,
phone: document.querySelector('#mobile input').value,
cardholderName: document.querySelector('#cardholder input').value,
saveCard: document.querySelector("#save-card").checked,
jsonParams: { foo: "bar" },
})
.then((result) => {
// Здесь можно что-то сделать с результатом платежа
console.log("result", result);
})
.catch((e) => {
// Выполнить при ошибке
errorEl.innerHTML = e.message;
errorEl.classList.remove("visually-hidden");
})
.finally(() => {
// Выполнить в любом случае. Например, снова сделать активной кнопку «Оплатить».
payButton.disabled = false;
spinnerEl.classList.add("visually-hidden");
});
});
// обрабочик Destroy
document
.querySelector("#destroyFormWithCredentials")
.addEventListener("click", function () {
// Удалить кнопку Destroy и отобразить кнопку повторной инициализации для демонстрации
this.style.display = "none";
document.querySelector("#reinitFormWithCredentials").style.display = "";
// Выполнить метод destroy в веб-форме SDK
webSdkFormWithBindings.destroy();
});
// Инициировать обработчик платежной формы
document
.querySelector("#reinitFormWithCredentials")
.addEventListener("click", function () {
// Удалить кнопку Reinit и отобразить кнопку Destroy для демонстрации
this.style.display = "none";
document.querySelector("#destroyFormWithCredentials").style.display = "";
// Инициализация платежной формы
initPaymentForm();
});
});
</script>
Обработка данных, возвращаемых методами init( ) и doPayment( )
Метод init( )
Метод возвращает Promise, который при успешном выполнении возвращает объект с информацией о зарегистрированном заказе.
Метод возвращает следующие параметры:
Обязательность | Название | Тип | Описание |
---|---|---|---|
Необязательно | mdOrder | String | Номер заказа, переданный при инициализации |
Необязательно | orderSession | object | Объект с информацией о зарегистрированном заказе |
Необязательно | orderSession.amount | String | Сумма платежа в миноре (умноженное на 100 значение) |
Необязательно | orderSession.currencyAlphaCode | String | Код валюты буквами, например "CHN" |
Необязательно | orderSession.currencyNumericCode | String | Код валюты цифрами, например "843" |
Необязательно | orderSession.sessionTimeOverAt | number | Время деактивации заказа в миллисекундах, прошедших с 1 января 1970 года UTC |
Необязательно | orderSession.orderNumber | String | Номер заказа |
Необязательно | orderSession.description | String | Описание заказа |
Необязательно | orderSession.cvcNotRequired | boolean | Обязательность заполнения поля CVC |
Необязательно | orderSession.bindingEnabled | boolean | Отображение чекбокса "Сохранить карту" |
Необязательно | orderSession.bindingDeactivationEnabled | boolean | Возможность для клиента удалять связки |
Необязательно | orderSession.merchantOptions | String | Настройки мерчанта по допустимым способам оплаты, платежным системам и прочее |
Необязательно | orderSession.customerDetails | object | Объект с информацией о покупателе |
Необязательно | orderSession.merchantInfo | object | Объект с информацией о мерчанте |
Необязательно | orderSession.merchantInfo.merchantUrl | String | URL магазина |
Необязательно | orderSession.merchantInfo.merchantFullName | String | Полное наименование мерчанта |
Необязательно | orderSession.merchantInfo.merchantLogin | String | Логин мерчанта |
Необязательно | orderSession.merchantInfo.custom | boolean | Дополнительные поля |
Необязательно | orderSession.bindings | Array | Массив с информацией о привязанных картах |
Необязательно | orderSession.bindings.cardholderName | String | Имя держателя карты |
Необязательно | orderSession.bindings.createdAt | number | Время создания в миллисекундах, прошедших с 1 января 1970 года UTC |
Необязательно | orderSession.bindings.id | String | Уникальный идентификатор |
Необязательно | orderSession.bindings.pan | String | Маскированный номер карты |
Необязательно | orderSession.bindings.expiry | String | Срок действия карты |
Необязательно | orderSession.bindings.cardInfo | object | Дополнительная инофрмация о карте |
Возможно наличие дополнительных полей, либо отсутствие некоторых полей из списка выше.
Пример
{
"mdOrder": "5541f44c-d7ec-7a6c-997d-1d4d0007bc7d",
"orderSession": {
"amount": "100000",
"currencyAlphaCode": "BYN",
"currencyNumericCode": "933",
"sessionTimeOverAt": 1740385187287,
"orderNumber": "27000",
"description": "",
"cvcNotRequired": false,
"bindingEnabled": false,
"bindingDeactivationEnabled": false,
"merchantOptions": [
"MASTERCARD_TDS",
"MASTERCARD",
"VISA",
"VISA_TDS",
"CARD"
],
"customerDetails": {},
"merchantInfo": {
"merchantUrl": "http://google.com",
"merchantFullName": "Coffee to Go",
"merchantLogin": "CoffeToGo",
"captchaMode": "NONE",
"loadedResources": {
"logo": true,
"footer": false
},
"custom": false
},
"bindings": [
{
"cardholderName": "CARDHOLDER NAME",
"createdAt": 1712321609666,
"id": "83ffea5d-061f-7eca-912a-02ff0007bc7d",
"pan": "4111 11** **** 1111",
"expiry": "12/24",
"cardInfo": {
"name": "TEST BANK-A",
"nameEn": "TEST BANK-A",
"backgroundColor": "#fbf0ff",
"backgroundGradient": [
"#fafafa",
"#f3f0ff"
],
"supportedInvertTheme": false,
"backgroundLightness": true,
"country": "hu",
"defaultLanguage": "en",
"textColor": "#040e5d",
"url": null,
"logo": "logo/main/293c39ad-0bcb-4cbb-803e-65c435877b5a/1.svg",
"logoInvert": "logo/invert/293c39ad-0bcb-4cbb-803e-65c435877b5a/1.svg",
"logoMini": "logo/mini/293c39ad-0bcb-4cbb-803e-65c435877b5a/1.svg",
"design": null,
"paymentSystem": "visa",
"cobrand": null,
"productCategory": null,
"productCode": null,
"mnemonic": "TEST BANK-A",
"params": null
}
}
]
}
}
Неуспешное выполнение
При неуспешном выполнении Promise возвращает сообщение об ошибке, которое мы можем отобразить на странице. Пример сообщения: Error: Форма недействительна
.
Примеры кода обработки данных, возвращаемых методом инициализации init()
приведены в разделе Шаг 1. Выполнить метод инициализации для оплаты без сохраненной карты и с сохраненной картой.
Метод doPayment( )
Метод возвращает Promise, который при успешном выполнении возвращает объект с информацией о проведенном платеже.
Метод возвращает следующие параметры:
Обязательность | Название | Тип | Описание |
---|---|---|---|
Необязательно | redirectUrl | String | Адрес редиректа после совершения оплаты |
Необязательно | finishedPaymentInfo | object | Объект с информацией о проведенном платеже |
Необязательно | finishedPaymentInfo.paymentSystem | String | Платежная система |
Необязательно | finishedPaymentInfo.merchantShortName | String | Короткое наименование мерчанта |
Необязательно | finishedPaymentInfo.merchantLogin | String | Логин мерчанта |
Необязательно | finishedPaymentInfo.merchantFullName | String | Полное наименование мерчанта |
Необязательно | finishedPaymentInfo.approvalCode | String | Код одобрения |
Необязательно | finishedPaymentInfo.orderNumber | String | Номер заказа |
Необязательно | finishedPaymentInfo.formattedTotalAmount | String | Форматированная итоговая сумма платежа |
Необязательно | finishedPaymentInfo.backUrl | String | Адрес возврата |
Необязательно | finishedPaymentInfo.failUrl | String | Адрес возврата при неуспешном платеже |
Необязательно | finishedPaymentInfo.terminalId | String | ID терминала |
Необязательно | finishedPaymentInfo.orderDescription | String | Описание заказа |
Необязательно | finishedPaymentInfo.displayErrorMessage | String | Сообщение об ошибке |
Необязательно | finishedPaymentInfo.loadedResources | object | Подгруженные ресурсы: лого, футер и др. |
Необязательно | finishedPaymentInfo.currencyAlphaCode | String | Код валюты буквами, например "CHN" |
Необязательно | finishedPaymentInfo.orderFeatures | Array | Дополнительные параметры заказа, такие как ACS_IN_IFRAME, BINDING_NOT_NEEDED, FORCE_CREATE_BINDING, VERIFY и др. |
Необязательно | finishedPaymentInfo.isWebView | boolean | Отображение в WebView |
Необязательно | finishedPaymentInfo.actionCodeDetailedDescription | String | Описание проведенного действия |
Необязательно | finishedPaymentInfo.transDate | String | Дата и время платежа |
Необязательно | finishedPaymentInfo.currency | String | Код валюты |
Необязательно | finishedPaymentInfo.actionCode | number | Код события |
Необязательно | finishedPaymentInfo.expiry | String | Срок действия карты |
Необязательно | finishedPaymentInfo.formattedAmount | String | Форматированная итоговая сумма платежа |
Необязательно | finishedPaymentInfo.actionCodeDescription | String | Описание проведенного действия |
Необязательно | finishedPaymentInfo.formattedFeeAmount | String | Форматированная сумма комиссии |
Необязательно | finishedPaymentInfo.email | String | Электронная почта |
Необязательно | finishedPaymentInfo.amount | String | Сумма |
Необязательно | finishedPaymentInfo.merchantCode | String | ID мерчанта |
Необязательно | finishedPaymentInfo.ip | String | IP адресс |
Необязательно | finishedPaymentInfo.panMasked | String | Маскированный номер карты |
Необязательно | finishedPaymentInfo.successUrl | String | Адрес возврата при успешном платеже |
Необязательно | finishedPaymentInfo.paymentWay | String | Способ оплаты |
Необязательно | finishedPaymentInfo.processingErrorType | object | Тип ошибки процессинга. Передаётся, когда ошибка происходит на стороне процессинга, а не в платёжном шлюзе, при том что попытки проведения платежа ещё не исчерпаны и переадресации на финальную страницу не происходит. |
Необязательно | finishedPaymentInfo.processingErrorType.value | String | Значение |
Необязательно | finishedPaymentInfo.processingErrorType.messageCode | String | Сообщение об ошибке |
Необязательно | finishedPaymentInfo.processingErrorType.apiErrorCodeMessage | String | Сообщение об ошибке api |
Необязательно | finishedPaymentInfo.panMasked4digits | String | Маскированный номер карты (открыты последние 4 цифры) |
Необязательно | finishedPaymentInfo.amountsInfo | object | Объект с общей информацией о суммах и валюте платежа |
Необязательно | finishedPaymentInfo.errorTypeName | String | Тип ошибки (либо SUCCESS) |
Необязательно | finishedPaymentInfo.feeAmount | String | Сумма комиссии |
Необязательно | finishedPaymentInfo.totalAmount | String | Полная сумма платежа |
Необязательно | finishedPaymentInfo.orderExpired | boolean | Истёк ли срок заказа |
Необязательно | finishedPaymentInfo.orderParams | object | Параметры заказа |
Необязательно | finishedPaymentInfo.refNum | String | Идентификационный номер заказа |
Необязательно | finishedPaymentInfo.finishPageLogin | String | Логин финишной страницы |
Необязательно | finishedPaymentInfo.sessionExpired | boolean | Истёк ли срок сессии |
Необязательно | finishedPaymentInfo.cardholderName | String | Имя держателя карты |
Необязательно | finishedPaymentInfo.paymentDate | String | Дата и время платежа |
Необязательно | finishedPaymentInfo.merchantUrl | String | URL адрес мерчанта |
Необязательно | finishedPaymentInfo.status | String | Статус оплаты |
Возможно наличие дополнительных полей, либо отсутствие некоторых полей из списка выше.
Пример
{
"redirectUrl": "https://bankhost.com/payment/merchants/ecom/finish.html?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"finishedPaymentInfo": {
"paymentSystem": "MASTERCARD",
"merchantShortName": "CoffeToGo",
"merchantLogin": "CoffeToGo",
"merchantFullName": "Coffee to Go",
"approvalCode": "123456",
"orderNumber": "4003",
"formattedTotalAmount": "15.00",
"backUrl": "https://www.coffeetogo.com/congratulation?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"failUrl": "https://www.coffeetogo.com/someproblem?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"terminalId": "12345678",
"orderDescription": "Order 123",
"displayErrorMessage": "",
"loadedResources": {
"footer": false,
"logo": false
},
"currencyAlphaCode": "EUR",
"orderFeatures": [
"ACS_IN_IFRAME",
"BINDING_NOT_NEEDED"
],
"isWebView": false,
"actionCodeDetailedDescription": "Request processed successfully",
"transDate": "29.11.2024 15:19:30",
"currency": "978",
"actionCode": 0,
"expiry": "12/2024",
"formattedAmount": "15.00",
"actionCodeDescription": "",
"formattedFeeAmount": "0.00",
"email": "address@mail.com",
"amount": "1500",
"merchantCode": "12345678",
"ip": "10.99.50.51",
"panMasked": "555555**5599",
"successUrl": "https://www.coffeetogo.com/congratulation?orderId=568b2db6-2acc-79e7-9ed8-746a00cd6608&lang=en",
"paymentWay": "CARD",
"processingErrorType": {
"value": "NO_ERROR",
"messageCode": "payment.errors.no_error",
"apiErrorCodeMessage": "payment.errors.no_error.code"
},
"panMasked4digits": "**** **** **** 5599",
"amountsInfo": {
"currencyDto": {
"alphabeticCode": "EUR",
"numericCode": "978",
"minorUnit": 2
},
"depositedAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"totalAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"refundedAmount": {
"value": 0,
"formattedValue": "0.00"
},
"approvedAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"feeAmount": {
"value": 0,
"formattedValue": "0.00"
},
"paymentAmount": {
"value": 1500,
"formattedValue": "15.00"
},
"amount": {
"value": 1500,
"formattedValue": "15.00"
},
"depositedTotalAmount": {
"value": 1500,
"formattedValue": "15.00"
}
},
"errorTypeName": "SUCCESS",
"feeAmount": "0",
"totalAmount": "1500",
"orderParams": {
"phone": "+4915112345678",
"foo": "bar",
"paymentMethod": "multiframe-sdk"
},
"orderExpired": false,
"refNum": "111111111111",
"finishPageLogin": "ecom",
"sessionExpired": false,
"cardholderName": "JOHN DOE",
"paymentDate": "29.11.2024 15:19:49",
"merchantUrl": "https://www.coffeetogo.com/",
"status": "DEPOSITED"
}
}
Неуспешное выполнение
При неуспешном выполнении Promise возвращает сообщение об ошибке, которое мы можем отобразить на странице. Пример сообщения: Error: Operation declined. Please check the data and available balance of the account
.
Примеры кода обработки данных, возвращаемых методом doPayment()
, приведены в разделе Шаг 2. Обработка нажатия кнопки оплаты. Выполнить метод оплаты для оплаты без сохраненной карты и с сохраненной картой
Автоматический редирект после совершения оплаты
После оплаты происходит автоматический редирект со страницы с Web SDK. Чтобы обработать возвращаемый методом doPayment()
объект непосредтственно на странице с Web SDK, требуется отключить автоматический редирект после совершения оплаты. Для этого требуется специальное разрешение в системе. Для получения разрешения обратитесь в службу технической поддержки банка. Также при инициализации Web SDK в передаваемом объекте должно содержаться свойство shouldHandleResultManually: true
.
webSdkForm = new window.PaymentForm({
...
shouldHandleResultManually: true,
...
});
Web SDK в React SPA
При использовании Web SDK в single page application на React необходимо инициализировать Web SDK, выполнив метод webSdkPaymentForm.init()
при каждом начальном рендере страницы с формой Web SDK в SPA.
При событии сброса страницы с формой Web SDK (т.е. при переходе на другую страницу), необходимо выполнить метод webSdkPaymentForm.destroy()
. Это важно, т.к. на странице должен оставаться только один обработчик формы webSDK (multiframe-commutator).
При возврате на страницу с формой Web SDK необходимо снова провести инициализацию при помощи метода webSdkPaymentForm.init()
.
Обратите внимание, что для использования этой библиотеки требуется соответствие стандарту PCI DSS, поскольку она обрабатывает данные карты. Подробнее о PCI DSS здесь.
Пример React компонента
import { useEffect, useRef } from "react";
function addScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.setAttribute("src", src);
script.addEventListener("load", resolve);
script.addEventListener("error", reject);
document.body.appendChild(script);
});
}
function App() {
const panRef = useRef(null);
const expiryRef = useRef(null);
const cvcRef = useRef(null);
const selectBindingRef = useRef(null);
const saveCardContainerRef = useRef(null);
const payButtonRef = useRef(null);
let webSdkPaymentForm = null;
useEffect(() => {
const initPaymentForm = async () => {
await addScript(
"https://3dsec.berekebank.kz/payment/modules/multiframe/main.js",
);
webSdkPaymentForm = new window.PaymentForm({
mdOrder: mdOrder,
containerClassName: "field-container",
onFormValidate: (isValid) => {
// Handle form validation
},
apiContext: "/payment",
language: "en",
autoFocus: true,
showPanIcon: true,
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: panRef.current,
onFocus: (containerElement) => {
// Handle focus
},
onBlur: (containerElement) => {
// Handle blur
},
onValidate: (isValid, containerElement) => {
// Handle validation
},
},
expiry: {
container: expiryRef.current,
// Handle expiry setup
},
cvc: {
container: cvcRef.current,
// Handle cvc setup
},
},
styles: {
base: {
padding: "0px 16px",
color: "black",
fontSize: "18px",
fontFamily: 'monospace',
},
focus: {
color: "blue",
},
disabled: {
color: "gray",
},
valid: {
color: "green",
},
invalid: {
color: "red",
},
placeholder: {
base: {
color: "gray",
},
focus: {
color: "transparent",
},
},
},
});
webSdkPaymentForm
.init()
.then(({ orderSession }) => {
console.info("orderSession", orderSession);
if (orderSession.bindings.length) {
selectBindingRef.current.style.display = "";
} else {
selectBindingRef.current.style.display = "none";
}
if (orderSession.bindingEnabled) {
saveCardContainerRef.current.style.display = "";
} else {
saveCardContainerRef.current.style.display = "none";
}
orderSession.bindings.forEach((binding) => {
const option = new Option(binding.pan, binding.id);
selectBindingRef.current.options.add(option);
});
})
.catch(() => {
// Handle initialization error
});
};
initPaymentForm();
return () => {
if (webSdkPaymentForm) {
webSdkPaymentForm.destroy();
}
};
}, []);
const handlePayment = () => {
payButtonRef.current.disabled = true;
webSdkPaymentForm
.doPayment({})
.then((result) => {
// Handle successful payment
})
.catch((e) => {
alert("Error");
})
.finally(() => {
payButtonRef.current.disabled = false;
});
};
const handleSelectBinding = () => {
const bindingId = selectBindingRef.current.value;
if (bindingId !== "new_card") {
webSdkPaymentForm.selectBinding(bindingId);
saveCardContainerRef.current.style.display = "none";
} else {
webSdkPaymentForm.selectBinding(null);
saveCardContainerRef.current.style.display = "";
}
};
return (
<div className="container">
<div className="websdk-form">
<div className="card-body">
<div
className="col-12"
id="select-binding-container"
onChange={handleSelectBinding}
>
<select
className="form-select"
id="select-binding"
ref={selectBindingRef}
aria-label="Default select example"
>
<option value="new_card">Pay by new card</option>
</select>
</div>
<div className="col-12 input-form">
<label htmlFor="pan" className="form-label">
Card number
</label>
<div id="pan" className="form-control" ref={panRef}></div>
</div>
<div className="col-6 col-expiry input-form">
<label htmlFor="expiry" className="form-label">
Expiry
</label>
<div id="expiry" className="form-control" ref={expiryRef}></div>
</div>
<div className="col-6 col-cvc">
<label htmlFor="cvc" className="form-label">
CVC / CVV
</label>
<div id="cvc" className="form-control" ref={cvcRef}></div>
</div>
<label className="col-12" id="save-card-container">
<input
className="form-check-input me-1"
ref={saveCardContainerRef}
type="checkbox"
value=""
id="save-card"
/>
Save card
</label>
</div>
<div className="pay-control">
<button
className="btn btn-primary btn-lg"
type="submit"
id="pay"
ref={payButtonRef}
onClick={handlePayment}
>
Pay
</button>
</div>
<div
className="error my-2 text-center text-danger visually-hidden"
id="error"
></div>
</div>
</div>
);
}
export default App;