S Subster

Возврат в приложение

Вы настроите переход, которым человек после оплаты в браузере попадает в ваше приложение с уже открытым доступом.

Весь путь целиком

  1. Покупатель платит на веб-странице и попадает на экран после оплаты: кнопки «Открыть приложение», «Скачать приложение» и код доступа.
  2. За кнопкой «Открыть приложение» стоит ссылка возврата — адрес нашего домена возврата с одноразовым кодом перехода в конце: https://<ID проекта>.go.<наш домен>/handoff/<КОД>. Код живёт 72 часа; ту же ссылку мы отправляем покупателю письмом.
  3. Дальше всё решает устройство, на котором ссылку открыли.
Где открыли ссылкуЧто видит покупательЧто делает приложение
телефон, приложение стоитсистема открывает приложение: на iOS по Universal Links, на Android по App Linksдостаёт код из адреса и меняет его на идентификатор покупателя (guid) — шаг 3
телефон, приложения неткнопки экрана после оплаты ведут в магазин приложений; ссылка из письма открывает нашу страницу с кодом доступа и просьбой сначала поставить приложениеузнаёт покупателя после установки уже без ссылки — как именно
компьютеркод доступа на экране после оплаты и на нашей странице, куда ведёт ссылка, с просьбой открыть её на телефоненичего: человек откроет письмо на телефоне или введёт код в приложении

Уведомление на устройство после оплаты мы не шлём: о том, что доступ открыт, приложение узнаёт, когда спросит право доступа.

Перед началом

Шаг 1. Отдайте три значения о приложении

Кабинет: «Настройки проекта» → «Подключение приложения» → вкладка «Основное» → ветка «Если приложение уже установлено — открывать его сразу». Если в разделе выбран путь «Собственный сервер», ветка скрыта: откройте её ссылкой «Показать остальные настройки» под списком веток.

ПолеЧто вписатьГде взять
iOS — Team ID и Bundle ID одной строкойABCDE12345.com.example.appApple Developer → Membership и Bundle Identifier в Xcode
Android — имя пакетаcom.example.androidapplicationId модуля приложения
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 часа, и обмен гасит его для обеих ссылок. Куда его отдать, зависит от того, кто открывал экран с ценами:

Встроенные окна — openWebPaywallEmbedded и openQuizEmbedded — схему не используют: исход и закрытие приходят в колбэк показа без ссылки, и handleReturnURL им не нужен.

Результат этих методов платформы отдают по-разному:

Шаг 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-адреса.

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

Продажа кнопкой внутри приложения

Тому, кто уже поставил приложение, можно продавать в браузере: кнопка в приложении открывает ваш экран с ценами, и покупатель на нём уже узнан.

  1. Впишите адрес. Кабинет: «Настройки проекта» → «Подключение приложения» → вкладка «Основное», секция «Оплата из приложения», поле «Адрес вашей страницы оплаты». Нужен https-адрес опубликованного экрана с ценами.
  2. Получите ссылку. По нажатию кнопки ваш сервер заводит покупателя запросом POST /s2s/v1/users с ключом доступа — запрос и ответ. В ответе рядом с идентификатором покупателя приходит paywallUrl — ваш адрес с пропуском покупателя.
  3. Откройте ссылку как есть, ничего к ней не дописывая: нашей библиотекой, вызовом openWebPaywall, или своим встроенным браузером. Библиотека сама дождётся права доступа, но оплата тогда ляжет на её идентификатор устройства (currentGuid()), а не на идентификатор из ответа — по нему спрашивайте право и с сервера. Без библиотеки спросите право по идентификатору из ответа.

Ссылку не храните: пропуск в ней живёт сутки, так что запрашивайте новую на каждое нажатие. Если поле в кабинете пустое, вместо ссылки придёт null, а идентификатор — как обычно.

Библиотека умеет открыть экран и по его ID, без запроса к вашему серверу: Web2App.openWebPaywall(paywallId:) на iOS, Web2AppSdk.openWebPaywallById на Android. ID копируется в кабинете: «Воронки» → вкладка «Пейволлы» → ⋮ в строке экрана → «Скопировать ID». Сработает это, только если домен привязан к самому экрану с ценами: экран внутри привязанной воронки по ID не найдётся.

Дальше