# Пользовательский интерфейс

# Платежная страница

Платежная страница Mandarin имеет адаптивный дизайн, содержит ваш логотип, а также информацию о платеже, переданную вами в API-запросе.

Для работы с платежной страницей необходимо перенаправить пользователя на адрес из параметра userWebLink, который получен в синхронном ответе на ваш API-запрос.

Платежная страница локализована на русском и английском языках. Язык выбирается автоматически в зависимости от настроек браузера пользователя.

Платежная страница на русском языке

Payment Page Desktop - Russian

Платежная страница на английском языке

Payment Page Desktop - English

Мобильная верстка платежной страницы

Payment Page Mobile - Russian&English

# Custom Form

# Встроенная форма обработки карточных данных

Встроенная форма обработки карточных данных позволяет осуществить полноценную интеграцию в ваш интерфейс, при этом для вас отсутствуют требования по соответствию стандарту безопасности банковских карт PCI DSS.

При создании вашей платежной страницы и интеграции в неё формы следует помнить - если вы переносите комиссии на плательщика, в интерфейсе Custom Form необходимо указывать "Комиссия платежного сервиса" или "Комиссия за оплату картой", в зависимости от того, кто берёт комиссию (Mandarin или ваш сервис) , поскольку Custom Form - это White label решение. Например, "Сумма к оплате: 3060.0 руб. (в т.ч. комиссия платёжного сервиса 60.0 руб.)".

Custom Form представляет собой форму с полями, которые на самом деле являются отдельными страницами в iframe. Соответственно, вы можете стилизовать всё, что окружает поля iframe.

Такая форма состоит из двух частей: html-код, представляющий собой схему расположения будущих полей, состоящую из элементов div с классами, обозначающими, в какой из элементов будет помещено соответствующее поле iframe, окруженное элементом form с уникальным идентификатором (произвольным). Также в форму добавляется кнопка, ссылка или другой элемент, служащий триггером сабмита формы.

Для начала отрисовки формы необходимо создать транзакцию по инструкциям - оплата, выплата

# Основные принципы формы

В наиболее простом виде форма выглядит следующим образом:

Заполнение полей mandarinpay-field для формы :

Параметр Тип операции Обязателен
card-number Pay, Payout Да
card-holder Pay Опционально
card-expiration Pay Да
card-cvv Pay Да (возможна оплата без cvv, для уточнения обратитесь в техническую поддержку)

Код формы

<form id="form-hosted-pay">
    <div class="mandarinpay-field-card-number"></div>
    <div class="mandarinpay-field-card-holder"></div>
    <div class="mandarinpay-field-card-expiration"></div>
    <div class="mandarinpay-field-card-cvv"></div>
    <button onclick="return mandarinpay.hosted.process(this);">Оплатить</a>
</form>

Классы mandarinpay-field-card-number, mandarinpay-field-card-holder, mandarinpay-field-card-expiration и mandarinpay-field-card-cvv будут заполнены соответствующими полями iframe.

Эта форма обрабатывается скриптом hosted fields, который подключается к HTML: <script src="https://secure.mandarinpay.com/api/hosted/v2.js"></script>

После подключения скрипта hosted fields необходимо передать ему идентификатор html-формы и operationId, полученное в поле jsOperationId синхронного ответа.

Это делается функцией mandarinpay.hosted.setup:

<script>
mandarinpay.hosted.setup("#form-hosted-pay", 
{
  operationId: "9874694yr87y73e7ey39ed80",
});
</script>

В этот момент происходит инициализация формы, в div элементы формы помещаются соответствующие iframe полей, и форму можно заполнять. Данные заполненной формы передаются в скрипт hosted fields командой mandarinpay.hosted.process(this);, как показано в примере html-кода формы, или командой mandarinpay.hosted.process('#form-hosted-pay');. Данные команды в данном случае равнозначны.

Хотя в данном виде форма и работает, для реального использования необходим - и существует - гораздо более обширный функционал, позволяющий настраивать внешний вид, поведение и дополнительные возможности формы. Этот функционал будет разобран в последующих разделах.

# Верстка и внешний вид формы

Для удобства пользователя встраиваемая форма требует гораздо большего количества элементов - например, нужно как-то подписать поля формы, выделить цветом состояния полей формы, иметь возможность менять шрифт формы, добавлять плейсхолдеры и так далее. В качестве примера более сложного html-кода используем следующий:

