aisplice

Build it into your app

Google Play subscriptions

For Android apps that sell subscriptions through Google Play and have no backend of their own. Your app sends the purchase token to the gateway with an API key; the gateway asks Google with your service account and answers whether the subscription is active. The check is free.

#Before you start

  • A Play Console login that is the account owner or an admin, so it can invite users.
  • A Google account that can create a project in Google Cloud console (any Google account can).
  • Your app’s package name (applicationId), for example com.example.app, and at least one subscription product created in Play Console.
  • About 15 minutes of clicking, then up to a day for Google to switch the permission on.

#1. Get the service account JSON

The JSON file is the service account’s key. It lets the gateway ask Google about your app’s purchases and nothing else. Google calls it a service account key; it is not the OAuth client file (client_secret_….json).

  1. 1

    Open Google Cloud console and pick a project

    console.cloud.google.com

    Use the project selector at the top. Any project works; creating a new one just for Play checks keeps things tidy.

  2. 2

    Turn on the Play API

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

    Search for the name exactly. If the button says Manage, it is already on.

  3. 3

    Create the service account

    IAM & Admin → Service accounts → Create service account

    Give it any name, such as play-verify. Skip the optional steps that grant project roles or user access: it needs no Cloud role. Click Done.

  4. 4

    Download its JSON key

    Open the service account → Keys → Add key → Create new key → JSON → Create

    The browser downloads a .json file. Keep it safe: Google never shows it again, and anyone with it can read your app’s purchases.

  5. 5

    Copy the service account email

    It looks like play-verify@your-project.iam.gserviceaccount.com and is shown on the service account page and inside the file as client_email. You need it in the next step.

Create new key is greyed out or refused?

Your Google Cloud organisation blocks key downloads with the policy iam.disableServiceAccountKeyCreation (on by default for new organisations). An organisation admin can turn it off for this project under IAM & Admin → Organization policies, or you can use a project outside the organisation.

#2. Give it access in Play Console

  1. 1

    Invite the service account

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

    Paste the service account email as the email address. A service account does not need to accept the invite.

  2. 2

    Grant these two permissions

    App permissions → Add app → choose your app

    Tick View financial data, orders, and cancellation survey responses, and Manage orders and subscriptions. Leave everything else off. Ticking the same two under Account permissions instead covers all your apps.

  3. 3

    Send the invite

    Apply → Invite user

    The service account now appears in Users and permissions as Active.

Why these permissions

The gateway only reads a subscription with Google’s purchases.subscriptionsv2.get. Google requires the financial-data and orders permissions for that read. The gateway never acknowledges, refunds or cancels anything.

Google can take up to a day to switch it on

Until then checks answer google_play_permission_denied. Editing any subscription product in Play Console (change its description, save, change it back) often makes the new permission apply sooner.

#3. Put it on an API key

  1. 1

    Open the key your app uses

    Console → API keys → Edit

    Use a key just for the app (see below).

  2. 2

    Fill in Google Play subscription check

    Set up Google Play check

    Enter the package name exactly as in your app’s build.gradle (applicationId), choose the JSON file from step 1, and save.

  3. 3

    Read what the save tells you

    The gateway tries the file with Google before storing it. If Google still refuses, the dialog says what is missing, usually the API from step 1 or the permission from step 2. The file is stored encrypted and never shown again; upload it again to replace it.

Use a key just for the app

Google Play settings belong to one key. Create a separate key for the app, limit it to the models the app uses and give it a quota ceiling.

#4. Check from the app

Check right after a purchase, and again when the app opens with the token you saved. Do not block app start on the answer: show the last saved result and update it when the check returns. The same token is answered from a one-minute cache. Each key may check 120 times per minute by default; the key’s Google Play settings show its current limit, and support can raise it for one key. Calls over the limit answer 429 and are not logged, so keep the last saved answer.

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. Decide what to unlock

Check answerWhat the app doesTypical cause
active: true, in_trial: trueUnlock. Show “Trial ends” with expiry_time.Free trial
active: trueUnlock until expiry_time.Paid, including canceled subscriptions that have not run out yet
subscribed: true, active: falseLock and show a renew screen.Expired, paused, on hold
subscribed: falseLock and show the subscribe screen.Unknown token, pending payment
HTTP 502 / 503Keep the last answer you saved and retry later.Google or the service account is not answering

Save expiry_time with the answer. Between checks, treat the subscription as usable until that time, so a phone that is offline keeps working.

#6. Acknowledge every purchase

Unacknowledged purchases are refunded after 3 days

The gateway only reads the subscription; it never acknowledges it. Your app must call completePurchase (Flutter) or acknowledgePurchase (Play Billing). If the check returns acknowledged: false for a purchase older than a few minutes, the app has not done it yet.

#7. Protect the key in the app

A key inside an app can be read from captured traffic. Turn on request signing for that key so a copied key cannot be used on its own, obfuscate the build, and keep a quota ceiling on the key.

Signing covers every call made with the key

Once signing is on, /v1/google-play/verify needs X-Timestamp, X-Nonce and X-Signature like every other request with that key, or it answers 401 signature_required. Sign it with its own path: signedHeaders('POST', '/v1/google-play/verify'). Turn signing on only after the app version that signs is out.

Request signing for apps →

#8. Check what happened

Every check, including cached answers and failures, is listed in Console → Request logs as a free “Google Play check” on the key. Search by the full purchase token, its last characters, or the order ID (GPA.…) a user sends you from Google’s receipt email.

  • google_play_api_disabled: the Play API from step 1 is not enabled in the project the JSON file belongs to.
  • google_play_permission_denied: the invite from step 2 is missing, lacks the two permissions, is for another app, or is not active yet.
  • google_play_credentials_invalid: the JSON key was deleted or disabled in Google Cloud. Create a new key and upload it on the API key.
  • The save says the file is not a service account key: you picked the OAuth client file. Download the key from the service account’s Keys tab instead.
  • Every check says the purchase was not found: the package name on the key differs from the app’s applicationId, or the token comes from another app.
  • Test purchases by license testers answer test_purchase: true.

Every field and error code →

Still stuck?

Send us the request ID or the error message and we will look into it.