Тема внутренних маркетплейсов очень популярна в продуктовом мире. Благодаря App Store и Play Market встроенные приложения стали частью нашей повседневной жизни. В этой статье я расскажу, как мы используем iFrame, чтобы дать внешним разработчиками возможность качественно встраиваться в нашу систему. Объясню, с какими сложностями уже успели столкнуться и какие инсайты получили.
Статья будет полезна тем, кто работает в продуктовых компаниях и строит платформу для своего продукта. Если ты человек продукта, запрыгивай на борт.
Немного о том, что мы делаем в Poster
Poster — это облачная система автоматизации для кафе, баров и ресторанов. Для непосвященных звучит пугающе, а если проще — кушали когда-то в Пузатой хате? Когда рассчитываешься, кассир пробивает заказ в системе учета. Такая система учета (ее еще называют POS-система или просто «касса») делает много важных и полезных вещей: ведет учет остатков на складе, печатает чеки, рассчитывает затраты на приготовление блюда, выручку и т.д.
Большинство POS-систем начинали свой путь со времен Windows 98 и выглядят вот так:
Работа с заказом в стационарной системе R_Keeper
Мы первые в СНГ сделали это по-другому:
Работа с заказом в Poster
Касса на iPad вместо Windows-моноблока. Админка в облаке вместо сервера под паркетом заведения. Сегодня с нами работают 8000 активных заведений в 75 странах мира.
Для чего нам открытый API
С самого начала мы знали, что у нашего продукта будет открытый API. Мы всегда верили, что за открытыми системами будущее. Для нас API это прежде всего:
Рост функций продукта и вовлечения пользователей.Это очевидно: всегда будет недостаточно ресурсов, чтобы запускать все функции, которые просят клиенты. Интеграции помогают продукту качественно расти в глазах пользователя, расширяют его функционал, а значит, более вероятно, что продукт не поменяют на конкурентный.
Драйвер роста продаж основного продукта.Например, к нашим партнерам, которые занимаются системами лояльности приходят клиенты с устаревшим кассовым ПО или вообще без него — наша потенциальная целевая аудитория. В этот момент партнер скорее всего порекомендует ту POS-систему, с которой у него есть интеграция и хорошие отношения.
Дополнительный канал заработка.Как правило, каталоги берут комиссию с продаж приложений. Наша комиссия 30%, но бизнес-модель компании не строится вокруг маркетплейса. В нашем случае, мы ставим перед маркетплейсом задачу быть самоокупаемым.
Как пришли к iFrame
Веб-API закрывает много варинатов интеграций, но к нам часто обращаются партнеры, которые хотят встроить свое решение прямо в кассу или админ-панель. Нужно было найти решение, которое было бы изолированным, чтобы внешний разработчик не смог поломать работу Poster, и при этом не ограниченным в инструментах разработки. Мы рассматривали несколько вариантов:
Встраивать приложение в код ядра.Изначально так и делали, но нагородили кучу костылей, от которых даже сейчас сложно избавиться. Не подходит.
Хостить JS/HTML на нашем сервере и загружать в админку.Таким подходом получим бесшовную интеграцию, но кто-то бесшовно поломает всю систему. Не катит.
Встроить в админку iFrame с приложением.
— Не, ребят, iFrame — это говно! Еще мой дед их юзал.
Это первое, что мы услышали про iFrame, но серьезных аргументов «против» не нашли, поэтому решили попробовать 😀
Почему разработчики думают, что iFrame говно? Честно, не знаю. Я и сам так думал, пока не нырнул в эту кроличью нору. Когда писал статью, собрал мнения, почему не нужно юзать iFrame. Вот, что услышал:
- «iFrame небезопасен» → Да, так и было, лет 10 назад.
- «У меня на прошлом проекте все было написано на iFrame, чтобы прокинуть параметр из одной части сайта в другую, нужно потратить день» → Если неправильно использовать и городить костыли, то так и получится.
- «Он очень медленный» → Согласен, если 5 штук добавить на страницу, начнет тупить. Тогда все, что нужно сделать — не добавлять 5 штук.
В общем, все это, на мой взгляд, стереотип, главное как iFrame использовать.
Как мы используем iFrame
Наш путь к платформе интеграций начался с веб-API, которое позволяло управлять данными аккаунта: создавать чеки, получать статистику по продажам, обновлять остатки на складах. Постепенно, мы добавили надстройки:
- Manage платформукак возможность встроить веб-страницу в админ-панель;
- POS платформудля расширения функций кассы;
- Device платформу — единый хаб для управления устройствами в заведении.
В этой статье я расскажу самое интересное: как мы используем iFrame в POS-платформе. Начну с нескольких примеров, которые уже сделали на этой технологии:
- Оплатить заказ криптовалютой, emoji;
- Отправить заказ из кассы на доставку;
- Вызвать официанта, просканировав QR-код на столе;
- Оплатить заказ бонусами из приложения по лояльности.
Вот так система лояльности может расширить работу кассы:
Приложение на платформе сканирует QR-код, который гость показывает на телефоне в приложении лояльности. Кассир видит бонусы гостя и может списать их в счет оплаты.
Платформа под капотом
Касса Poster — это Single Page Application, которое открыто в браузере в нативном приложении. Внутри SPA под каждое приложение создается iFrame, в котором мы загружаем страницу с таким HTML:
<html manifest="/platform_157.appcache"><head><script src="/js/pos/platform/bundle.1545155099.js"></script><link rel="stylesheet" href="/css/pos/platform/main.1545155225.css"></head><body><div id="app-container"></div></body></html>
На этой странице подгружается JS, который связывает Poster и iFrame. Как только страница полностью загружена, iFrame отправляет Poster сообщение о том, что он готов к работе. В примере я буду использовать упрощенный код, но в реальности все обернуто в дополнительный уровень абстракции:
window.top.sendMessage({ action: "loaded", msgHash: "Ha2s7m" })
Poster слушает сообщения от iFrame и в ответ говорит, что нужно загрузить ещё один JS с кодом, который написал уже внешний разработчик:
// Слушатель на стороне Poster window.addEventListener("message", (msg) => { If (msg.action === "loaded") { // Отвечаем приложению window.top.sendMessage({ action: "init", data: "http://localhost:8080/bundle.js" }) } });
Разработчик пишет свой SPA на основе нашего шаблона на github. JS собирается Webpack-ом в один bundle файл. В процессе разработки bundle раздается через Webpack Dev Server и касса грузит его с локальной машины. При этом можно использовать фишки webpack, такие как live reload.
Как только приложение закончено, разработчик загружает bundle к нам на сервер одной командой — npm run deploy
. С этого момента любой клиент Poster может подключить приложение и пользоваться им.
Зачем загружать код к нам на сервер? В работе заведения часто случаются перебои с интернетом, поэтому Poster работает оффлайн и, если отключить интернет и перезагрузить Poster, все ресурсы загружаются из кэша. Для этого мы используем AppCache, но у нее есть ограничение — кэшируемые файлы должны быть на одном домене. Таким образом, чтобы внешние приложения могли работать оффлайн, их код должен быть на нашем сервере.
API платформы
Мы даем простой JS API, с которым можно управлять кассой. Например, чтобы установить на открытый заказ скидку, нужно написать следующий код:
let result = await window.Poster.orders.getActive(); // Получаем текущий заказ await window.Poster.orders.setOrderBonus(result.order.id, 10) // Ставим скидку 10 грн
window.Poster — это глобальная переменная со всеми методами API. Под капотом каждый метод отправляет сообщение на Poster через postMessage. Каждому сообщению присваивается уникальный хеш. По этому хешу в локальную переменную сохраняется callback, который нужно вызвать, как только закончится выполнение метода. Когда Poster заканчивает выполнение, он отправляет на iFrame postMessage c результатом и хешом сообщения, на которое отвечает. По нему мы находим сохраненный callback и вызываем его.
Таким образом, вся коммуникация между iFrame и Poster построена на postMessage, при этом весь этот процесс двухсторонний и может инициироваться как со стороны Poster, так и со стороны фрейма.
Для удобства разработки все функции API возвращают Promise. Код процессится через babel поэтому можно использовать async. Это упрощает написание кода и помогает избежать callback hell.
Интерфейс и общение с пользователем
Чаще всего отображение интерфейса инициируется действием пользователя. Например, создали заказ, добавили гостя, перешли к окну закрытия заказа. Поэтому приложение может подписаться на такие ивенты и показывать свой интерфейс.
По умолчанию, iFrame скрыт от пользователя. Чтобы показать его, нужно вызвать метод Poster.interface.popup
, который просто меняет CSS у iFrame и показывает его поверх основного интерфейса. В попапе разработчик рендерит страницу любым удобным для него способом. Например, мы используем React, но можно подключить другие фреймворки: Angular, Vue.js.
Некоторые ивенты могут менять обычный алгоритм работы Poster. Такие ивенты называются блокирующими и ждут ответа от приложения. Например, ивент закрытия заказа: в один из аргументов обработчика приходит callback-функция, которую нужно вызвать, чтобы продолжить закрытие заказа. Эту функцию нужно вызвать в течение 5 секунд или отобразить свой интерфейс, иначе мы перезагрузим iFrame и продолжим стандартный алгоритм работы Poster.
Приведу пример бизнес задачи, которую помогают решать блокирующие ивенты.
Владелец кафе хочет оцифровывать базу своих гостей. Поэтому он вводит правило — кассир должен предложить гостю завести дисконтную карту и, таким образом, передать свои контакты. Но на практике сотрудники забывают про это правило. Чтобы решить эту проблему, можно написать простое приложение, которое будет показывать сотруднику напоминание перед закрытием заказа. Выглядеть оно будет так:
// Подписываемся на ивент перед закрытием заказа Poster.on('beforeOrderClose', (data, next) => { // Если к заказу уже привязан гость, продолжаем обычный алгоритм работы Poster if (data.order.clientId) { next(); } else { // Показываем окно с напоминанием Poster.interface.popup({title: 'Спроси про дисконтную карту'}); } });
В итоге, в платформе можно нарисовать любой интерфейс, но он ограничен рамками iFrame и не может поломать работу кассы. К сожалению, не у всех разработчиков есть дизайнеры, которые смогут продумать UI/UX до деталей. Большинство просто юзает Bootstrap и не заморачивается над качеством. Это привело нас к новому вызову — сделать интерфейсы внешних приложений простыми и красивыми. Глобально, мы еще не решили эту задачу, но план уже есть:
- Выпустить свою библиотеку с компонентами интерфейса, чтобы разработчики могли использовать ее вместе с Bootstrap.
- Сформировать UI/UX гайдлайны.
- Сделать шаблон приложения с готовым интерфейсом.
Все наши планы по разработке платформы мы опубликовали на доске Trello, где видно какие задачи и когда мы планируем брать. Там же внешние разработчики могут голосовать за фичи или предлагать новые.
Инсайты в процессе разработки
Работая над интеграцией внешних приложений, мы поняли, как сильно платформа упрощает разработку внутренних. Приведу несколько примеров.
Кастомные доработки под клиента
В ядре продукта мы стараемся не делать фичи под клиента. Раньше приходилось отказывать в кастомных доработках, например, запретить гостям пользоваться бонусами, пока сумма покупок не превысит 500 грн.
Сейчас рекомендуем нанять разработчика, который сделает эту доработку для кассы. Так внешний разработчик сети кофеен RedCup решил проблему с мошенничеством со стороны гостей, когда гость покупает сим-карту за 50 грн, регистрирует карту лояльности и получает бонус 200 грн.
Упрощение выхода на зарубежные рынки
С ростом компании у нас появляется все больше зарубежных клиентов. У каждой страны есть свои национальные особенности. Например, в Польше обеды относят к расходам компании. Чтобы иметь возможность это сделать, ресторан должен выставить специальный инвойс на компанию. Мы пробовали делать этот функционал внутри ядра Poster, но поняли, что тогда придется закрыть на месяц всю разработку на доработки под этот рынок.
В итоге, мы наняли в Польше разработчика на аутсорс, который самостоятельно делает необходимые фичи под клиентов, а ядро только дорабатывает API, которое сразу же шерим для всех внешних разработчиков. Это позволило намного быстрее продвинуться по продажам в Польше.
Улучшение архитектуры кода в ядре
Мы стали разбивать приложение на микросервисы, оставляя только самое необходимое в ядре системы, а новые функции делать как надстройки. Например, сейчас мы работаем над системой, которая будет показывать владельцу заведения, что рестораторы в его регионе закупают морковку или другие ингредиенты по 25 гривен, а он по 30. И все это делается на платформе открытой для всех разработчиков.
Итог
За полтора года работы POS-платформы iFrame зарекомендовал себя как отличный инструмент, и мы планируем использовать его в дальнейших проектах. С его помощью мы качественно решаем проблемы наших клиентов и приводим новых клиентов партнерам. Уже 1200 заведений из 8000 пользуются внешними разработками.
Если эта статья будет интересна читателям, в следующей я поделюсь опытом создания Device-платформы — хаба, который управляет девайсами в заведении.