image alt text


iMessage в свежих версиях iOS научился работать со сторонними расширениями. Например, теперь можно добавлять котиков к сообщениям или даже переводить кому-то деньги без дополнительных реквизитов. Это же мечта лентяя — отправлять деньги не выходя из мессенджера, поэтому разработка Яндекс.Денег засела за реализацию.


При разработке модной магии без квестов не обошлось, ведь iMessage практически ничего не рассказывает о получателе сообщения. Нет ни номера кошелька, ни ФИО, ни хотя бы статичного ID. Но мы придумали способ узнать об адресате все необходимое для отправки денег.


Зачем вообще переводить деньги по iMessage


… ведь есть «родное» приложение Яндекс.Денег, где нужно только указать номер кошелька получателя. На это можно ответить встречным вопросом: а почему бы не реализовать упрощенный механизм, который не будет требовать от пользователя знания номера кошелька и переключений между приложениями в процессе перевода денег?


Или можно ответить еще короче — потому что люди этого хотят, а мы это можем.


image alt text
… и никаких вопросов в духе «какой у тебя номер кошелька?», «куда перевести деньги?».


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


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


Разумеется, все описанное далее можно использовать и в других сценариях или приложениях, не обязательно ограничиваться переводами.


Как подружить iMessage с основным приложением


В первую очередь нужно наладить обмен данными между iMessage и основным приложением, для чего Яндекс.Деньги включили в App Groups:


// Общие user defaults:
let appGroupUserDefaults = NSUserDefaults(suiteName: "com.bundle.group") // Путь к общим ресурсам — нужен для доступа к общей базе данных
let appGroupDirectoryPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "com.bundle.group")

После этого для iMessage-расширения открывается доступ к списку пользовательских параметров NSUserDefaults и локальной базе данных приложения. Добавим еще Keychain Sharing для доступа к хранилищу Keychain мобильных Яндекс.Денег — там хранится платежный токен:


image alt text


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


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


let layout = MSMessageTemplateLayout()
layout.image = UIImage(named: "moneyTransfer")
layout.caption = "$\(conversation.localParticipantIdentifier.uuidString) хочет отправить деньги"
message.layout = layout

image alt text
Такой получился интерфейс переводов в сообщении.


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


Меняем абстрактный UUID на нечто более конкретное


В любой беседе iMessage собеседники представлены абстрактными идентификаторами UUID вида «154D1B67-FF3B-40E2-AB53-49DD127BB1FA». По UUID можно понять число собеседников в чате или использовать его для получения читаемых имен пользователей в текстовых полях.


Например, мы можем добавить текстовое описание в новое сообщение, в котором система заменит UUID на имя отправителя:


let newMessage = MSMessage(session: activeConversation.selectedMessage.session)
newMessage.summaryText = "$\(conversation.localParticipantIdentifier.uuidString) перевел \(amount) ?"
…
conversation.insert(newMessage)

Можно подумать, что UUID уникален во всей сети iMessage и за ним всегда прячется один и тот же собеседник, но это было бы слишком просто. В действительности UUID отличается на каждом устройстве одного и того же пользователя, а также создается заново при переустановке приложения, к которому привязано расширение. Поэтому для точной идентификации пользователя и понимания, что «отсюда» платеж точно выполнен, мы решили использовать UID Яндекс-аккаунта.


Как мы уже знаем, экземпляры класса MSMessage (сообщения) могут содержать множественные текстовые данные, которые кодируются в одном веб-адресе:


http://yamoney.ru?account=1234567&amount=100&comment=за%20пиццу

Такой строкой мы сразу сообщаем номер счета получателя, сумму перевода и пояснение для пользователя. Если в сообщении использовать URL-схему HTTP или HTTPS, то этот адрес будет открываться в браузере macOS с переходом на мобильный веб-интерфейс Яндекс.Денег. В iOS вместо этого вызывается соответствующее расширение.


Потом мы решили хранить всю текстовую информацию в объекте JSON, закодированном в base64:


"?data= TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdC…"

Использование JSON позволяет избежать длинных неудобных URL, а также добавляет универсальности и экономит трафик. Таким образом, сбор необходимой для перевода информации выполняется с помощью сервисных сообщений с закодированными URL.


Допустим, вы переводите Иннокентию 1000 р.


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


{
    "status": "created", // Статус сообщения
    "amount": "1000", // Сумма платежа
    "senderId": "dc47160234144e198baa62a3e5edafe5", // Яндекс аккаунт отправителя сообщения
    "text": "Возврат долга" // Комментарий к переводу
}