Пример более практичного кода формы оплаты

<form id="form-hosted-pay">
  <div style="margin: 10px; padding: 10px; border: 1px solid gray">
    Card Number:
    <div class="mandarinpay-field-card-number hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    Card Holder:
    <div class="mandarinpay-field-card-holder hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    Card Expiration:
    <div class="mandarinpay-field-card-expiration hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    CVV:
    <div class="mandarinpay-field-card-cvv hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    <br/>
    <a href="#" onclick="return mandarinpay.hosted.process(this);" class="btn btn-default">Оплатить</a>
  </div>
</form>

Пример более практичного кода формы выплаты

<form id="form-hosted-pay">
  <div style="margin: 10px; padding: 10px; border: 1px solid gray">
    Card Number:
    <div class="mandarinpay-field-card-number hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    <br/>
    <a href="#" onclick="return mandarinpay.hosted.process(this);" class="btn btn-default">Выплатить</a>
  </div>
</form>

Как видите, добавлены дополнительные классы, инлайн-стили, добавили в поля формы дополнительные элементы <div class="glyphicon glyphicon-check"></div>, позволяющие использовать глификонки Bootstrap (для этого, разумеется, надо подключить соответствующий компонент Bootstrap), заменили кнопку "Оплатить" на ссылку (для её отображения также используется Bootstrap).

Следует помнить, что в процессе взаимодействия пользователя с формой тегам div могут быть присвоены классы mandarinpay-field-state-error, mandarinpay-field-state-focused и mandarinpay-field-state-valid, обозначающими, соответственно, состояние каждого поля и введенных в него значений (скрипт hosted fields также осуществляет валидацию значений по мере ввода).

Соответственно, теперь нужно добавить стили, позволяющие более наглядно отобразить форму и позволить пользователю интуитивно понять, что именно происходит с формой в данный момент.

CSS для полей

.hosted-field
{
    background: #f0f0f0;
    height: 40px;
    padding: 5px;
    border: 1px solid gray;
    border-radius: 10px;
}

.hosted-field {
    position: relative;
}

.hosted-field .glyphicon {
    visibility: hidden;
    position: absolute;
    right: 5px;
    top: 5px;
    color: green;
    float: right;
}

.mandarinpay-field-state-error
{
    background: #fff0f0;
    border: 1px solid #900000;
}

.mandarinpay-field-state-focused
{
    background: #ffffff;
    border: 1px solid yellowgreen;
}

.mandarinpay-field-state-valid {
    background: #c0ffc0 !important;
    border: 1px solid green !important;
}

.mandarinpay-field-state-valid  .glyphicon{
    visibility: visible;
}

Но таким образом можно настроить только те элементы, которые существуют на странице; всё, отображаемое непосредственно в iframe, для стилей недоступно. Стилизация содержимого iframe осуществляется посредством добавления соответствующего поля в объект, передаваемый mandarinpay.hosted.setup. Для изменения доступны placeholder и CSS-стили.

Пример стилизации для оплаты

mandarinpay.hosted.setup("#form-hosted-pay",
{ 
  operationId: "9874694yr87y73e7ey39ed80",
  fields:
  {
    "card-number": {
      settings: {
        placeholder: "НОМЕР КАРТЫ",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#0000c0"
        },
        placeholderStyles: {
          "color": "pink" 
        },
      }
    },
    "card-cvv": {
      settings: {
        placeholder: "123",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#555"
        },
      }
    },
  }
});

Пример стилизации для выплаты

mandarinpay.hosted.setup("#form-hosted-pay",
{ 
  operationId: "9874694yr87y73e7ey39ed80",
  fields:
  {
    "card-number": {
      settings: {
        placeholder: "НОМЕР КАРТЫ",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#0000c0"
        },
        placeholderStyles: {
          "color": "pink" 
        },
      }
    }
  }
});
  • color в формате #000000.
  • font-size с единицами px и pt.
  • font-family (можно использовать запятые и кавычки).
  • font-style.

В результате получаем некий подобный базовый вариант внешнего вида:

Custom Form

# События формы и их обработка

Для улучшения пользовательского взаимодействия с сайтом и с формой оплаты необходимо получать и обрабатывать события, происходящие во время работы пользователя с формой. Для этого было имплементировано несколько хуков-обработчиков событий, пример кода с пояснениями приведен ниже.

