Как Принимать Платежи В Мобильном Приложении: Токенизация, Nfc, Оптическое Сканирование И Прочие Вкусности В Одном Sdk

Я уже сказал ранее на примере Android SDK, как встроить нативную форму приема платежей по банковской карте в мобильное приложение, не ограничиваясь фреймом и WebView, и не попасть под аудит PCI DSS. С тех пор наш SDK достаточно существенно расширился и к привычной форме ввода карты в Android и iOS добавился следующий функционал: — Библиотека React Native для Android и iOS — настройка макета формы с реквизитами карты — функция оптического сканирования карт — прием бесконтактных платежей в Android по технологии NFC В этой публикации я расскажу, что можно делать с платежами в мобильных приложениях, какие есть лайфхаки и подводные камни, и напоследок приведу пример кода демо-приложения и расскажу, как списать долг по карте с друг, использующий NFC-считыватель вашего смартфона.



Как принимать платежи в мобильном приложении: токенизация, NFC, оптическое сканирование и прочие вкусности в одном SDK



Кейс 1. Привязка карты клиента к бэкенду для регулярных списаний или платежей в 1 клик.

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

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

Для этого вам необходимо совершить первый платеж через мобильное приложение при участии клиента и желательно с 3D-Secure, заблокировав на карте небольшую сумму, например 1 единицу валюты.

3D-secure в данном случае необходим, во-первых, чтобы защитить себя, как торговую точку, от финансовых претензий (чарджбэков) при будущих периодических списаниях, а во-вторых, для улучшения конверсии, как, например, с картами от Сбербанк в России и Приватбанк в Украине в большинстве случаев транзакция не пройдет без 3D-Secure. Итак, чтобы получить токен карты, вам нужно передать параметры требуетсяRecToken И проверка (подробнее о том, как создать мобильное приложение, смотрите в статье, ссылку на которую я указал в начале, а также в коде демонстрационные приложения на гитхабе):

  
  
  
   

order.setRequiredRecToken(true)



order.setVerification(true)

Параметр требуетсяRecToken требует, чтобы токен карты был возвращен после успешной авторизации карты, и проверка — что не нужно списывать средства с карты, а просто заблокировать их и затем вернуть (платежный шлюз возвращает их автоматически).

В ответ платежный шлюз вернет параметры RecToken — жетон карты, RecTokenLifeTime — срок действия токена (по сути срок действия карты) и MaskedCard — замаскированный номер карты, который необходимо привязать к токену в бэкенде для дальнейшего отображения клиенту при выборе способа оплаты.

Теперь, имея токен карты, вы можете вызвать метод в любой момент по запросу клиента или при наступлении срока оплаты.

списания по жетонам через межсерверное API и спишите необходимую сумму.

Подводные камни: По нашей статистике, довольно значительная часть держателей карт не имеет возможности оплатить через 3DSecure на мобильном устройстве по ряду причин, не зависящих от устройства и шлюза: — СМС может не прийти, либо пользователь, переключаясь между приложением СМС и вашим, потерял форму ввода пароля 3D-Secure, так как она открывается в WebView или системном браузере — теперь доступна верстка 3D-Secure страницы банка на смартфоне или планшете (банки очень редко адаптируют такие страницы) — веб-сервер банка отключил поддержку небезопасного протокола TSL 1.0, что делает 3D-Secure недоступным для версии Android <4.1 Ухищрение: На платежном шлюзе мы можем на лету включить/отключить 3D-Secure, а если клиент все равно не может заплатить, мы подстраиваемся под него и пытаемся совершить платеж без пароля 3D-Secure. Также стоит помнить, что если вы сохраните в своей системе токены от одного платежного провайдера, то вы не сможете использовать их на другом провайдере, если только провайдеры не договорятся между собой о миграции токенов, что, в принципе, уже произошло.

в нашей практике было несколько раз.



Кейс 2. Настройка макета формы ввода номера карты.

Часто возникает необходимость разместить поля для ввода номера карты, срока действия и cvv2 в другой последовательности, чем предусмотрено стандартной раскладкой в SDK. Но из-за требований PCI DSS нельзя просто заменить поле ввода номера карты стандартным компонентом EditText. Для этих целей мы разработали гибкую планировку.

