Web SDK Payment

Описание

JavaScript-библиотека для отображения платежной формы на странице мерчанта, у которого нет PCI DSS.

Сценарий работы:

  1. Мерчант регистрирует заказ через REST API в платежном шлюзе
  2. Полученный mdOrder мерчант передает на страницу, где используется эта js-библиотека

SDK предоставляет возможность добавлять на свою платежную страницу поля ввода платежных данных через iframe на стороне платежного шлюза.

Плюсы:

Минусы:

Библиотека помогает в сборе данных карты, их валидации и проверке, совершении платежа и автоматическом перенаправлении покупателя на указанный в настройках returnUrl (конечная страница).

Как использовать

Подключить скрипт

Тестовая среда

<script src="https://vtb.rbsuat.com/payment/modules/multiframe/main.js"></script>

Боевая среда

<script src="https://platezh.vtb24.ru/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

mdOrder string required
Идентификатор заказа

Описание полей в форме

apiContext string optional
Контекст (часть URL-адреса платежного шлюза после домена) для запросов API.
По умолчанию apiContext автоматически берется из ссылки, используемой для подключения скрипта modules/multiframe/main.js.

language string optional
Язык, используемый для локализации ошибок и плейсхолдеров.
Значение по умолчанию: en.

autofocus boolean optional
Автоматическое переключение фокуса при заполнении полей.
Значение по умолчанию - true

showPanIcon boolean optional
Показывать иконку платежной системы.
По умолчанию true

panIconStyle CSSStyleDeclaration optional
Кастомные стили для иконки платежной системы

Кастомные стили для иконки платежной системы

containerClassName string optional
Имя класса выставляемое дополнительно для контейнера.
Значение по умолчанию: field-container

onFormValidate (result: boolean) => void optional
Callback для обработки изменения валидации формы.
Например:
onFormValidate: (isValid) => {
    alert(isValid ? 'Congratulations!' : 'Oops! We regret.'); 
}

Пример инициализации Web SDK

const webSdkPaymentForm = new window.PaymentForm({
  // Order number (order registration happens before initialization of the form)
  mdOrder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  // Name of the class that will be set for containers with frames
  containerClassName: "field-container",
  onFormValidate: (isValid) => {
    // Handling form validation
  },
  // Context for API queries
  apiContext: "/payment",
  // Language - is used for localization of errors and placeholder names.
  // The language must be supported in Merchant settings
  language: "en",
  // Automatically shift focus as fields are filled out
  autoFocus: true,
  // Show payment system icon
  showPanIcon: true,
  // Custom styles for payment system icon
  panIconStyle: {
    height: "16px",
    top: "calc(50% - 8px)",
    right: "8px",
  },
  fields: {
    pan: {
      container: document.querySelector("#pan"),
      onFocus: (containerElement) => {
        // Action when field gets focus
        // (containerElement contains link to field container element)
      },
      onBlur: (containerElement) => {
        // Action when field gets focus off it
        // (containerElement contains link to field container element)
      },
      onValidate: (isValid, containerElement) => {
        // Action when field is valid
        // (isValid is true if field is valid, otherwise is false)
        // (containerElement contains link to field container element)
      },
    },
    expiry: {
      container: document.querySelector("#expiry"),
      // ...
    },
    cvc: {
      container: document.querySelector("#cvc"),
      // ...
    },
  },
  // Style for input fields
  styles: {
    // Base state
    base: {
      color: "black",
    },
    // Focused state
    focus: {
      color: "blue",
    },
    // Disabled state
    disabled: {
      color: "gray",
    },
    // Has valid value
    valid: {
      color: "green",
    },
    // Has invalid value
    invalid: {
      color: "red",
    },
    // Style for placeholder
    placeholder: {
      // Base style
      base: {
        color: "gray",
      },
      // Style when focused
      focus: {
        color: "transparent",
      },
    },
  },
});

Метод destroy

Метод destroy() в Web SDK используется для удаления всех ресурсов и слушателей событий, связанных с конкретным экземпляром SDK. Когда вы вызываете метод destroy(), он очищает все слушатели событий и контейнеры iframe, которые были созданы SDK в течение его жизненного цикла. Это полезно, когда вам больше не нужен экземпляр SDK.

Метод destroy() обычно выполняет следующие задачи:

  1. Удаляет все слушатели событий, которые были добавлены в экземпляр SDK.
  2. Очищает все созданные контейнеры iframe.

