company_banner

Темные уголки iOS Auto renewable Subscriptions


    Все больше и больше приложений переходят на подписочную систему монетизации. Планируете использовать систему подписок в своем приложении? Тогда вам сюда.


    Денис Кириллов на Mobius 2019 Moscow рассказал о лучших практиках применения технологии Auto-renewable Subscriptions в iOS и осветил ряд особенностей в ее работе, которые не всегда очевидны и документированы в официальных источниках.


    Под катом — видео и текстовая расшифровка доклада с конференции Mobius 2019 Moscow.



    Денис руководит командой iOS-разработки в компании Mamba. За 15 лет в сфере ИТ участвовал в работе над проектами на платформах: macOS, Windows, Android, Bada, Tizen и Smart TV.


    Далее — повествование от лица спикера.


    Сегодня я расскажу вам о том, как реализовать автопродлеваемые подписки в вашем iOS-приложении и как справиться со сложностями, которые у вас могут возникнуть.


    План доклада


    • Небольшая предыстория о том, почему я решил углубиться в эту тему.
    • Разберем несколько распространенных способов реализации автопродлеваемых подписок и сравним их между собой.
    • Поговорим о работе над ошибками, — об одном из самых важных аспектов реализации надежной системы в целом.
    • Раздел «Грязные хаки» будет посвящен примерам, в которых вы увидите, что не нужно делать в своем приложении с автопродлеваемыми подписками, чтобы с позором не вылететь из AppStore.
    • Раздел «Легальный Boost» противоположен «Грязным хакам» и содержит информацию о том, какие существуют легальные способы прокачать свою подписку, найти новых и сохранить старых подписчиков.

    Подписки рулят


    Подписки становятся всё более популярной моделью монетизации. Тому есть ряд причин.
    Первая причина — это регулярная выручка, которую вы можете потратить на дальнейшую поддержку и развитие своего продукта уже после релиза.
    Вторая причина — это сниженная до 15% комиссия на прибыль, которую вы получаете от ваших подписчиков, которые пользуются подпиской суммарно более 12 месяцев и не делали перерывов более, чем на 60 дней.
    Третья причина — это гибкие возможности ценообразования, которые дают вам рычаги для взаимодействия со своей аудиторией на финансовом плане после релиза.


    Главный минус системы автопродлеваемых подписок — это высокая цена ошибки. Если в вашей системе что-то работает не так, если что-то ломается, то пользователи быстро начинают вас ненавидеть. Соответственно, это приводит к резкой потере рейтинга, снижению количества подписчиков и снижению прибыли.


    Несколько лет назад я стал свидетелем подобной истории: мы получили проект в наследство от другой команды. Для обработки платежей как в iOS, так и в Android в проекте изначально использовались сторонние библиотеки. Они работали стабильно, не вызывали никаких нареканий, пока один раз в Android-релизе не полыхнуло. Вечером в комнату разработчиков зашел сотрудник отдела контроля качества и сказал, что замечены некие проблемы с продлением подписок. Ребята сели разбираться, спустя несколько часов безуспешных попыток найти конкретную ошибку они решили оставить дело до утра и ушли домой. Примерно к полудню следующего дня мониторинг просигнализировал полное отсутствие подтверждения оплаченных подписок. Бизнес насчитал крупную сумму убытков, рейтинг приложения вошел в пике и устремился к одной звезде. Проект потерял часть своей репутации. Некоторых сотрудников даже показательно лишили премии. Тогда мы с командой решили, что мы избавляемся от всех сторонних решений, от всех библиотек, выпиливаем весь чужой код, разбираемся в этой теме и пишем свой код, который удобно, просто и понятно поддерживать. С этого началось моё путешествие в удивительный мир автопродлеваемых подписок, о тонкостях и особенностях которых мы и поговорим.


    Реализация


    Существует 4 распространенных подхода к реализации системы автопродлеваемых подписок, чтобы было короче, буду называть их просто «подписки». Начнем разбирать их с самого простого и перейдем к самому навороченному, а в конце сравним их.



    На схеме стандартные компоненты, участники системы. Это пользователь, у которого есть деньги на карте, это сервер Apple, который знает о существовании пользователя, StoreKit, который является посредником между сервисами Apple и нашим приложением. Начнем рассмотрение с того момента, как пользователь оплатил подписку. Не имеет значения в данном методе, сделал он это в первый раз, став вашим подписчиком, или выполнил продление. Когда Apple получил деньги, их сервер сгенерировали у себя Receipt (чек). Receipt — это основной компонент, вокруг которого строится большинство бизнес-логики во всей системе работы с подписками.


    Давайте посмотрим, что там внутри.



    Во-первых, начиная с iOS 7, это файл, который лежит в Bundle вашего приложения, туда его кладет система iOS, там же она его обновляет. Известно, что как минимум система обновляет его после запуска приложения. Во-вторых, это не обычный файл, это криптоконтейнер, данные в котором подписаны электронно-цифровой подписью по стандарту PKCS#7. Причем payload, это полезные данные, которые содержатся там, это не привычный JSON или XML, а бинарный формат ANS.1. Receipt содержит самое нужное для нас, — историю всех транзакций. Это актуально только для автопродлеваемых подписок. В Receipt есть вся история того, как пользователь оплачивал, продлял или менял условия подписки. Receipt уникален для конкретного AppleId и приложения. В случае необходимости посредством StoreKit, можно запросить принудительное обновление Receipt до последней версии.



    После того как сервер Apple сгенерировал Receipt, наше приложение получает уведомление о новой транзакции и новый Receipt. Далее приложение должно его обработать.



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


    Посмотрим на этот процесс детальнее.



    Проверить подлинность Receipt можно, воспользовавшись библиотекой OpenSSL для проверки ЭЦП и выполнив сравнение вычисляемых по специальному алгоритму значений GUID_hash и Receipt_hash. Подробно алгоритм получения GUID_hash и Receipt_hash описан в документации Apple. Для их вычисления понадобится извлечь из Receipt данные в формате ANS.1 и преобразовать их в заготовленные модели данных. Обработать данные в формате ANS.1 поможет одноименная утилита (ans1). Эта утилита, получив схему данных из документации, генерирует Objective-C код для получения готовых моделей. Получив все данные, вычислив GUIDE_hash и Receipt_hash, мы сравниваем их значение. Если они совпали, то можно считать что Receipt прошел верификацию. В конце остается пройтись по массиву транзакций извлеченных из поля latest_receipt_info и найти транзакцию, в которой обозначена самая поздняя дата expires_date. Значение этой даты, дата до которой пользователь оплатил подписку.


    Эти действия не тривиальны, но с этим можно разобраться рассмотрев примеры готовых решений, заглянув в документацию и приложив немного усилий. Всё было бы хорошо, если бы не существование в мире людей, которые не хотят платить деньги за контент. Среди людей этих есть хакеры, которые придумали множество способов, как этого добиться.



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


    Второй способ заключается в непосредственном изменении кода вашего приложения в функции верификации Receipt. После взлома модифицированное приложение распространяется через неофициальные сторы, достаточно распространенные например в Китае. Взлом выполняется путем подмены условного оператора if на else в момент верификации, либо вставкой раннего return с фиксированным значением даты в далеком будущем. По факту это нескольких измененных байт в нужном месте кода.
    Третий подход заключается в подмене самой даты окончания подписки (exp.date), там где она хранится, либо подменой эталонных часов, которые используются для ее сравнения с текущей датой.


    Борьба с этими уязвимостями и есть основная сложность данного метода.


    Плюсы и минусы подхода


    Подведем итоги.



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


    Защита при подходе All in App


    Для того чтобы реализовать защиту, есть некий набор рекомендаций. Во-первых, не стоит хранить дату в открытом виде, лучше её зашифровать, и не хранить в NSDefaults. Во-вторых, верификацию имеет смысл сделать в нескольких местах, написать несколько функций verify, вызывать их в разных местах, чтобы их было сложнее найти и изменить. В-третьих, очень эффективной является проверка на наличие Jailbreak. Все способы, о которых я до этого говорил, возможны на джейлбрейкнутых устройствах. Если вы задетектили Jailbreak, вы можете просто заставить приложение не работать. Это будет нормально, потому что те люди, которые занимаются взломом, ведут себя не очень прилично, и вы, если вам позволит ваша совесть можете поступить с ними так же. Но для того чтобы принять решение об этом, вы должны знать, какое количество ваших пользователей пользуется Jailbreak-ом, должны оценивать то, насколько это вообще имеет смысл. В-четвертых нужно проверять часы не по настройкам системы, а где-то ещё, например, на стороннем time server’е.


    В расширенном виде рекомендации по защите дополняются отказом от использования готовых решений, использованием языка C, вместо Objective-C, там, где это возможно, и статическая линковка библиотеки OpenSSL, которая защищает её от подмены. Никто вас не ограничивает в фантазии и возможности создания изощренных ловушек, которыми будут снабжены методы верификации, и помимо самой функции верификации определять, факт стороннего вмешательства в данные или код.


    Подход — App unlock, API verify



    Смысл подхода “App unlock, API verify” в том, что ранее описанные действия по защите кода и верификации, мы делегируем на сторону сервера Apple. Для этого нам понадобится ещё один компонент — собственный сервер API. Он будет очень простой, в нем будет лишь один запрос, который принимает Receipt и отправляет его в Apple, а в ответ от него мы получаем проверенные данные о статусе подписке в удобном формате JSON. Замечу, что последовательность действий в этом подходе одинакова для как продления, так и для инициации подписки.



    С точки зрения защиты от взлома, в данном подходе у нас стало на одну проблему меньше, функцию verify мы больше не пишем. Однако вы по-прежнему можете заметить, что остается угроза поддельного Receipt.


    Это возможно из-за уязвимости типа «Man in the middle». Благо что существует достаточно эффективный способ защиты от нее под названием «SSL Pinning». Использовать эту защиту мы можем только в связке с своим подконтрольным сервером для которого мы контролируем наличие и обновление SSL сертификата. Именно поэтому в данном подходе мы не отправляем Receipt напрямую в Apple, а делаем это через наш сервер.


    Теперь уязвимостей становится ещё меньше и остается всего одна задача по обеспечению защиты -, защитить саму дату и место её хранения.


    Плюсы и минусы подхода



    Из плюсов данного метода мы имеем простой код как на сервере, так и на клиенте. Для того чтобы написать такое API, достаточно порядка десяток строк на том же Node.js. У нас есть достаточно надежная защита от поддельного Receipt. Данный подход очень хорошо подходит как первый шаг для того, чтобы развивать свою систему дальше в сторону более навороченных решений. Минусы — это расходы на содержание сервера, от которых мы уже никуда не денемся, и, по-прежнему, отсутствие актуального статуса подписки где-либо, кроме как у клиента.


    Подход — All in API



    Проблему с актуализацией статуса решает подход “All in API”, который подразумевает, что всю бизнес-логику, верификации подписок и расчет expiration date мы отдаем на сторону API.


    В этом подходе у нас появляется серьезное API, которое, помимо метода верификации Receipt, предполагает регистрацию и авторизацию пользователей, хранит Receipt, Expiration date с привязкой к пользователю, и также содержит механизм polling’а.



    Это первый подход где отличаются действия для инициации подписки и для её продления. Начнем мы с того, как пользователь первый раз становится нашим подписчиком, что происходит. Приходит Receipt, мы этот Receipt отправляем на наше API, где он связывается с пользователем. Далее по схеме Receipt валидируется, возвращается обратно и на сервере уже мы сохраняем дату окончания подписки. Соответственно наше приложение ориентируется на данные от API по тому, когда, до какого числа предоставить контент или действие сервиса. При продлении вступает в действие механизм polling’а. Механизм polling подразумевает, что наше API время от времени, в какой-то определенный заданный промежуток времени берет Receipt, который был связан с пользователем, и отправляет его в Apple. В ответ на запрос верификации сервер Apple всегда возвращает Receipt последней версии. Если вы в базе сохранили любой Receipt пользователя, вы можете его в дальнейшем использовать, чтобы постоянно актуализировать статус подписки этого пользователя.


    Плюсы и минусы подхода



    В первую очередь актуализация статуса на сервере дает возможность предоставить вашу iOS-подписку для использования на других платформах. Если пользователь имеет какие-то другие устройства, он сможет ей пользоваться. Минус — с ростом количества пользователей функция polling становится высоконагруженной, потому что серверу необходимо постоянно гонять Receipt-ы для их обновления. У этого минуса есть решение, об этом я расскажу далее. Плюсы: надежная защита, актуальный статус подписки на сервере, доступность на другой платформе и удаленная поддержка пользователей. Когда все данные о подписке у вас хранятся на сервере, это значит, что вы в любой момент, если что-то пошло не так, была какая-то авария, вы можете залезть в базу и подкрутить для пользователя это значение, и, возможно, он даже не заметит, что была какая-то авария.


    Проблема, растущей нагрузки на сервер при polling решаема за счет применения так называемого механизма server-to-server notifications. Суть этого механизма в том, что сервер Apple уведомляет наш сервер об изменениях статуса подписки. Мы больше не ходим за ним сами, мы получаем всё в качестве уведомлений.


    Notifications



    Notifications работает по принципу callback-запроса на тот URL, который вы заводите в iTunes. Ваш сервер, на который будет происходить обращение этого метода, должен соответствовать требованиям App Transport Security. Важный момент здесь в том, что для этого вам понадобится SSL-сертификат, который выдан одним из центров сертификации, которым доверяет Apple. У них в документации есть список этих центров. Это выливается лишь в финансовые траты, потому что бесплатных SSL сертификатов этих компаний не выдают. Вам придется оплатить за этот сертификат. Notifications защищены от подделки с помощью Shared Secret, который у вас задан в iTunes и который приходит в поле «password». Сравнивая их, вы можете убедиться, что это не поддельный Notification. С 2019 года внутри Notification есть поле «unified_receipt», которое содержит все данные о подписках и всё содержимое Receipt. На самом деле внутри Notification очень много данных. С 2019 года там решили навести порядок и всё необходимое упаковали в unified_receipt. Notifications привязывается к пользователю по значению original_transaction_id, который вы можете найти в массиве активных подписок.



    В Notifications самым неоднозначным моментом и вызывающим затруднение является то, как трактовать их события. Всего существует 9 событий, 9 типов нотификаций. В документации они описаны не очень хорошо, я даже встречал ошибки их трактовки в сторонних библиотеках, которые выложены на GitHub и имеют кучу звезд. Первый — это INITIAL_BUY, он срабатывает только один раз, когда пользователь становится вашим подписчиком. Далее это DID_FAIL_TO_RENEW — возникает в тот момент, когда у пользователя возникает проблема при оплате, именно следующего периода. После того, как проблема устраняется, вызывается метод DID_RECOVER, который сигнализирует о том, что пользователь изменил данные своей карточки и оплата успешно прошла. Раньше он назывался RENEWAL, но чтобы не путать их, его переименовали в DID_RECOVER, они до сих пор приходят оба, и важно помнить, что RENEWAL — это на самом DID_RECOVER. По информации, полученной на WWDC 19, это будет продолжаться до середины 2020 года, затем RENEWAL перестанет приходить вообще. Следующее уведомление CANCEL происходит в двух случаях: когда пользователь вообще отменил продление вашей подписки, либо когда он вернул деньги за подписку, воспользовавшись сервисом Apple Care.



    Следующий набор также требует комментариев, он ещё более неоднозначный. Начнем с DID_CHANGE_RENEWAL_STATUS. Это сообщение приходит в том случае, когда пользователь апгрейдит подписку на более дорогой тариф. Он означает, что по текущему тарифу отменяется продление, и после него сразу приходит следующий Notification, который информирует уже о новой подписке. При даунгрейде подписки приходит другой Notification — DID_CHANGE_RENEWAL_PREF, который означает, что текущий тариф остался таким же, но для следующего периода обновления тариф поменялся. INTERACTIVE_RENEWAL приходит всегда не один и означает то, что те изменения, которые произошли в тарифе или в подписке, были инициализированы пользователем в реальном времени, то есть он зашел в iTunes и руками поменял настройки. Если вы видите такой Notification, то вам нужно как можно быстрее обновить статус, потому что пользователь наверняка этого ждет, глядя на контент внутри своего приложения. Последний, добавленный в 2019 году, PRICE_INCREASE_CONSENT, приходит в том случае, если пользователю было предложено повышение цены текущего тарифа.



    Здесь не хватает самого важного — Notification о том, что у пользователя была успешная оплата продления и подписка продлилась. Вместо этого рекомендуется использовать polling. Когда на WWDC инженерам Apple задали вопрос, почему нет этого события, они объяснили это следующим образом. Событие об успешной оплате продления — самое ценное событие во всей системе автопродлеваемых подписок. При этом сервис Notification не дает 100% гарантии доставки всех событий. Он делает 4 попытки обращения на ваш сервер для передачи события, если ни одна попытка не завершилась успехом событие будет потерянно. Поэтому для надежной проверки успешного продления рекомендуется использовать polling. Для того чтобы не сильно нагружать сервер polling запросами его можно сделать более умным, мы например делаем его регулярный вызов только для пользователей, у которых в скором времени ожидается продление. За счет этого значительно снижается нагрузку на сервер, чем если бы мы запрашивали обновление постоянно для всех пользователей в системе.



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


    Сравним?


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



    Я выбрал для сравнения несколько категорий: cкорость запуска, сложность кодирования, необходимость защиты и набор возможностей.
    Первый подход у нас самый быстрый в плане запуска, потому что, во-первых, мы всё делаем на клиенте, во-вторых, мы можем использовать готовые решения. Это не очень хорошо в плане защиты, но мы можем этим воспользоваться, если нам нужно быстро запустить проект, проверить какие-то гипотезы. Сложность достаточно высокая за счет того, что придется взаимодействовать с низкоуровневым API OpenSSL, и если вы собираетесь реализовывать защиту, вам придется поработать с низкоуровневым кодом. Возможности тут минимальные, потому что никто, кроме нашего клиента, не знает, какой у него на данный момент статус подписки.


    Следующий подход, который предполагает валидацию на сервере Apple, также запускается достаточно быстро, при этом у него очень простой код. Мы пишем простой код и на сервере, и на клиенте. Защиту мы имеем более надежную, за счет выполнения верификации Receipt на стороне сервера Apple. Возможности по-прежнему минимальны, так как никто не знает о статусе подписки, кроме самого клиента.


    Третий подход уже более медленный в запуске, потому что предполагает реализацию серьезного API с авторизацией и регистрацией. При этом он надежно защищен и у него уже больше возможностей в плане какого-то продвижения, учета статуса, маркетинговых подходов, призванных улучшить работу вашей подписки. Они могут быть реализованы на основе данных о подписке, которые теперь уже актуализированы на сервере.


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


    Работа над ошибками


    Теперь когда мы разобрались с тем, как разработать систему автопродлеваемой подписки, имеет смысл поговорить об ошибках.


    У нас в системе много компонентов, в них будут ошибки, и цена ошибки очень велика. То, как вы обрабатываете свои ошибки, то, как вы их собираете, очень сильно влияет на надежность и стабильность системы. Причём не просто на её работу в штатном режиме, но и на то, как она будет работать, когда что-то пойдет не так, когда случится авария.


    Приведу пример. Один наш пользователь, не получив подписку вовремя, написал нам гневное письмо на 6 листов A4, где он в нелитературной форме составил отзыв о нашей работе. Было не очень приятно.



    Как только начали заниматься подписками, мы с командой стали искать подход, который бы систематизировал работу с ошибками так, чтобы мы могли обработать любые ошибки, даже те, которых мы не ожидаем или те с которыми мы не знаем что делать. В итоге был выработан следующий подход. Во-первых, мы решили, что будем собирать все ошибки в одном месте и делать мы это будем на клиенте, т.е. в самом приложении. Потому что приложение — это самый удобный и быстрый способ коммуникации с пользователем, мы можем взаимодействовать с ним не отходя от кассы и информировать его о состоянии дел в системе. Также приложение — единственное место, где можно закрыть платежную транзакцию. Закрытие платежной транзакции означает, что вы выполнили все свои обязательства перед клиентом, взяли с него деньги и соответственно предоставили ему продукт. После того как мы собрали все ошибки в одном месте, мы по определенным критериям разбиваем их на три группы, и для каждой из этих групп мы реализуем стратегию их обработки.


    Критерии группировки ошибок


    Критериев всего три. Первый — это факт того, была ли там выполнена оплата. Этот критерий основан на статусе транзакции, который учитывается, если она побывала в статусе purchase, так, мы считаем, что с пользователя снимали деньги. Второй — это факт продления подписки, который учитывается на нашем сервере сменой даты окончания оплаченного периода. И, третий, факт закрытия транзакции определяемый на основе ее последнего известно статуса имеющего значение finished.


    Группы ошибок


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


    Стратегия обработки


    Для каждой из этих групп предусмотрена своя стратегия. В первой мы предупреждаем пользователя, показываем информационное сообщение. Во второй мы пытаемся повторить попытку валидации и получить expiration date — тут есть важный момент, мы ограничиваем количество попыток устранить ошибку. В первую очередь это связано с тем, что наличие открытой транзакции по какому-то продукту, особенно в подписках, блокирует его дальнейшую покупку из клиента. Если у вас пользователь в клиенте хочет купить подписку на неделю, что-то пошло не так, транзакция осталась открытой, он больше не сможет её купить, пока не будет закрыта транзакция, это ограничение StoreKit. Поэтому важно транзакции не хранить и не превращать их в бесконечно существующий массив проблем, их нужно закрывать. Когда мы закрываем такую повисшую транзакцию, ошибка попадает в третью группу. Третья группа предполагает создание автоматически тикета в службу поддержки. Мы пытаемся минимизировать проблемы и уведомляем техподдержку о том, что у кого-то были потрачены деньги, и он не получил продукт, после техподдержка связывается с этим пользователем. Мы не ждем, пока пользователь напишет нам в рейтинге или пришлет нам очередное письмо на 6 страниц, мы сами ему об этом говорим. Но перед тем, как это сделать, происходит некий анализ ошибки, мы поднимаем логи, смотрим, что там пошло не так, и когда очевидно, что проблема была на нашей стороне, начисляем ему на сервере продукт руками. В Таком случае пользователь даже не узнает о том, что у него были какие-то серьезные проблемы с продлением. Он просто получит свой продукт, чуть позже. О каждой ошибке в этой группе отправляется аварийный сигнал в мониторинг, который сразу сигнализирует всем разработчикам, что в системе произошло что-то нехорошее.


    Грязные хаки


    Давайте посмотрим на то, как некоторые разработчики пытаются прокачать свои подписки не совсем легальными способами. Самые распространенные способы, как обманывают пользователей и пытаются завладеть их деньгами. Во-первых, это отсутствие кнопки «Закрыть витрину», либо её присутствие, но с использованием dark pattern дизайна, который делает её плохо заметной. Это самый распространенный хак, и он сильно увеличивает конверсию пользователей в подписчики, когда пользователь просто не знает, куда ему уйти с витрины подписки и интуитивно выбирает одну единственную кнопку «Подписаться». Следующий — это контент низкого качества или его отсутствие. Это уже на грани с мошенничеством, но тем не менее такие примеры есть, и я вам их покажу. После, это авто-инициация покупки, когда пользователю предлагается покупка без нажатия на кнопку «Купить». И последний — это отсутствие ссылки для отписки от подписки. Сейчас стало обязательным требованием, чтобы на подписочной витрине была ссылка на настройки, где от этой подписки можно отказаться, однако существуют способы, как это сделать так, чтобы формально эта ссылка присутствовала, но найти её было непросто.


    Примеры


    Давайте посмотрим конкретные примеры. Первый, мой самый любимый, это приложение «Weather Alarms», которое даже засветилось на стене WWDC.



    Оно использует сразу 3 хака. Первый — это отсутствие кнопки «Закрыть», которая появляется потом и затемняется так, что её практически не видно. Во-вторых, это условия подписки, скрытые в UIScrollView так, что вы не видите ссылку для перехода в настройки для отписки. И третье — это достаточно посредственной контент, где вы за 80$ получайте банальный прогноз погоды, который у вас и так бесплатно доступен на телефоне. На видео можно видеть поведение кнопки «Закрыть» в верхнем углу, она появляется на какой-то момент, потом она становится серой и практически не видна.


    Следующее приложение — это «Translate Assistant», которое косит под «Google Translate».



    Оно также использует хак с отсутствием кнопки «Закрыть», вместо этого у него серая надпись, которая не сразу заметна. У него точно так же скрыты условия и ссылка на отписку. Также это приложение достаточно агрессивно себя ведет, оно показывает витрину подписки каждый раз, когда возвращается из бэкграунда.


    Дальше — круче. «QR Code Reader» — приложение, которое использует автоматическую инициацию покупки, и без нажатия кнопки «Купить» вам сразу предлагают поднести свой палец и, соответственно, оплатить подписку.


    В докладе это демонстрируется с 37:36:



    И это ещё не предел. Приложение «Fitness balance», кстати, от отечественного разработчика, превзошло вообще все возможные грани хаков. Это приложение предлагает вам узнать количество калорий по отпечатку пальца.


    Это с 37:58:



    Вам демонстрируют наглядную инструкцию о том, что вы должны сделать, после этого инициируется покупка, которая одобряется отпечатком вашего пальца и за 99$ вы получаете “ничего”. Усугубляет положение что здесь нет подписки, здесь обычный consumable In-App, который вы можете хоть 100 раз за день купить.


    Все эти приложения попали в разгромную статью, которая вышла в 2018 году на TechCrunch. Было много шума, потому что, по информации в этой статье, практически каждое из этих приложений получило прибыль превышающую один миллион долларов. О том, смогли ли разработчики забрать деньги, история умалчивает, однако Apple обратил внимание на эти приложения, и все они впоследствии были удалены. Сейчас не нужно пытаться сделать что-то подобное, потому что, во-первых, вас проверят, если вас не проверят на review, и найдут это уже в опубликованном приложении, его выбросят из AppStore. У моего знакомого была подобная ситуация, из-за ошибки после отмены покупки инициировалась вторая попытка, т.е. происходила посути автоинициация покупки. Кто-то из пользователей на это пожаловался и Apple заблокировали целиком весь developer аккаунт в тот момент, когда у проекта был разгар маркетинговой кампании, причем средства на эту кампанию были взяты в кредит. Это была очень неприятная ситуация, которая произошла именно из-за того, что Apple стал очень внимательно относиться к подобным вещам.
    Легальный Boost


    Поговорим о том, как прокачать свою подписку легально, не прибегая ни к каким хакам.


    Самый простой способ — это активировать так называемый Grace period.



    Grace period — это период, в течение которого продукт продолжает быть доступен для пользователя, даже если у него есть проблемы с биллингом и он не оплатил продление. Вот этот график показывали на WWDC и здесь видно, что за 7 дней активации Grace period до 16% подписчиков восстанавливают свои биллинговые данные и продолжают платить за подписку. Во время действия Grace period система iOS продолжает бомбить пользователя о том, что у него проблемы с биллингом, что ему нужно зайти в настройки и перепривязать свою карту. Это самый простой способ, достаточно просто его включить в iTunes и указать то количество дней, которое вы готовы дать пользователю бесплатно пользоваться вашим контентом, пока он налаживает биллинг.


    Следующий очень дешевый и быстрый способ — это выполнить promotion вашей подписки непосредственно в AppStore. У вас в AppStore появится такая иконка рядом с приложением и нажатие на кнопку предполагает, что пользователь и оформляет подписку, и загружает ваше приложение одновременно.


    Следующее — это целый раздел, Introductory Offers, которые были презентованы в 2018 году, предполагает несколько вариантов конструирования скидочных периодов на начальный этап подписки. Когда ваш пользователь в первый раз становится вашим подписчиком, в специальном конструкторе в iTunes вы можете придумать для него хитрые скидки. Но это работает только на начальный период.


    Если вы хотите взаимодействовать с пользователем уже после того, как он стал вашим подписчиком, хотите представить для него специальные условия или скидки, для этого есть так называемый Subscription Offers. С помощью этой функции можно сгенерировать до 10 различных скидок, и вы сами решаете на клиенте, когда эти скидки отобразить пользователю. Самый распространенный кейс — это, когда человек сделал даунгрейд тарифа или вообще отписался от вашей подписки. Вы можете сделать ему контр-оффер, предложить пользоваться услугой по сниженной цене.


    Также никто не запрещает вам использовать A/B тесты для того, чтобы выяснить, какие комбинации цены и периода подписки наиболее привлекательны в разных регионах для разных аудиторий пользователей.


    С iOS 13 появилась возможность на приложении с помощью StoreKit узнать о том, какой на данный момент актуальный регион для AppleId пользователя — вы можете взаимодействовать в разных регионах по-разному с пользователями.
    И последнее — это Subscription Multiple Apps, который предполагает, что подписку можно сделать сразу на несколько ваших приложений, одну и ту же подписку.


    Вопрос из зала: Как тестировать?


    Тестировать всё это непросто. Когда вы начнете реализовывать эту систему, подумайте сразу том, как сделать тестирование удобно, оставляйте специальные методы, особенно на сервере, которые помогут вам фильтровать notification. Самая сложная в плане тестирования штука — это оттестить notification-ы. Когда нужно изолировать какого-то одного пользователя, одну ситуацию и проанализировать, что там происходит, а на сервер, если у вас много пользователей, сыпется очень много notification-ов, в логах их не получится проанализировать. Для этого нужно писать специальный код, обвязку, которая ведет статистику событий.


    Касательно тестирования StoreKit, есть режим, в котором вы переключаете отправку Receipt на специальный URL сервера Apple, где он валидируется не в продакшене, а в тестовом окружении. Когда вы в режиме тестирования совершаете покупки, валидацию нужно проводить по этому специальному URL. Там кстати есть очень интересная штука. У этого тестового сервера похоже есть лимит на кол-во запросов, о котором не сказано ни в одной документации: если повесить автотесты на совершение каких-то покупок, то мы у себя в компании часто нарывались на этот лимит, и у нас просто сервер Apple на Sandbox переставал нам отвечать, а мы долго и мучительно пытались понять что мы делаем не так,


    Итоги


    Что касается реализации, оптимально начинать со второго подхода «App unlock, API verify», который использует верификацию на стороне Apple, но тем не менее предоставляет контент уже на стороне приложения. Это быстрый простой метод, который поможет вам начать разбираться в том, что происходит в системе, и перейти к более сложным вариантам.


    Обработка ошибок — это залог надежности системы. После того как вы озадачились реализацией, второе, что вам нужно сделать, продумать то, как вы будете работать с ошибками, как построить свою систему так, чтобы ни одна ошибка не утекла от вас и не превратилась в кошмар для пользователя.
    Развитие стека дает много возможностей. Если вы используете notification-ы, учитываете и складываете их в статистику, то это может быть очень полезно для вашего проекта, если с ними грамотно работать. Если у вас есть отдел маркетинга, расскажите ему об этом, ребята однозначно придумают, что делать с этими данными и какие предложения делать на основе поведения вашей аудитории. Notifications имеет смысл использовать не только ради каких-то маркетинговых исследований, но и для снижения нагрузки на сервер, это достаточно эффективный механизм. И последнее: не нужно использовать хаки, есть достаточное количество реальных способов прокачать свою подписку, пользуйтесь ими.




    Денис рекомендует несколько ссылок на самые полезные доклады с WWDC, касающиеся этой темы:


    Auto-renewable Subscriptions


    Engineering Subscriptions


    Advanced StoreKit


    In-App Purchases and Using Server-to-Server Notifications


    Best Practices and What’s New with In-App Purchases


    Второй снизу посвящён server-to-server notification. В нём подробно разобран каждый notification, его тип, то, какие поля меняются при этом notification, то, на что следует обратить внимание. Если собираетесь делать Notification, обязательно посмотрите этот доклад.




    В этом году все по-новому. Конференция Mobius пройдет с 22 по 25 июня в совершенно новом онлайн-формате! Узнай подробности тут.
    JUG Ru Group
    Конференции для программистов и сочувствующих. 18+

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

      0
      У этого тестового сервера похоже есть лимит на кол-во запросов, о котором не сказано ни в одной документации: если повесить автотесты на совершение каких-то покупок, то мы у себя в компании часто нарывались на этот лимит, и у нас просто сервер Apple на Sandbox переставал нам отвечать, а мы долго и мучительно пытались понять что мы делаем не так.

      Testing auto-renewable subscriptions

      Duration times are shortened when test your auto-renewable subscriptions. Additionally, test subscriptions only auto-renew a maximum of six times.
      help.apple.com/app-store-connect/#/dev7e89e149d
        0
        Во время действия Grace period система iOS продолжает бомбить пользователя о том, что у него проблемы с биллингом, что ему нужно зайти в настройки и перепривязать свою карту. Это самый простой способ, достаточно просто его включить в iTunes и указать то количество дней, которое вы готовы дать пользователю бесплатно пользоваться вашим контентом, пока он налаживает биллинг.

        Кол-во дней зависит от срока подписки, указать их нельзя, можно только включить/выключить.

        The length of the grace period depends on the subscription duration.
        help.apple.com/app-store-connect/#/dev58bda3212

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

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