Имплементация хуков для оплаты

mandarinpay.hosted.setup("#form-hosted-pay",
{ 
  operationId: "9874694yr87y73e7ey39ed80",
  onsuccess: function(data) {
    // Событие, срабатывающее при успешной оплате.
    // Возвращаемые данные содержат информацию для дальнейшей обработки или демонстрации пользователю.
    alert("Success, id: " + data.transaction.id + ' card number: ' + data.cardInfo.maskedCardNumber);
  },
  onerror: function(data) {
    // Событие, срабатывающее при неудаче - например, нет денег на карте, неверный номер карты и так далее.
    alert("Error code: " + data.errorCode +" text: " + data.error);
  },
  oncancel: function(data) {
    // Событие, срабатывающее при отказе пользователя от оплаты - например, закрытие окна 3-D Secure вместо ввода кода.
    console.log(data);
  },
  onvalidationerror: function() {
    // Событие срабатывает при попытке отослать невалидные данные при нажатии на кнопку оплаты.
    alert("Validation error");
  },
  onformstatechange: function(state) {
    // Событие, отрабатывающее каждый раз на изменение состояния формы - например, ввод данных пользователем, потеря фокуса и так далее. Если, например, надо активировать кнопку оплаты только если все данные, введенные пользователем во все поля, валидны - удобно использовать это событие для проверки.
    console.log(state);
  },

  fields:
  {
    "card-number": {
      settings: {
        placeholder: "НОМЕР КАРТЫ",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#0000c0"
        },
        placeholderStyles: {
          "color": "pink" 
        },
      }
    },
    "card-cvv": {
      settings: {
        placeholder: "123",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#555"
        },
      }
    },
  }
});

Имплементация хуков для выплаты

mandarinpay.hosted.setup("#form-hosted-pay",
{ 
  operationId: "9874694yr87y73e7ey39ed80",
  onsuccess: function(data) {
    // Событие, срабатывающее при успешной оплате.
    // Возвращаемые данные содержат информацию для дальнейшей обработки или демонстрации пользователю.
    alert("Success, id: " + data.transaction.id + ' card number: ' + data.cardInfo.maskedCardNumber);
  },
  onerror: function(data) {
    // Событие, срабатывающее при неудаче - например, нет денег на карте, неверный номер карты и так далее.
    alert("Error code: " + data.errorCode +" text: " + data.error);
  },
  oncancel: function(data) {
    // Событие, срабатывающее при отказе пользователя от оплаты - например, закрытие окна 3-D Secure вместо ввода кода.
    console.log(data);
  },
  onvalidationerror: function() {
    // Событие срабатывает при попытке отослать невалидные данные при нажатии на кнопку оплаты.
    alert("Validation error");
  },
  onformstatechange: function(state) {
    // Событие, отрабатывающее каждый раз на изменение состояния формы - например, ввод данных пользователем, потеря фокуса и так далее. Если, например, надо активировать кнопку оплаты только если все данные, введенные пользователем во все поля, валидны - удобно использовать это событие для проверки.
    console.log(state);
  },

  fields:
  {
    "card-number": {
      settings: {
        placeholder: "НОМЕР КАРТЫ",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#0000c0"
        },
        placeholderStyles: {
          "color": "pink" 
        },
      }
    }
  }
});

# Добавление функционала Apple Pay

Для устройств, поддерживающих Apple Pay, полезно добавить оплату через Apple Pay. Этот функционал тоже доступен нашему скрипту, однако требует несколько более сложного подключения. Кнопка Apple Pay не должна показываться если браузер или устройство не поддерживают этот функционал, или если мерчант не позволяет оплату через Apple Pay; для этого существует специальная проверка, которую описана ниже.

Начнем с инициализации формы. Для ранее рассматривающегося функционала нам это не было важно, но вообще mandarinpay.hosted.setup это promise, возвращающий сессию с дополнительными функциями. Обсуждение промисов выходит за рамки данной инструкции, поэтому в данном примере предполагается, что промисы всегда fulfilled, и показываем только обработку успешно возвращенных данных.

Инициализация скрипта с доступом к дополнительному функционалу