Гибкая верстка наследует стили вашего мобильного приложения и позволяет расположить элементы формы в любой последовательности и в любом дизайне, предотвращая при этом случайную передачу данных карты на ваш бэкенд. Существует два механизма организации входа карты в SDK: CardInputView — готовое к использованию представление; CardInputLayout — это просто оболочка макета для создания представления в вашем собственном стиле разметки.

По сути CardInputView = CardInputLayout + CardNumberEdit + CardExpMmEdit + CardExpYyEdit + CardCvvEdit. Упрощенную структуру CardInputView в XML можно записать следующим образом:

<com.cloudipsp.android.CardInputLayout> <com.cloudipsp.android.CardNumberEdit/> <LinearLayout android:orientation="horizontal"> <com.cloudipsp.android.CardExpMmEdit /> <com.cloudipsp.android.CardExpYyEdit /> </LinearLayout> <com.cloudipsp.android.CardCvvEdit /> <com.cloudipsp.android.CardInputLayout>

Поэтому вы можете абсолютно свободно настраивать и располагать элементы ввода по своему воображению.

Необходимо соблюдать только одно правило — каждый из входных элементов (CardNumberEdit, CardExpMmEdit, CardExpYyEdit, CardCvvEdit) должен находиться в CardInputLayout один раз, при этом уровень вложенности View не имеет значения.

Вот как это может выглядеть:

Как принимать платежи в мобильном приложении: токенизация, NFC, оптическое сканирование и прочие вкусности в одном SDK

Подводные камни: При настройке полей ввода стоит помнить: - cvv2 может состоять из 3 или 4 символов.

— номер карты может содержать от 14 до 19 символов — вы можете добиться максимально точной настройки вашего дизайна, разветвив SDK и внеся изменения в реализацию вашего макета (это не запрещено, если вы не начнете передавать данные карты через свой бэкенд).

Но при форке вы теряете поддержку обновлений SDK со шлюза и интеграцию новых функций.

Ухищрение: Часто на форме ввода реквизитов карты можно встретить поля для ввода имени и фамилии держателя карты и его почтового индекса.

Для платежей внутри СНГ практической необходимости делать это в 99% случаев нет – только некоторые банки США, Канады и Великобритании поддерживают данную технологию, которая называется Система проверки адреса , и чтобы чек работал, он должен поддерживаться как банком-эквайером, так и банком-эмитентом.



Как принимать платежи в мобильном приложении: токенизация, NFC, оптическое сканирование и прочие вкусности в одном SDK



Кейс 3. Подключение возможности сканирования карты через камеру и NFC

Для Android в библиотеке реализована функция сканирования оптических карт. Android-SDK-оптический , для iOS в библиотеке CloudipspОптический с использованием SDK карты.

io .

NFC-сканирование реализовано с помощью библиотек Android-SDK-NFC И реакция-native-cloudipsp-nfc и доступно только для Android. Хотя Apple открывается начиная с iOS 11+ Сторонние разработчики умеют читать RFID-метки, но чтение EMV-меток с банковских карт по-прежнему остается недоступным.

Пример демонстрационного приложения для использования NFC

package com.cloudipsp.nfcexample; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; import android.util.Patterns; import android.view.View; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.Toast; import com.cloudipsp.android.Card; import com.cloudipsp.android.CardInputView; import com.cloudipsp.android.Cloudipsp; import com.cloudipsp.android.CloudipspWebView; import com.cloudipsp.android.Currency; import com.cloudipsp.android.Order; import com.cloudipsp.android.Receipt; import com.cloudipsp.nfc.NfcCardBridge; public class MainActivity extends Activity implements View.OnClickListener { private static final int MERCHANT_ID = 1396424; private EditText editAmount; private Spinner spinnerCcy; private EditText editEmail; private EditText editDescription; private CardInputView cardInput; private CloudipspWebView webView; private Cloudipsp cloudipsp; private NfcCardBridge nfcCardBridge; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); nfcCardBridge = new NfcCardBridge(this); findViewById(R.id.btn_amount).

setOnClickListener(this); editAmount = (EditText) findViewById(R.id.edit_amount); spinnerCcy = (Spinner) findViewById(R.id.spinner_ccy); editEmail = (EditText) findViewById(R.id.edit_email); editDescription = (EditText) findViewById(R.id.edit_description); cardInput = (CardInputView) findViewById(R.id.card_input); cardInput.setHelpedNeeded(true); findViewById(R.id.btn_pay).

