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

Подписки Google Play

Для Android-приложений, которые продают подписки через Google Play и не имеют собственного бэкенда. Приложение отправляет токен покупки на шлюз вместе с API-ключом; шлюз обращается к Google от имени вашего сервисного аккаунта и сообщает, активна ли подписка. Проверка бесплатна.

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

  • Вход в Play Console с правами владельца аккаунта или администратора, чтобы можно было приглашать пользователей.
  • Аккаунт Google, который может создать проект в Google Cloud console (это может любой аккаунт Google).
  • Имя пакета вашего приложения (applicationId), например com.example.app, и хотя бы одна подписка, созданная в Play Console.
  • Около 15 минут на настройку, затем до суток, пока Google включит права.

#1. Получите JSON сервисного аккаунта

JSON-файл — это ключ сервисного аккаунта. С ним шлюз может запрашивать у Google данные о покупках в вашем приложении — и ничего больше. Google называет его ключом сервисного аккаунта (service account key); это не файл OAuth-клиента (client_secret_….json).

  1. 1

    Откройте Google Cloud console и выберите проект

    console.cloud.google.com

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

  2. 2

    Включите Play API

    APIs & Services → Library → Google Play Android Developer API → Enable

    Ищите точно по названию. Если на кнопке написано Manage, API уже включён.

  3. 3

    Создайте сервисный аккаунт

    IAM & Admin → Service accounts → Create service account

    Назовите его как угодно, например play-verify. Пропустите необязательные шаги с выдачей ролей в проекте или доступа пользователям: роли Cloud ему не нужны. Нажмите Done.

  4. 4

    Скачайте его JSON-ключ

    Откройте сервисный аккаунт → Keys → Add key → Create new key → JSON → Create

    Браузер скачает файл .json. Храните его надёжно: Google больше его не покажет, а любой, у кого он есть, сможет читать данные о покупках в вашем приложении.

  5. 5

    Скопируйте email сервисного аккаунта

    Он выглядит как play-verify@your-project.iam.gserviceaccount.com и указан на странице сервисного аккаунта, а в файле — в поле client_email. Он понадобится на следующем шаге.

Кнопка Create new key неактивна или выдаёт ошибку?

Ваша организация в Google Cloud запрещает скачивание ключей политикой iam.disableServiceAccountKeyCreation (по умолчанию включена для новых организаций). Администратор организации может отключить её для этого проекта в IAM & Admin → Organization policies, или используйте проект вне организации.

#2. Выдайте доступ в Play Console

  1. 1

    Пригласите сервисный аккаунт

    play.google.com/console → Users and permissions → Invite new users

    Вставьте email сервисного аккаунта в поле адреса. Сервисному аккаунту не нужно принимать приглашение.

  2. 2

    Выдайте эти два права

    App permissions → Add app → выберите своё приложение

    Отметьте View financial data, orders, and cancellation survey responses и Manage orders and subscriptions. Остальное не включайте. Если отметить эти же два пункта в Account permissions, доступ будет ко всем вашим приложениям.

  3. 3

    Отправьте приглашение

    Apply → Invite user

    Теперь сервисный аккаунт отображается в Users and permissions со статусом Active.

Зачем эти права

Шлюз только читает подписку через purchases.subscriptionsv2.get от Google. Для такого чтения Google требует права на финансовые данные и заказы. Шлюз никогда ничего не подтверждает, не возвращает и не отменяет.

Google может включать права до суток

До этого проверки возвращают google_play_permission_denied. Часто права начинают действовать быстрее, если отредактировать любую подписку в Play Console (изменить описание, сохранить, вернуть как было).

#3. Привяжите его к API-ключу

  1. 1

    Откройте ключ, который использует приложение

    Консоль → API-ключи → Изменить

    Используйте отдельный ключ для приложения (см. ниже).

  2. 2

    Заполните «Проверка подписок Google Play»

    Настройка проверки Google Play

    Введите имя пакета точно как в build.gradle приложения (applicationId), выберите JSON-файл из шага 1 и сохраните.

  3. 3

    Прочитайте сообщение после сохранения

    Перед сохранением шлюз проверяет файл в Google. Если Google всё ещё отказывает, в окне будет указано, чего не хватает, — обычно API из шага 1 или прав из шага 2. Файл хранится в зашифрованном виде и больше не показывается; чтобы заменить его, загрузите файл заново.

Отдельный ключ для приложения

Настройки Google Play привязаны к одному ключу. Создайте для приложения отдельный ключ, ограничьте его моделями, которые использует приложение, и задайте предел квоты.

#4. Проверка из приложения