Пример метода destroy

document.querySelector("#destroy").addEventListener("click", function () {
  webSdkPaymentForm.destroy();
});

Стилизация

Вы можете определить стили для контейнера iframe, используя имена классов:

className задаётся при инициализации в параметре containerClassName

Оплата без сохраненной карты (связки)

Шаг 1. Выполнить метод инициализации

После определения параметров Web SDK необходимо вызвать init(). Эта функция возвращает callback, где вы можете, например, скрыть загрузчик или сделать что-либо еще. init() возвращает Promise.

Например:

webSdkFormWithoutBindings
  .init()
  .then(() => {
    // Скрипт успешно инициализирован
    document
      .querySelector(".payment-form-loader")
      .classList.remove("payment-form-loader--active");
  })
  .catch((error) => {
    // При инициализации скрипта возникли ошибки. Дальнейшее выполнение невозможно.
  });

Шаг 2. Обработка нажатия кнопки оплаты. Выполнить метод оплаты

Чтобы выполнить оплату, вызовите функцию doPayment(). Отправлять данные карты не нужно, это сделает Web SDK. doPayment() возвращает Promise.

Метод принимает следующие параметры:

phone String optional
Номер телефона клиента

email String optional
Email клиента

cardholderName String optional
Имя держателя карты.

jsonParams Object optional
Дополнительные поля. Например, вы можете отправить дополнительную информацию о заказе или любую другую полезную для вас информацию. Например, jsonParams: { "t-shirt-color": "черный", "size": "M" }

Пример вызова:

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");
  });

Демонстрация без сохраненной карты

Для работы Web SDK Payment требует Order ID (идентификатор зарегистрированного заказа).
Для рабочих целей - регистрируйте заказ через 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>
        </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;
  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",
        },
        invalid: {
          color: "red",
        },
        placeholder: {
          base: {
            color: "gray",
          },
          focus: {
            color: "transparent",
          },
        },
      },
    });

    // Действие после инициализации
    webSdkFormWithoutBindings.init().then(() => {
      document
        .querySelector(".payment-form-loader")
        .classList.remove("payment-form-loader--active");
      })
  }

  // Обработчик "Оплатить"
  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");
    // Начинаем процесс оплаты
    webSdkFormWithoutBindings
      .doPayment({
        // Дополнительные параметры
        email: "foo@bar.com",
        phone: "4420123456789",
        cardholderName: "JOHN DOE",
        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) => {
    // Произошли ошибки при инициализации скрипта. Дальнейшее выполнение невозможно.
  });

Шаг 2. Обработка нажатия кнопки оплаты. Выполнить метод оплаты

Чтобы выполнить оплату, вызовите функцию doPayment(). Отправлять данные карты не нужно, это сделает Web SDK. doPayment() возвращает Promise.

Метод принимает следующие параметры:

phone String optional
Номер телефона клиента

saveCard boolean optional
Опция сохранения карты. Например, document.querySelector('#save-card').checked

email String optional
Email клиента

cardholderName String optional
Имя держателя карты.

jsonParams Object optional
Дополнительные поля. Например, вы можете отправить дополнительную информацию о заказе или любую другую полезную для вас информацию. Например, 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");
  });

Демонстрация с сохраненной картой