setOnClickListener(this); webView = (CloudipspWebView) findViewById(R.id.web_view); cloudipsp = new Cloudipsp(MERCHANT_ID, webView); spinnerCcy.setAdapter(new ArrayAdapter<Currency>(this, android.R.layout.simple_spinner_item, Currency.values())); if (savedInstanceState == null) { processIntent(getIntent()); } } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_amount: fillTest(); break; case R.id.btn_pay: processPay(); break; } } private void fillTest() { editAmount.setText("1"); editEmail.setText("[email protected]"); editDescription.setText("test payment"); } private void processPay() { editAmount.setError(null); editEmail.setError(null); editDescription.setError(null); final int amount; try { amount = Integer.valueOf(editAmount.getText().

toString()); } catch (Exception e) { editAmount.setError(getString(R.string.e_invalid_amount)); return; } final String email = editEmail.getText().

toString(); final String description = editDescription.getText().

toString(); if (TextUtils.isEmpty(email) || !Patterns.EMAIL_ADDRESS.matcher(email).

matches()) { editEmail.setError(getString(R.string.e_invalid_email)); } else if (TextUtils.isEmpty(description)) { editDescription.setError(getString(R.string.e_invalid_description)); } else { final Currency currency = (Currency) spinnerCcy.getSelectedItem(); final Order order = new Order(amount, currency, "vb_" + System.currentTimeMillis(), description, email); order.setLang(Order.Lang.ru); final Card card; if (nfcCardBridge.hasCard()) { card = nfcCardBridge.getCard(order); cardInput.display(null); } else { card = cardInput.confirm(); } cloudipsp.pay(card, order, new Cloudipsp.PayCallback() { @Override public void onPaidProcessed(Receipt receipt) { Toast.makeText(MainActivity.this, "Paid " + receipt.status.name() + "\nPaymentId:" + receipt.paymentId, Toast.LENGTH_LONG).

show(); } @Override public void onPaidFailure(Cloudipsp.Exception e) { if (e instanceof Cloudipsp.Exception.Failure) { Cloudipsp.Exception.Failure f = (Cloudipsp.Exception.Failure) e; Toast.makeText(MainActivity.this, "Failure\nErrorCode: " + f.errorCode + "\nMessage: " + f.getMessage() + "\nRequestId: " + f.requestId, Toast.LENGTH_LONG).

show(); } else if (e instanceof Cloudipsp.Exception.NetworkSecurity) { Toast.makeText(MainActivity.this, "Network security error: " + e.getMessage(), Toast.LENGTH_LONG).

show(); } else if (e instanceof Cloudipsp.Exception.ServerInternalError) { Toast.makeText(MainActivity.this, "Internal server error: " + e.getMessage(), Toast.LENGTH_LONG).

show(); } else if (e instanceof Cloudipsp.Exception.NetworkAccess) { Toast.makeText(MainActivity.this, "Network error", Toast.LENGTH_LONG).

show(); } else { Toast.makeText(MainActivity.this, "Payment Failed", Toast.LENGTH_LONG).

show(); } e.printStackTrace(); } }); } } @Override public void onBackPressed() { if (webView.waitingForConfirm()) { webView.skipConfirm(); } else { super.onBackPressed(); } } @Override public void onNewIntent(Intent intent) { super.onNewIntent(intent); processIntent(intent); } private void processIntent(Intent intent) { if (nfcCardBridge.readCard(intent)) { Toast.makeText(this, "NFC Card read success", Toast.LENGTH_LONG).

show(); nfcCardBridge.displayCard(cardInput); } } }

Отличается от обычной реализации наличием NfcCardBridge и привязкой к нему Intent для ожидания события чтения карты (readCard) Подводные камни: Хотя карта считывается с помощью NFC, протокол финансовой авторизации карты по-прежнему отсутствует. Те.

Для полноценной работы данного функционала карта должна быть открыта для онлайн-платежей.

Ухищрение: Написав простое приложение, вы сможете с его помощью перевести средства с чужой карты на свою, поднеся чужую карту к своему телефону.

Например, это может быть удобно, если вам нужно списать с друга небольшую сумму за долг по карте.

С одной стороны, это будет практично и удобно, с другой – весьма эффектно.

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

В целях безопасности сумма, которую можно списать через NFC без поддержки 3D-Secure, не может превышать эквивалент 4 долларов США.

Теги: #Android #iOS #платежи #sdk #платежные системы #Разработка для iOS #Разработка мобильных приложений #Разработка для Android

Вместе с данным постом часто просматривают: