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>ххххх-ххххх-ххххх-ххххх</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;