Web SDK Payment
Описание
JavaScript-библиотека для отображения платежной формы на странице мерчанта. Данный способ подходит мерчантам как c высоким, так и с низким уровнем соотвтетсвия требованиям PCI DCC.
Сценарий работы:
- Мерчант регистрирует заказ через REST API в платежном шлюзе
- Полученный
mdOrder(номер заказа) мерчант передает на страницу, где используется данная js-библиотека
Web SDK предоставляет возможность добавлять на свою платежную страницу поля ввода платежных данных через iframe непосредственно от платежного шлюза. Безопасность передачи данных обеспечивается шифрованием протокола HTTPS.
Плюсы:
- Безопасно: Платежная страница мерчанта и её скрипты не имеют доступа к платежным полям, переданным через iframe. Данные карт не могут быть собраны внешними скриптами.
- Просто: Скрипт легко интегрируется на страницу. Чтобы соответствовать общему стилю сайта, требуется минимальная кастомизация.
- Надёжно: Со стороны мерчанта не требуется соответствие высоким уровням PCI DCC.
- Удобно: На сервер могут быть переданы дополнительные поля, такие как
email,language,phoneиjsonParams.
Минусы:
- На данный момент Web 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>Если вы используете связки, вам необходимо включить дополнительный блок HTML: #select-binding.
Вот пример HTML-формы, которая поддерживает связки:
<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>Вы можете добавить в форму любые дополнительные поля, такие как Cardholder name (имя владельца карты), Email (электронная почта), Phone (номер телефона) и т. д. Однако не забудьте в дальнейшем передать их в метод doPayment().
Инициализация Web SDK
Описание платежной формы
Вам нужно выполнить функцию конструктора new window.PaymentForm().
window.PaymentForm() может принимать следующие свойства:
Свойства инициализации PaymentForm
По умолчанию apiContext автоматически берется из ссылки, используемой для подключения скрипта
modules/multiframe/main.js.
Значение по умолчанию:
en.
Значение по умолчанию -
true
По умолчанию
false
По умолчанию
false
По умолчанию
true
По умолчанию
true
Значение по умолчанию:
field-container
Например:
onFormValidate: (isValid) => {
alert(isValid ? 'Congratulations!' : 'Oops! We regret.');
}Пример инициализации Web SDK
const webSdkPaymentForm = new window.PaymentForm({
// Номер заказа (регистрация заказа происходит до инициализации формы)
mdOrder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
// Имя класса, который будет установлен для контейнеров с полями ввода
containerClassName: "field-container",
onFormValidate: (isValid) => {
// Обработка валидации формы
},
// Контекст для API запросов
apiContext: "/payment",
// Язык - используется для локализации ошибок и названий плейсхолдеров.
// Язык должен поддерживаться в настройках Мерчанта
language: "en",
// Автоматически переключать фокус при заполнении полей
autoFocus: true,
// Показывать иконку платежной системы
showPanIcon: true,
// Пользовательские стили для иконки платежной системы
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: document.querySelector("#pan"),
onFocus: (containerElement) => {
// Действие при получении фокуса полем
// (containerElement содержит ссылку на элемент-контейнер поля)
},
onBlur: (containerElement) => {
// Действие при потере фокуса полем
// (containerElement содержит ссылку на элемент-контейнер поля)
},
onValidate: (isValid, containerElement) => {
// Действие при валидации поля
// (isValid равен true если поле валидно, иначе false)
// (containerElement содержит ссылку на элемент-контейнер поля)
},
},
expiry: {
container: document.querySelector("#expiry"),
// ...
},
cvc: {
container: document.querySelector("#cvc"),
// ...
},
},
// Стили для полей ввода
styles: {
// Базовое состояние
base: {
color: "black",
padding: '0px 16px',
fontSize: '18px',
fontFamily: 'monospace',
},
// Состояние с фокусом
focus: {
color: "blue",
},
// Отключенное состояние
disabled: {
color: "gray",
},
// С валидным значением
valid: {
color: "green",
},
// С невалидным значением
invalid: {
color: "red",
},
// Стиль для плейсхолдера
placeholder: {
// Базовый стиль
base: {
color: "gray",
},
// Стиль при фокусе
focus: {
color: "transparent",
},
},
},
});Метод destroy
Метод destroy() в Web SDK используется для удаления всех ресурсов и слушателей событий, связанных с конкретным экземпляром Web SDK. Когда вы вызываете метод destroy(), он очищает все слушатели событий и контейнеры полей ввода, которые были созданы Web SDK в течение его жизненного цикла. Это полезно, когда вам больше не нужен экземпляр Web SDK.
Метод destroy() обычно выполняет следующие задачи:
- Удаляет все слушатели событий, которые были добавлены в экземпляр Web SDK.
- Очищает все созданные контейнеры полей ввода.
Пример метода destroy
document.querySelector("#destroy").addEventListener("click", function () {
webSdkPaymentForm.destroy();
});Стилизация
Стилизация контейнеров, в которые передаются поля ввода, определяется самостоятельно согласно дизайну вашей страницы. Различные состояния контейнеров поля ввода можно стилизовать с помощью следующих CSS-классов:
-
{className}--focus- поле в фокусе -
{className}--valid- поле с валидным значением -
{className}--invalid- поле с невалидным значением
Параметр className задается при инициализации через параметр containerClassName в свойствах window.PaymentForm().
Пример:
<style>
.field-container {
width: 100%;
height: 50px;
padding: 0;
}
.field-container--focus {
border-color: #86b7fe;
outline: 0;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.field-container--valid {
border-color: #198754;
}
.field-container--valid.field-container--focus {
box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25);
}
.field-container--invalid {
border-color: #dc3545;
}
.field-container--invalid.field-container--focus {
box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25);
}
</style>
<script>
//...
const paymentForm = new window.PaymentForm({
//...
containerClassName: 'field-container',
//...
</script>Шрифты
В Web SDK можно использовать системные/предустановленные на устройства шрифты. Шрифт задается при инициализации Web SDK в свойстве styles или customStyles. Например в styles.base.fontFamily.
Стилизация полей ввода
Вы можете настроить внешний вид полей ввода. Для этого
- Используйте объект
stylesдля базовых стилей всех полей - Используйте
customStylesдля переопределения стилей отдельных полей (например, pan, expiry, cvc)
Пример:
const webSdkPaymentForm = new window.PaymentForm({
// ...
// стили по умолчанию для всех полей ввода
styles: {
base: {
color: 'black',
padding: '0px 16px',
fontSize: '18px',
fontFamily: 'Arial, sans-serif',
},
// ...
invalid: {
color: 'red',
},
// ...
},
// кастомные стили для поля ввода номера карты
customStyles: {
pan: {
base: {
color: 'blue',
padding: '0px 24px',
fontSize: '22px',
},
invalid: {
color: 'orange',
},
},
},
});Дополнительные настройки
Настройте дополнительные параметры при инициализации, такие как язык placeholder, иконки платежных систем, маскирование полей ввода и др. Полный список параметров доступен в Свойствах инициализации PaymentForm.
Валидация
WebSDK обеспечивает проверку, валидацию и защиту только основных полей ввода, требующихся для проведения оплаты: Pan, Expiry, CVC.
Все остальные дополнительные поля, такие как Cardholder name, Phone, Email, поля обеспечивающие выполнение требований мандата Visa Secure Data и т.п., мерчант обязан валидировать самостоятельно на своей стороне.
Примеры регулярных выражений для валидации дополнительных полей:
Cardholder name:
// regex: ^[A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]* [A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]*$
export function validateCardholderName({
errorMessage = 'Invalid cardholder'
} = {}) {
return (value) => {
const regex = /^[A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]* [A-zÁÉÍÑÓÚÜáéíñóúü][A-zÁÉÍÑÓÚÜáéíñóúü'\.\s]*$/;
return regex.test(String(value).trim()) ? null : errorMessage;
};
}Email:
// regex: ^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$
export function validateEmail({
errorMessage = 'Invalid email'
} = {}) {
return (value) => {
const regex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
return regex.test(String(value).toLowerCase()) ? null : errorMessage;
};
}Phone:
// regex: ^\+\d{7,15}$
export function validatePhone({
errorMessage = 'Invalid phone number'
} = {}) {
return (value) => {
const regex = /^\+\d{7,15}$/;
return regex.test(String(value).trim()) ? null : errorMessage;
};
}Оплата без сохраненной карты (связки)
Шаг 1. Выполнить метод инициализации
После определения параметров Web SDK необходимо вызвать init().
Эта функция возвращает callback, где вы можете, например, скрыть загрузчик или сделать что-либо еще.
init() возвращает c.
Например:
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(() => {
// Действия выполняемые после инициализации, независимо от её успешного или неуспешного выполнения.
});Шаг 2. Обработка нажатия кнопки оплаты. Выполнить метод оплаты
Чтобы выполнить оплату, вызовите функцию doPayment().
Отправлять данные карты не нужно, это сделает Web SDK. doPayment() возвращает Promise.
Метод принимает следующие параметры:
jsonParams: { "t-shirt-color": "черный", "size": "M" }
Обновление мандата Visa Secure Data Field
Обратите внимание на требования IPS Visa относительно дополнительных полей данных, необходимых для запросов на аутентификацию EMV 3DS. Мерчанты должны предоставлять полные и точные данные о транзакциях в своих запросах на аутентификацию. Мерчанты также должны гарантировать, что 3DS Method URL-адрес совершает сбор данных устройства для поддержки успешной аутентификации в случае, если 3DS Method URL-адрес предоставлен эмитентом.
Таким образом, сбор дополнительных полей для VISA является ответственнотью мерчанта. Вы можете ознакомиться c полным текстом требований в Visa Secure Data Field Mandate.
Дополнительные поля передаются как свойства объекта, который является аргументом функции doPayment().
Пример вызова:
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>Оплата сохраненной картой (связкой)
Шаг 1. Выполнить метод инициализации
После определения параметров Web SDK необходимо вызвать init().
Эта функция возвращает callback, где вы можете, например, скрыть загрузчик или сделать что-либо еще.
init() возвращает Promise.
Например:
// Инициализация
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');
});Шаг 2. Обработка нажатия кнопки оплаты. Выполнить метод оплаты
Чтобы выполнить оплату, вызовите функцию doPayment().
Отправлять данные карты не нужно, это сделает Web SDK. doPayment() возвращает Promise.
Метод принимает следующие параметры:
document.querySelector('#save-card').checked
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>xxxxx-xxxxx-xxxxx-xxxxx</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": "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": "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": "x.x.x.x",
"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, требуется отключить автоматический редирект после совершения оплаты. Для этого требуется специальное разрешение в системе ‒ в платежном шлюзе для данного мерчанта должна быть включена пермиссия Поддержка Acs IFrame включена. Для получения разрешения обратитесь в службу технической поддержки банка. Также при инициализации Web SDK в передаваемом объекте должно содержаться свойство shouldHandleResultManually: true.
Например:
webSdkForm = new window.PaymentForm({
...
shouldHandleResultManually: true,
...
});Web SDK в React SPA
При использовании Web SDK в single page application (SPA) на 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) => {
// Обработка валидации формы
},
apiContext: "/payment",
language: "en",
autoFocus: true,
showPanIcon: true,
panIconStyle: {
height: "16px",
top: "calc(50% - 8px)",
right: "8px",
},
fields: {
pan: {
container: panRef.current,
onFocus: (containerElement) => {
// Обработка фокуса
},
onBlur: (containerElement) => {
// Обработка потери фокуса
},
onValidate: (isValid, containerElement) => {
// Обработка валидации
},
},
expiry: {
container: expiryRef.current,
// Настройка поля срока действия
},
cvc: {
container: cvcRef.current,
// Настройка CVC/CVV поля
},
},
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(() => {
// Обработка ошибки инициализации
});
};
initPaymentForm();
return () => {
if (webSdkPaymentForm) {
webSdkPaymentForm.destroy();
}
};
}, []);
const handlePayment = () => {
payButtonRef.current.disabled = true;
webSdkPaymentForm
.doPayment({})
.then((result) => {
// Обработка успешного платежа
})
.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;