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

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

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

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

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

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

Payment Page Desktop - Russian

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

Payment Page Desktop - English

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

Payment Page Mobile - Russian&English

# Mandarin Custom Pay

# Встраиваемая форма оплаты

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

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

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

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

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

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

Код формы

<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>

Как видите, мы добавили дополнительные классы, инлайн-стили, добавили в поля формы дополнительные элементы <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"
        },
      }
    },
  }
});
  • color в формате #000000.
  • font-size с единицами px и pt.
  • font-family (можно использовать запятые и кавычки).
  • font-style.

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

Mandarin Custom Pay

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

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

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

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

# Добавление функционала 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).

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

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

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

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

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

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

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

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

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

# Инициация

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

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

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

Запрос 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 в рамках предыдущего запроса необходимо использовать для создания транзакции через Mandarin Custom Pay. В этом случае необходимо передать только значение CVV. Передача остальных карточных данных не требуется.

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

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

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>