Быстрый перевод из мессенджеров — QIWI Кошелек Android

    Привет!

    Меня зовут Алексей, я разработчик в компании QIWI.

    TL;DR

    Как перебросить из мессенджера сразу на платежную форму:

    1. В манифест помещаем пустую Activity c intent-filter вида ACTION_VIEW и ACTION_DIAL со схемой “tel”.
    2. В activity перебрасываем на форму оплаты через существующий deeplink, обогатив его данными из оригинального intent-а “tel:XXXXX”



    Профит: по клику на подсвеченный номер телефона в мессенджере человек попадает на форму перевода с заполненным полем получателя перевода.
    Бонус: расскажу, как красиво включать эту фичу, не имея возможности изменить список intent-filter в манифесте в рантайме.

    Зачем?


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

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

    Теория


    Многие мессенджеры в смартфонах распознают номера телефонов и подсвечивают их в виде гиперссылки. По умолчанию для обработки такого вида ссылок на любом мобильном девайсе с модулем GSM связи есть приложение. Обычно оно называется “Телефон”. В Android возможность обработки расшаренного номера телефона декларируется в AndroidManifest. Примеры приложений: “Телефон”, Viber, Skype. Если перехватить эти события, можно сразу предоставить пользователю список возможных действий с номером телефона. В нашем случае это будет предложение перевода через QIWI Кошелек.

    Нас интересуют события с типами Intent.ACTION_VIEW, Intent.ACTION_DIAL, Intent.ACTION_CALL со схемой “tel”. Событие Intent.ACTION_CALL имеет ограничения по политике безопасности Android и для его инициализации требуется разрешение от пользователя начиная с версии Marshmallow включительно. По нашим исследованиям мессенджеры используют более общие Intent.ACTION_VIEW, Intent.ACTION_DIAL.

    Существуют несколько похожих типов специально для телефонов экстренных служб, но это явно не наш случай. Тело интента имеет вид “tel:12345678”. Этот вид интента нельзя обработать android.content.UriMatcher, так как здесь отсутствует host. Самый простой способ обработать этот вид Uri — запросить у него schemeSpecificPart и проверить, является ли он валидным номером телефона.

    Список intent-filter-ов нужно указывать в AndroidManifest, поменять его в процессе исполнения приложения нельзя. Как дистанционно отключить эту фичу? Первый вариант — это отбить запрос пользователя на момент открытия Activity — сообщением или просто закрыть приложение. С нашей точки зрения это не лучший UX, так как мы прервем выполнение флоу пользователя на середине. Лучше, если мы не будем давать человеку выбрать наше приложение вообще, если функционал отключен. Проще отключать компонент со списком intent-filter в runtime, это не даст пользователю никаких ложных обещаний в интерфейсе системы. При использовании PackageManager обратите внимание, что проверка на включенность компонента не очень надежна. Предпочтительно выставлять флаг enabled у компонента принудительно, не опираясь на его текущее состояние используя значения COMPONENT_ENABLED_STATE_ENABLED и COMPONENT_ENABLED_STATE_DISABLED. Изменения, сделанные через PackageManager, сохраняются до момента удаления приложения с устройства.

    Практика


    Для того, чтобы отключить фичу, нам нужен Android-компонент для отключения.
    Самый простой способ это использовать пустую Activity, что мы и сделаем. Если вы решите использовать Service с аналогичным набором intent-filter, ознакомьтесь с гайдами Google, где в частности отмечают “do not declare intent filters for your services”. Обратите внимание, что по умолчанию компонент отключен: android:enabled="false"

      <activity
                android:name=".messengerP2P.view.MessengerP2PActivity"
                android:configChanges="orientation"
                android:label="@string/title_activity_messenger_p2_p"
                android:enabled="false"
                android:screenOrientation="portrait">
                <!-- Open shared telephone number as dial application -->
                <intent-filter
    
                    android:label="@string/title_activity_messenger_p2_p">
                    <action android:name="android.intent.action.VIEW" />
                    <action android:name="android.intent.action.DIAL" />
    
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:scheme="tel" />
                </intent-filter>
            </activity>

    Сама activity просто редиректит на форму оплаты через уже существующие диплинки.

    class MessengerP2PActivity : Activity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            var phoneNumberFromDial: String? = intent?.data?.schemeSpecificPart
            phoneNumberFromDial?.let {
                if (ru.mw.utils.Utils
                        .isPhoneNumber(phoneNumberFromDial)) {
                    val phoneNumberFromDialLink = PaymentActivity.getUriForProviderId(
                            resources.getInteger(R.integer.providerIdQiwiWallet).toLong(), null, null)
                        .buildUpon()
                        .appendQueryParameter(PaymentActivity.QUERY_PARAM_ACCOUNT, phoneNumberFromDial)
                    startActivity(
                        Intent(Intent.ACTION_VIEW, phoneNumberFromDialLink.build()))
                }
            }
            finish()
        }
    }

    Управлять флагом android:enabled можно этим вспомогательным классом.

    import android.content.ComponentName
    import android.content.Context
    import android.content.pm.PackageManager
    
    class MessengerP2PUtils {
    
        companion object {
            private const val componentName = "ru.mw.messengerP2P.view.MessengerP2PActivity"
    
            private fun switchMessengerP2P(enabled: Boolean = true, packageName: String, packageManager: PackageManager) {
                val compName = ComponentName(packageName, componentName)
                packageManager.setComponentEnabledSetting(
                    compName,
                    when (enabled) {
                        true -> PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                        else -> PackageManager.COMPONENT_ENABLED_STATE_DISABLED
                    },
                    PackageManager.DONT_KILL_APP)
            }
    
            fun enableMessengerP2P(applicationContext: Context) {
                val packageName = applicationContext.packageName
                val packageManager = applicationContext.packageManager
                switchMessengerP2P(enabled = true, packageName = packageName, packageManager = packageManager);
            }
    
            fun disableMessengerP2P(applicationContext: Context) {
                val packageName = applicationContext.packageName
                val packageManager = applicationContext.packageManager
                switchMessengerP2P(enabled = false, packageName = packageName, packageManager = packageManager);
            }
    
            fun isMessengerP2PEnabled(packageName: String, packageManager: PackageManager): Boolean {
                val state = packageManager.getComponentEnabledSetting(ComponentName(packageName, componentName))
                return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
            }
    
        }
    }

    Здесь не используется метод “isMessengerP2PEnabled” для проверки текущего состояния компонента, так как в ходе тестирования он выдавал ненадежные результаты. Если решитесь его использовать, тщательно проверьте его работу в условиях асинхронной бизнес-логики и при загрузке приложения.

    Время изменения значения флага enabled не очень важно, мы выбрали момент полной загрузки конфигурации feature-флагов.

    Чтобы при нажатии на номер телефона в мессенджерах был системный диалог выбора, нужно снабдить пользователя инструкцией, как скинуть настройки по умолчанию для intent-a “tel:XXXXX”. Например: “Если при нажатии на номер получается только позвонить, зайдите в Настройки смартфона → выберите Телефон в списке приложений → сбросьте настройку «Открывать по умолчанию».”


    Если отключить компонент на ходу, когда пользователю показан этот системный диалог, ничего страшного не случится. Из списка мгновенно пропадет соответствующий пункт, в нашем случае “Перевести деньги”.

    Интересное поведение начнется, если пользователь будет обрабатывать ссылки через наше приложение по умолчанию. Для этого ему достаточно ткнуть пункт “Запомнить выбор” или “Всегда”. Если отключить компонент, то пользователю система предоставит выбор, какое приложение использовать, причем при первом запуске не будет пункта “Запомнить выбор”. После включения фичи весь флоу восстановится, пользователя будет перебрасывать в приложение напрямую.

    Заключение


    Идея этой фичи пришла к нам давно, оригинальный тикет был создан в 2017 году. Задумка не утратила своей актуальности даже сейчас, найти банковское приложение со сходным функционалом мне не удалось. Мы зарелизили “переводы из мессенджера” 29 апреля, в первый день ими воспользовались почти 8000 уникальных пользователей. Если бизнес-показатели в ближайшее время нас удовлетворят, мы будем развивать эту фичу дальше.

    Как думаете, сколько еще интересных intent-ов Android-а мирно дожидаются своего бизнес-применения?
    QIWI
    Ведущий платёжный сервис нового поколения в России

    Комментарии 5

      +1
      Важно заметить, что Qiwi в своё время дал возможность регистрации без ограничений, а потом начал блокировать и замораживать суммы пользователей под предлогом того, что их номер телефона не был зарегистрирован на них (даже при наличии официальных документов).

      Расчёт на то, что человек не будет из-за относительно небольших сумм обращаться в суд или ЦБ для защиты своих интересов.

      Собственно, такая бизнес-схема вполне рабочая, но доверия к банку не добавляет.
        0
        А ещё через 180 суток неактивности кошелька начинают списывать абонентку по 10 рублей в день, и когда будет ноль — вся инфа о кошельке стирается вместе со всей историей транзакций. Таким был ответ техподдержки.
        Спасибо, QIWI.
          0
          Держал кошелёк, подтвержденный по паспорту. Переехал в другую страну, через время сим карту с номером на который был зарегистрирован кошелёк заблокировали и мой номер очевидно попал в продажу.
          Обратился в поддержку, чтобы они удалили мой кошелёк, опасаясь что кошелёк подтвержденный моим паспортом попадёт в чужие руки. Был готов предоставить любые пруфы, что кошелёк мой. Ответ убил… Меня попросили потратить деньги со счета (то-ли тенге, то-ли ещё в какой-то такой валюте, я даже не знаю как они появились на моем счете, наверное кто-то пошутил). Мол с рублями ладно, можете не тратить, мы сами справимся, а вот тенге потратьте не имея доступа к номеру телефона. При том там сумма была в духе 10 центов. Так и не добился ничего от поддержки.
          +1
          Из телеграмма предлагает киви. Из вайбера — никак, лишь пригласить в вайбер и Viber Out.
          Только у меня или особенность вайбера?
            +2
            По нашим исследованиям Viber не кидает в систему описанные в статье Intent-ы. С этим мессенджером фича не работает.

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

          Самое читаемое