// Важно обратить внимание на то, что область видимости переменной hostedSession должна быть глобальной - возможно обращаться к этой переменной из разных мест и даже скриптов.
var hostedSession;

mandarinpay.hosted.setup("#form-hosted-pay",
{ 
  operationId: "9874694yr87y73e7ey39ed80",
  onsuccess: function(data) {
  },
  onerror: function(data) {
  },
  oncancel: function(data) {
  },
  onformstatechange: function(state) {
  },
  fields:
  {
    "card-number": {
      settings: {
        placeholder: "НОМЕР КАРТЫ",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#0000c0"
        },
        placeholderStyles: {
          "color": "pink" 
        },
      }
    },
    "card-cvv": {
      settings: {
        placeholder: "123",
        styles: {
          "font-size": "20px",
          "font-family": "Helvetica",
          "color": "#555"
        },
      }
    },
  }
}).then(function(result){
    // Получаем результат промиса - сессию - и сохраняем её в нашей глобальной переменной hostedSession - дальше возможно или обращаться к сессии из других мест через переменную hostedSession (например, при нажатии на кнопку Apple Pay), или, например, передавать сессию напрямую в функцию.
    hostedSession = result;
    // Проверка на то, поддерживает ли устройство и мерчант эппл-пей возможно, разумеется, только после получения сессии, поэтому запускается по возвращению промиса.
    setupPromiseReturned(result);
  });

function setupPromiseReturned(result){
  // запускаем промис isApplePaySupportedAsync . Когда промис вернет ответ, передаётся ответ в функцию processApplePayResolve. Ответ, собственно, может быть только true или false.
  result.isApplePaySupportedAsync().then(processApplePayResolve);
}
function processApplePayResolve(result){
  // Скрипт прислал ответ, можно ли в данном случае показывать кнопку Apple Pay. Проверка включает в себя и поддержку браузера, и настройки мерчанта. Ответ может быть true или false
  if (result){
    console.log('Apple Pay поддерживается. Показываем кнопку.');
    $('.apple-pay-button').show();
  }else{
    console.log('Apple Pay не поддерживается. Не показываем кнопку.');
  }
}

Также добавляем кнопку Apple Pay в форму. Добавлять можно в любое место.

Код формы с кнопкой Apple Pay

<form id="form-hosted-pay">
  <div style="margin: 10px; padding: 10px; border: 1px solid gray">
    Card Number:
    <div class="mandarinpay-field-card-number hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    Card Holder:
    <div class="mandarinpay-field-card-holder hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    Card Expiration:
    <div class="mandarinpay-field-card-expiration hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    CVV:
    <div class="mandarinpay-field-card-cvv hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    <br/>
    <a href="#" onclick="return mandarinpay.hosted.process(this);" class="btn btn-default">Оплатить</a>
    <div class="apple-pay-button apple-pay-button-black" style="display: none;" onclick="return hostedSession.popupApplePay('#form-hosted-pay');"></div>             
  </div>
</form>

Обратите внимание на то, что кнопка спрятана по умолчанию - ведь неизвестно, поддерживается Apple Pay или нет до отработки промисов, а потому не можем сразу показывать кнопку. В случае положительного ответа от промиса isApple PaySupportedAsync кнопка показывается пользователю, а в случае отрицательного просто остается спрятанной.

Apple предлагает собственные инструкции по форме, размеру, правилам показа и прочим особенностям работы (opens new window) с кнопкой Apple Pay, пример одной из возможных реализаций верстки приводится ниже.

@supports (-webkit-appearance: -apple-pay-button) {
    .apple-pay-button {
        display: inline-block;
        -webkit-appearance: -apple-pay-button;
    }
    .apple-pay-button-black {
        -apple-pay-button-style: black;
    }
    .apple-pay-button-white {
        -apple-pay-button-style: white;
    }
    .apple-pay-button-white-with-line {
        -apple-pay-button-style: white-outline;
    }
}

Учитывая возможности, предоставляемые скриптом hosted fields, возможности интеграции формы оплаты в ваш интерфейс ограничены только фантазией вашего дизайнера и опытом программиста. Некоторые примеры интеграции формы hosted fields можно увидеть на демо-странице (opens new window).

# Интерактивный платеж как пример реализации Custom Form

Интерактивный платеж использует токен карты. В отличие от обычного рекуррентного платежа, в случае интерактивной оплаты пользователь вводит CVV/CVC-код. Для реализации формы ввода CVV/CVC-кода вы должны использовать технологию Custom Form.

