Напиши мне денег: переводы через iMessage

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 можно приспособить для удобного обмена контактами из социальных сетей или для рассылки индивидуальных приглашений на какое-либо мероприятие.


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

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

  • 9 марта 2017 в 15:03 (комментарий был изменён)

    0

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


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

© Habrahabr.ru