image alt text


Иннокентий тапает по сообщению и у него тоже открывается iMessage-расширение с предложением принять перевод. В ответное сообщение от Иннокентия добавляется номер его электронного кошелька в Яндекс.Деньгах, если он авторизован в основном приложении. Если же нет, с помощью URL-схемы расширение вызывает окно приложения для авторизации.


{
    "status": "accepted",
    "amount": "1000",
    "to": "4100145388962", // Номер Яндекс кошелька Иннокентия
    "senderId": "af865bc2575a803dc9726e876f6ee23f", // Яндекс аккаунт Иннокентия
    "text": "Возврат долга"
}

image alt text
Подтверждение платежа.


Как только Иннокентий согласится на перевод, можно отправлять деньги. Если вы уже авторизованы в приложении, откроется экран ввода ПИН-кода. С его помощью расширение iMessage расшифрует платежный токен их Keychain-приложения, после чего проведет с его помощью платеж. Подробнее про API Яндекс.Денег можно почитать в специальном разделе справки.


{
    "status": "paid",
    "amount": "1000",
    "to": "4100145388962",
    "senderId": "dc47160234144e198baa62a3e5edafe5",
    "text": "Возврат долга"
}

image alt text
Подтверждение ПИНом и экран успеха.


Когда нужно собрать деньги с нескольких участников беседы в iMessage, инициатор создает сообщение с одной суммой для каждого получателя, в котором содержится номер целевого кошелька. Получателям остается только тапнуть на сообщение и подтвердить перевод по уже знакомому нам алгоритму.


Легкая кривизна бета-фреймворка, и в целом про интерфейс


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


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


image alt text
Вот тут выяснилось, что «из коробки» расширение не очень дружит в альбомной ориентацией.


image alt text
А этот пример вообще самый любимый впервые на iOS диагональный интерфейс.


Раз уж мы коснулись интерфейсов, то подробнее остановимся на особенностях работы с GUI расширениями iMessage.


Основным View Controller для расширения является наследуемый от MSMessagesAppViewController класс, который и отображает интерфейс при активации расширения. Если же мы хотим отобразить другие view controller, то обычные вызовы present или push тут не сработают — необходимо накладывать контроллеры вручную.


Для этого мы написали небольшое расширение к классу UIViewController:


extension UIViewController {
    func presentChild(_ controller: UIViewController) {
        addChildViewController(controller)
        controller.view.frame = view.bounds
        view.addSubview(controller.view)
        controller.didMove(toParentViewController: self)
    }

    func dismissChild() {
        willMove(toParentViewController: nil)
        view.removeFromSuperview()
        removeFromParentViewController()
    }
}

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


Для расширения Яндекс.Денег мы обошлись тремя из них:


class MessagesViewController: MSMessagesAppViewController {
        override func willBecomeActive(with conversation: MSConversation) {
               super.willBecomeActive(with: conversation) 
               // Скрываем кнопку перевода, если это групповой чат. Иначе получится, что перевод получит тот, кто первый его подтвердит.
               transferButton.isHidden = conversation.remoteParticipantIdentifiers.count > 1
        }

        override func willSelect(_ message: MSMessage, conversation: MSConversation) {
           super.willSelect(message, conversation: conversation) 
               // При нажатии на сообщение открываем экран подходящий для его отображения
           present(message: message)
        }

        override func willTransition(to presentationStyle: MSMessagesAppPresentationStyle) {
        super.willTransition(to: presentationStyle) 
        // При смене формата отображения расширения меняем отображаемый view controller
        switch presentationStyle {
            case .compact: presentChild(selectViewController)
            case .expanded: present(message: activeConversation.selectedMessage)
                }
        }
...

}

Из заслуживающих внимания особенностей построения интерфейса на этом, пожалуй, все.


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


Какие еще платежные фишки, по-вашему, могут быть полезны в мессенджере?

Поделиться с друзьями
-->