Таким образом, вы можете использовать токен полных карточных данных, даже если он находится в статусе payout-only и для платежа в схеме одностадийной оплаты, и для авторизации в схеме двухстадийной оплаты.

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

  • interactive. Платеж будет осуществлен в интерактивном режиме независимо от статуса токена. Для этого необходимо передать "interactive": true.

  • allowinteractive. Платеж будет произведен с помощью автосписания (рекуррентный платеж). Если токен находится в статусе payout-only, то он будет осуществлен в интерактивном режиме. Для этого необходимо передать "allowinteractive": true.

Платеж происходит в два этапа:

  1. Инициация (в интерактивном режиме через API).

  2. Создание транзакции (с применением Custom Form).

# Инициация

В качестве примера рассмотрим запрос платежа по сохраненной карте в интерактивном режиме, где "interactive": true.

Для запроса платежа по сохраненной карте без ввода CVV/CVC-кода и без прохождения 3-D Secure, где "allowinteractive": true, необходимо использовать те же параметры.

В синхронном ответе содержится id платежа, и jsOperationId для создания транзакции через Custom Form.

Запрос interactive платежа для одностадийной оплаты

POST https://secure.mandarinpay.com/api/transactions
{
	"payment": {
		"action": "pay",
		"orderId": "your_unique_order_id",
		"price": "1000.00"
	},
	"target": {
		"card": "0eb51e74-e704-4c36-b5cb-8f0227621518"
	},
	"interactive": true,
	"customValues": [
		{"name": "first parameter to save and show", "value": "p1"},
		{"name": "second parameter to save and show", "value": "p2"}
	],
	"metadata": {
		"parameter to callback and not to show 0": "0",
		"parameter to callback and not to show 1": "1"
	},
	"urls": {
		"callback": "http://...",
		"return": "http://..."
	}
}

Запрос interactive авторизации для двухстадийной оплаты

POST https://secure.mandarinpay.com/api/transactions
{
	"payment": {
		"action": "preauth",
		"orderId": "your_unique_order_id",
		"price": "1000.00"
	},
	"target": {
		"card": "0eb51e74-e704-4c36-b5cb-8f0227621518"
	},
	"interactive": true,
	"customValues": [
		{"name": "first parameter to save and show", "value": "p1"},
		{"name": "second parameter to save and show", "value": "p2"}
	],
	"metadata": {
		"parameter to callback and not to show 0": "0",
		"parameter to callback and not to show 1": "1"
	},
	"urls": {
		"callback": "http://...",
		"return": "http://..."
	}
}

Ответ в случае успешной инициации (200 ОК)

{
	"id": "43913ddc000c4d3990fddbd3980c1725",
	"jsOperationId": "9874694yr87y73e7ey39ed80"
}

Ответ в случае, если инициация не произошла (400 Bad request)

{
	"error": "Invalid request"  
}

# Создание транзакции

Полученный в синхронном ответе jsOperationId в рамках предыдущего запроса необходимо использовать для создания транзакции через Custom Form. В этом случае необходимо передать только значение CVV. Передача остальных карточных данных не требуется.

Детальное описание находится в соответствующем разделе.

Создание транзакции через Custom Form

HTML-форма соответствует той, которую используется для полной формы оплаты, просто удаляются все поля кроме поля CVV:

<form id="form-hosted-pay">
  <div style="margin: 10px; padding: 10px; border: 1px solid gray">
    CVV:
    <div class="mandarinpay-field-card-cvv hosted-field"><div class="glyphicon glyphicon-check"></div></div>
    <br/>
    <a href="#" onclick="return mandarinpay.hosted.process(this);" class="btn btn-default">Оплатить</a>
  </div>
</form>

Инициализация формы проводится точно таким же способом, как и инициализация стандартной формы оплаты. Поддерживаются те же события, хуки, стилизация и так далее как и в стандартной форме. Пример:

<script>
mandarinpay.hosted.setup("#form-hosted-pay", {
  operationId: "9874694yr87y73e7ey39ed80",
  onsuccess: function(data) {
    alert("Success, id: " + data.transaction.id);
  },
  onerror: function(data) {
    alert("Error: " + data.error);
  }
});
</script>