Для работы Web SDK Payment требует Order ID (идентификатор зарегистрированного заказа).
Для рабочих целей - регистрируйте заказ через 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>
        </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;
  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",
        },
        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");
      })
  }

  // Обработчик платежа
  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");
    // Начать оплату
    webSdkFormWithBindings
      .doPayment({
        // Дополнительные параметры
        email: "foo@bar.com",
        phone: "4420123456789",
        cardholderName: "JOHN DOE",
        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>

Web SDK в React SPA

При использовании Web SDK в single page application на React необходимо инициализировать Web SDK, выполнив метод webSdkPaymentForm.init() при каждом начальном рендере страницы с формой Web SDK в SPA.

При событии сброса страницы с формой Web SDK (т.е. при переходе на другую страницу), необходимо выполнить метод webSdkPaymentForm.destroy(). Это важно, т.к. на странице должен оставаться только один обработчик формы webSDK (multiframe-commutator).

При возврате на страницу с формой Web SDK необходимо снова провести инициализацию при помощи метода webSdkPaymentForm.init().

Пример 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://vtb.rbsuat.com/payment/modules/multiframe/main.js",
      );

      webSdkPaymentForm = new window.PaymentForm({
        mdOrder: mdOrder,
        containerClassName: "field-container",
        onFormValidate: (isValid) => {
          // Handle form validation
        },
        apiContext: "/payment",
        language: "en",
        autoFocus: true,
        showPanIcon: true,
        panIconStyle: {
          height: "16px",
          top: "calc(50% - 8px)",
          right: "8px",
        },
        fields: {
          pan: {
            container: panRef.current,
            onFocus: (containerElement) => {
              // Handle focus
            },
            onBlur: (containerElement) => {
              // Handle blur
            },
            onValidate: (isValid, containerElement) => {
              // Handle validation
            },
          },
          expiry: {
            container: expiryRef.current,
            // Handle expiry setup
          },
          cvc: {
            container: cvcRef.current,
            // Handle cvc setup
          },
        },
        styles: {
          base: {
            color: "black",
            fontSize: "18px",
          },
          focus: {
            color: "blue",
          },
          disabled: {
            color: "gray",
          },
          valid: {
            color: "green",
          },
          invalid: {
            color: "red",
          },
          placeholder: {
            base: {
              color: "gray",
            },
            focus: {
              color: "transparent",
            },
          },
        },
      });

      webSdkPaymentForm
        .init()
        .then(({ orderSession }) => {
          console.info("orderSession", orderSession);

          if (orderSession.bindings.length) {
            selectBindingRef.current.style.display = "";
          } else {
            selectBindingRef.current.style.display = "none";
          }

          if (orderSession.bindingEnabled) {
            saveCardContainerRef.current.style.display = "";
          } else {
            saveCardContainerRef.current.style.display = "none";
          }

          orderSession.bindings.forEach((binding) => {
            const option = new Option(binding.pan, binding.id);
            selectBindingRef.current.options.add(option);
          });
        })
        .catch(() => {
          // Handle initialization error
        });
    };

    initPaymentForm();

    return () => {
      if (webSdkPaymentForm) {
        webSdkPaymentForm.destroy();
      }
    };
  }, []);

  const handlePayment = () => {
    payButtonRef.current.disabled = true;

    webSdkPaymentForm
      .doPayment({})
      .then((result) => {
        // Handle successful payment
      })
      .catch((e) => {
        alert("Error");
      })
      .finally(() => {
        payButtonRef.current.disabled = false;
      });
  };

  const handleSelectBinding = () => {
    const bindingId = selectBindingRef.current.value;
    if (bindingId !== "new_card") {
      webSdkPaymentForm.selectBinding(bindingId);
      saveCardContainerRef.current.style.display = "none";
    } else {
      webSdkPaymentForm.selectBinding(null);
      saveCardContainerRef.current.style.display = "";
    }
  };

  return (
    <div className="container">
      <div className="websdk-form">
        <div className="card-body">
          <div
            className="col-12"
            id="select-binding-container"
            onChange={handleSelectBinding}
          >
            <select
              className="form-select"
              id="select-binding"
              ref={selectBindingRef}
              aria-label="Default select example"
            >
              <option value="new_card">Pay by new card</option>
            </select>
          </div>
          <div className="col-12 input-form">
            <label htmlFor="pan" className="form-label">
              Card number
            </label>
            <div id="pan" className="form-control" ref={panRef}></div>
          </div>
          <div className="col-6 col-expiry input-form">
            <label htmlFor="expiry" className="form-label">
              Expiry
            </label>
            <div id="expiry" className="form-control" ref={expiryRef}></div>
          </div>
          <div className="col-6 col-cvc">
            <label htmlFor="cvc" className="form-label">
              CVC / CVV
            </label>
            <div id="cvc" className="form-control" ref={cvcRef}></div>
          </div>
          <label className="col-12" id="save-card-container">
            <input
              className="form-check-input me-1"
              ref={saveCardContainerRef}
              type="checkbox"
              value=""
              id="save-card"
            />
            Save card
          </label>
        </div>
        <div className="pay-control">
          <button
            className="btn btn-primary btn-lg"
            type="submit"
            id="pay"
            ref={payButtonRef}
            onClick={handlePayment}
          >
            Pay
          </button>
        </div>
        <div
          className="error my-2 text-center text-danger visually-hidden"
          id="error"
        ></div>
      </div>
    </div>
  );
}

export default App;
Категории:
eCommerce SDK
Категории
Результаты поиска