Проверяйте сразу после покупки и снова при запуске приложения с сохранённым токеном. Не задерживайте запуск приложения ради ответа: показывайте последний сохранённый результат и обновляйте его, когда придёт ответ. Для одного и того же токена ответ берётся из кеша на одну минуту. По умолчанию каждый ключ может выполнять 120 проверок в минуту; текущий лимит указан в настройках Google Play ключа, и поддержка может повысить его для отдельного ключа. Вызовы сверх лимита получают 429 и не логируются, поэтому храните последний сохранённый ответ.

google_play.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:in_app_purchase/in_app_purchase.dart';

const apiKey = 'sk-YOUR_KEY';

Future<Map<String, dynamic>?> checkSubscription(String purchaseToken) async {
  final res = await http.post(
    Uri.parse('https://www.aisplice.store/v1/google-play/verify'),
    headers: {
      'Authorization': 'Bearer $apiKey',
      'Content-Type': 'application/json',
      // Request signing on for this key? Then this call must be signed too:
      // ...signedHeaders('POST', '/v1/google-play/verify'),
    },
    body: jsonEncode({'purchase_token': purchaseToken}),
  );
  if (res.statusCode != 200) return null; // keep the last saved answer
  return jsonDecode(res.body) as Map<String, dynamic>;
}

// In your purchaseStream listener:
Future<void> onPurchase(PurchaseDetails purchase) async {
  if (purchase.status != PurchaseStatus.purchased &&
      purchase.status != PurchaseStatus.restored) return;
  final token = purchase.verificationData.serverVerificationData;
  final status = await checkSubscription(token);
  if (status != null && status['active'] == true) {
    await saveEntitlement(token, status); // your local storage
    unlockPaidFeatures();
  }
  if (purchase.pendingCompletePurchase) {
    await InAppPurchase.instance.completePurchase(purchase); // acknowledges on Android
  }
}

#5. Решите, что открывать

Ответ проверкиЧто делает приложениеТипичная причина
active: true, in_trial: trueОткрыть доступ. Показать «Пробный период заканчивается» с expiry_time.Пробный период
active: trueОткрыть доступ до expiry_time.Оплачена, включая отменённые подписки, срок которых ещё не истёк
subscribed: true, active: falseЗакрыть доступ и показать экран продления.Истекла, приостановлена, на удержании
subscribed: falseЗакрыть доступ и показать экран оформления подписки.Неизвестный токен, ожидается оплата
HTTP 502 / 503Использовать последний сохранённый ответ и повторить позже.Google или сервисный аккаунт не отвечает

Сохраняйте expiry_time вместе с ответом. Между проверками считайте подписку действующей до этого времени, чтобы приложение работало и без сети.

#6. Подтверждайте каждую покупку

Неподтверждённые покупки возвращаются через 3 дня

Шлюз только читает подписку и никогда её не подтверждает. Приложение должно вызвать completePurchase (Flutter) или acknowledgePurchase (Play Billing). Если проверка возвращает acknowledged: false для покупки старше нескольких минут, приложение этого ещё не сделало.

#7. Защитите ключ в приложении

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

Подпись распространяется на все вызовы с этим ключом

После включения подписи /v1/google-play/verify, как и любой другой запрос с этим ключом, требует X-Timestamp, X-Nonce и X-Signature, иначе отвечает 401 signature_required. Подписывайте его с собственным путём: signedHeaders('POST', '/v1/google-play/verify'). Включайте подпись только после выхода версии приложения, которая умеет подписывать запросы.

Подпись запросов в приложениях →

#8. Посмотрите, что произошло

Каждая проверка, включая ответы из кеша и ошибки, отображается в разделе Консоль → Журнал запросов как бесплатная «Проверка Google Play» у этого ключа. Ищите по полному токену покупки, его последним символам или ID заказа (GPA.…), который пользователь пришлёт вам из письма-чека Google.

  • google_play_api_disabled: Play API из шага 1 не включён в проекте, к которому относится JSON-файл.
  • google_play_permission_denied: приглашения из шага 2 нет, в нём нет двух нужных прав, оно для другого приложения или ещё не вступило в силу.
  • google_play_credentials_invalid: JSON-ключ удалён или отключён в Google Cloud. Создайте новый ключ и загрузите его в настройках API-ключа.
  • При сохранении сообщается, что файл не является ключом сервисного аккаунта: вы выбрали файл OAuth-клиента. Скачайте ключ на вкладке Keys сервисного аккаунта.
  • Каждая проверка сообщает, что покупка не найдена: имя пакета в ключе отличается от applicationId приложения или токен получен из другого приложения.
  • Для тестовых покупок лицензированных тестировщиков возвращается test_purchase: true.

Все поля и коды ошибок →

Проблема не решена?

Пришлите нам ID запроса или текст ошибки, и мы разберёмся.