Комментарии (13)


  1. Kondra007
    09.03.2017 15:03

    Платежи/донаты для каналов/ботов в Telegram :)
    (если вопрос в конце статьи про все мессенджеры)


    А для iMessage — например, сбор денег в складчину в групповых чатах (аналог вашего yasobe.ru)


    1. dimskiy
      09.03.2017 17:13

      Для iMessage сбор денег как раз и реализовали — через расширение Я.Денег можно скинуть запрос группе адресатов. Например, собрать с каждого по 300р на подарок к Дню Рождения кому-то.


      1. Kondra007
        09.03.2017 17:15

        Каюсь, отвлёкся и пропустил небольшой кусок текста, а редактировать было поздно.
        Спасибо!


  1. Germanets
    09.03.2017 15:48

    Иннокентий тапает по сообщению и у него тоже открывается iMessage-расширение с предложением принять перевод. В ответное сообщение от Иннокентия добавляется номер его электронного кошелька в Яндекс.Деньгах, если он авторизован в основном приложении. Если же нет, с помощью URL-схемы расширение вызывает окно приложения для авторизации.

    {
        "status": "accepted",
        "amount": "1000",
        "to": "4100145388962", // Номер Яндекс кошелька Иннокентия
        "senderId": "af865bc2575a803dc9726e876f6ee23f", // Яндекс аккаунт Иннокентия
        "text": "Возврат долга"
    }
    

    Надеюсь, что это всё-таки косяк псевдокода, но на всякий случай спрошу — в протоколе обмена сообщениями есть возможность изменить сумму перевода получателем, отдающим свой номер кошелька? Или всё-таки сумма закрепляется при создании платежа отправителем?
    Просто окончательное подтверждение суммы — это хорошо, но человек, создавший платёж может не обратить внимание, что исходная сумма изменилась при подтверждении.


    1. muxo
      09.03.2017 16:25

      У нас нет функционала меняющего сумму платежа в сообщении на промежуточных шагах, а никакое другое расширение открыть чужое кастомное сообщение не может. У Apple с этим всё строго


      1. DjPhoeniX
        09.03.2017 20:35
        +1

        У Apple-то, может, и строго, но не забываем, что может внезапно вылезти какой-нибудь хитрожопый Анонимус с джейлбрейком и прочими прелестями, и немного модифицировать ваши запросы, и отправлять лишний нолик в подтверждении перевода. Так что «доверяй, но проверяй» — сумму лучше переспрашивать у обоих. Крупно и подробно. Особенно если ожидаемый перевод не совпадает с тем, что ввёл инициатор.


        1. muxo
          10.03.2017 11:53

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


          1. Germanets
            10.03.2017 16:56

            Надеяться на пользователя в данном случае — плохой вариант. Нужно либо полностью запретить какую-либо теоретическую возможность подменить данные(фиксировать данные у инициатора и не передавать сумму обратно), либо — сделать яркое предупреждение, о том, что сумма изменена на другой стороне и вообще выкатить такое изменение как фичу.
            Более того — повторив одни и те же операции сотню раз, и привыкнув, что та пишется та же сумма, что и изначально — человек не будет обращать своё внимание ни на что, кроме кнопки «подтвердить перевод». В вопросах, связанных с деньгами клиентов не стоит полагаться на внимание пользователя и на корректность работы операционной системы на чьем-то телефоне.


            1. dimskiy
              10.03.2017 18:06

              Интересная мысль, спасибо что обратили внимание — передадим разработке


  1. onthefly
    09.03.2017 16:11

    Пользуясь случаем, прошу «разработку Яндекс.Денег» об одной сущей мелочи: сделайте так, чтобы можно было одной кнопкой подтвердить перевод и закрыть мобильное приложение.

    Сейчас приходит уведомление о необходимости подтверждения, выполняется подтверждение, после чего приложение остаётся открытым, требуя ввести пин-код.

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


    1. DjPhoeniX
      09.03.2017 20:37
      +2

      «Закрыть» мобильное приложение не получится. Точнее, получится (надо отправить UIApplication сообщение suspend), но вроде-как за такое App Review Team даёт линейкой по рукам, ибо «не лезь в Private API».


  1. Agb
    10.03.2017 08:07

    Комиссия за перевод между кошельками, это так 2000-но.
    Вывод средств с кошелька 3%… ну что тут сказать, когда у конкурентов от 2% до 1.6% и ниже.
    Даже для оплаты покупок яд не выгоден — по сравнению с кешбечными картами.
    Плюсы яд это широчайшая сеть пополнения в сберкоматах с кодом покупки, ну и периодически появляющиеся дыры)


    1. dimskiy
      10.03.2017 18:10

      Прекрасно вас понимаю, я тоже был бы не против пользоваться удобствами бесплатно… но век капитализма :)
      На что-то комиссия есть, на что-то нет. А бонусы за покупки и у нас, чего уж — карта с бонусами Связного, скидки по MasterCard, много и регулярно бонусы за оплату с кошелька от партнеров. На вкус и цвет, конечно.