Возврат в приложение
Вы настроите переход, которым человек после оплаты в браузере попадает в ваше приложение с уже открытым доступом.
Весь путь целиком
- Покупатель платит на веб-странице и попадает на экран после оплаты: кнопки «Открыть приложение», «Скачать приложение» и код доступа.
- За кнопкой «Открыть приложение» стоит ссылка возврата — адрес нашего домена возврата с одноразовым кодом перехода в конце:
https://<ID проекта>.go.<наш домен>/handoff/<КОД>. Код живёт 72 часа; ту же ссылку мы отправляем покупателю письмом. - Дальше всё решает устройство, на котором ссылку открыли.
| Где открыли ссылку | Что видит покупатель | Что делает приложение |
|---|---|---|
| телефон, приложение стоит | система открывает приложение: на iOS по Universal Links, на Android по App Links | достаёт код из адреса и меняет его на идентификатор покупателя (guid) — шаг 3 |
| телефон, приложения нет | кнопки экрана после оплаты ведут в магазин приложений; ссылка из письма открывает нашу страницу с кодом доступа и просьбой сначала поставить приложение | узнаёт покупателя после установки уже без ссылки — как именно |
| компьютер | код доступа на экране после оплаты и на нашей странице, куда ведёт ссылка, с просьбой открыть её на телефоне | ничего: человек откроет письмо на телефоне или введёт код в приложении |
Уведомление на устройство после оплаты мы не шлём: о том, что доступ открыт, приложение узнаёт, когда спросит право доступа.
Перед началом
- Роль администратора проекта в кабинете. Нет её — три значения из шага 1 впишет владелец проекта, остальное делаете вы. Кто кого зовёт и какие бывают роли — «Проект и команда» и «Передача разработчику» на дорожке владельца.
- Наша библиотека для iOS или Android. Без неё переход тоже работает, но код вы меняете запросом сами.
- Адрес API и ID проекта. Оба значения лежат в кабинете: «Подключение приложения» → «Для разработчика».
Шаг 1. Отдайте три значения о приложении
Кабинет: «Настройки проекта» → «Подключение приложения» → вкладка «Основное» → ветка «Если приложение уже установлено — открывать его сразу». Если в разделе выбран путь «Собственный сервер», ветка скрыта: откройте её ссылкой «Показать остальные настройки» под списком веток.
| Поле | Что вписать | Где взять |
|---|---|---|
| iOS — Team ID и Bundle ID одной строкой | ABCDE12345.com.example.app | Apple Developer → Membership и Bundle Identifier в Xcode |
| Android — имя пакета | com.example.android | applicationId модуля приложения |
| Android — отпечаток ключа подписи | AA:BB:CC:… | Play Console → «Целостность приложения» |
Отпечаток ключа подписи нужен от того ключа, которым Google Play подписывает приложение при публикации, а не от ключа загрузки. Их путают чаще всего, и App Links после такой ошибки перестают работать без единого сообщения.
Поля пустые — переход всё равно состоится, но окольным путём: покупателя узнают по отпечатку устройства — признакам телефона вроде экрана, часового пояса и языка — или по коду из письма, то есть с лишними шагами для него.
Файлы ассоциации apple-app-site-association и assetlinks.json мы собираем и отдаём на домене возврата сами, сразу после сохранения значений. Выкладывать их у себя не нужно.
Шаг 2. Привяжите домен возврата к сборке
Домен возврата один на проект, и в кабинете его не показывают. Возьмите его из ссылки возврата — это часть между https:// и /handoff/ у кнопки «Открыть приложение» на экране после оплаты и в письме. Ссылку даст и прогон тестовой картой — «Диагностика», раздел «Проверить, не дожидаясь настоящей оплаты».
На iOS добавьте его в Associated Domains: applinks:<ID проекта>.go.<наш домен>.
На Android объявите тот же адрес в манифесте:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="<ID проекта>.go.<наш домен>"
android:pathPrefix="/handoff/" />
</intent-filter>
Файлы ассоциации видны из браузера: откройте https://<домен возврата>/.well-known/assetlinks.json — в ответе будут имя вашего пакета и отпечаток ключа подписи. Так видно, что значения из шага 1 доехали, ещё до сборки.
Шаг 3. Разберите ссылку в приложении
Код лежит последним куском пути. Отдайте его библиотеке — она обменяет код на идентификатор покупателя и сохранит его у себя.
// iOS — from scene(_:continue:) or .onContinueUserActivity
// url.pathComponents = ["/", "handoff", "<CODE>"]
guard url.pathComponents.count >= 3, url.pathComponents[1] == "handoff" else { return }
Web2App.identify(deepLinkValue: url.pathComponents[2]) { result in
// .success(guid) / .failure
}
// Android — from onCreate and onNewIntent; same code, different method name
val segments = intent.data?.pathSegments.orEmpty() // ["handoff", "<CODE>"]
if (segments.size == 2 && segments[0] == "handoff") {
Web2AppSdk.identifyWithDeepLinkValue(segments[1]) { result -> }
}
Код гаснет при первом обмене. Повторный переход по той же ссылке на Android вернёт ошибку, хотя идентификатор сохранился ещё в первый раз; на iOS — успех с сохранённым идентификатором. Поэтому на ошибке сперва прочитайте право доступа и только на пустом ответе предлагайте письмо со ссылкой заново.
На iOS вызов меняет код, только пока библиотека не хранит идентификатор. Если она уже завела свой — это делают openWebPaywall и openQuizEmbedded, — вернётся сохранённый, а код останется непогашенным, хотя оплата по ссылке легла на другой идентификатор.
Проверить это можно заранее: Web2App.currentGuid() отдаёт хранимый идентификатор или nil, если его ещё нет. Заводит его библиотека при опознании либо при первом показе экрана с ценами или квиза — «SDK», шаг 3. При непустом значении меняйте код запросом и спрашивайте право по идентификатору из его ответа.
Идентификатор покупателя в открытом виде в ссылке не едет и параметром ?guid= не принимается: личность переносит только одноразовый код.
Шаг 4. Добавьте схему возврата
Схема — второй путь в приложение: по ней ведут кнопки «Открыть приложение (альтернативный способ)» и «Закрыть» на экране после оплаты. Заменить Universal Links она не может: ссылка из письма открывает приложение только по ним.
Готовую строку вида w2a-3f1e2d3c://handoff?code=<код> кабинет показывает в той же ветке. Имя схемы — это часть до ://; пропишите его одинаково в обеих сборках, потому что строку мы отдаём обеим одну. На iOS имя объявляется в CFBundleURLSchemes, на Android — отдельным intent-filter со схемой.
Показанная строка ещё не сохранена. Откройте «У приложения уже есть своя схема?», нажмите «Вернуть нашу» или впишите своё имя и сохраните настройки — иначе обеих кнопок на экране после оплаты не будет.
Код лежит в параметре code, и это тот же код перехода, что в ссылке возврата: живёт те же 72 часа, и обмен гасит его для обеих ссылок. Куда его отдать, зависит от того, кто открывал экран с ценами:
- Ваш вызов
openWebPaywall, и вы ждёте его результата. Отдайте адрес вWeb2App.handleReturnURL(_:)на iOS илиWeb2AppSdk.handleReturnUrl(url, onResult)на Android. Код перехода этот вызов не тратит: доступ приезжает по уже сохранённому идентификатору, а ссылка из письма остаётся рабочей. - Человек платил в обычном браузере. Отдайте код в вызов из шага 3, без библиотеки — в тот же запрос.
Встроенные окна — openWebPaywallEmbedded и openQuizEmbedded — схему не используют: исход и закрытие приходят в колбэк показа без ссылки, и handleReturnURL им не нужен.
Результат этих методов платформы отдают по-разному:
- Android. Право доступа приходит в
onResult: библиотека опрашивает его раз в секунду, до десяти раз. - iOS. Колбэка у метода нет. Он закрывает открытый экран с ценами, а результат приходит в completion того вызова
openWebPaywall, которым экран открывали: опрос идёт раз в две секунды, до тридцати раз.
Шаг 5. Проверьте, что приложение до нас дошло
С библиотекой отметка о первом открытии уходит сама, как только она узнаёт покупателя. Без библиотеки пошлите её после первого удачного обмена кода:
curl -X POST "https://api.subster.ai/public/handoff/app-callback" \
-H "Content-Type: application/json" \
-d '{"guid":"…","projectId":"…","device":"ios","event":"app_installed"}'
Ответ — 204 без тела, права доступа он не возвращает. Тело запроса нужно всегда: на пустом придёт 400, и установка не запишется. В поле device — ios или android. Отметок принимаем до 60 в минуту с одного IP-адреса.
По этой отметке кабинет на вкладке «Для разработчика» пишет «Приложение обращается к нам» и дату последней установки. Это самый быстрый способ убедиться, что переход собран верно.
Продажа кнопкой внутри приложения
Тому, кто уже поставил приложение, можно продавать в браузере: кнопка в приложении открывает ваш экран с ценами, и покупатель на нём уже узнан.
- Впишите адрес. Кабинет: «Настройки проекта» → «Подключение приложения» → вкладка «Основное», секция «Оплата из приложения», поле «Адрес вашей страницы оплаты». Нужен https-адрес опубликованного экрана с ценами.
- Получите ссылку. По нажатию кнопки ваш сервер заводит покупателя запросом
POST /s2s/v1/usersс ключом доступа — запрос и ответ. В ответе рядом с идентификатором покупателя приходитpaywallUrl— ваш адрес с пропуском покупателя. - Откройте ссылку как есть, ничего к ней не дописывая: нашей библиотекой, вызовом
openWebPaywall, или своим встроенным браузером. Библиотека сама дождётся права доступа, но оплата тогда ляжет на её идентификатор устройства (currentGuid()), а не на идентификатор из ответа — по нему спрашивайте право и с сервера. Без библиотеки спросите право по идентификатору из ответа.
Ссылку не храните: пропуск в ней живёт сутки, так что запрашивайте новую на каждое нажатие. Если поле в кабинете пустое, вместо ссылки придёт null, а идентификатор — как обычно.
Библиотека умеет открыть экран и по его ID, без запроса к вашему серверу: Web2App.openWebPaywall(paywallId:) на iOS, Web2AppSdk.openWebPaywallById на Android. ID копируется в кабинете: «Воронки» → вкладка «Пейволлы» → ⋮ в строке экрана → «Скопировать ID». Сработает это, только если домен привязан к самому экрану с ценами: экран внутри привязанной воронки по ID не найдётся.
Дальше
- Как мы узнаём покупателя — шесть путей, которыми оплата находит человека, включая установку приложения после оплаты.
- Проверить право доступа — что спросить по идентификатору покупателя и как разобрать ответ.
- Диагностика — что смотреть, когда покупатель оплатил, а доступа в приложении нет.