In this guide, we’ll walk you through how to verify a subscription on your server after an in-app purchase is completed in your Android app. We'll be using TypeScript on a Node.js server, but the logic can be adapted to any backend framework.
Photo by Austin Distel on Unsplash
Step 1: Setup Google Cloud Project & Play Console
First, create a Pub/Sub topic in your Google Cloud Project and register it in the Play Console to receive real-time notifications for subscription purchases or status changes. If you're unfamiliar with this process, refer to this article first.
Save the service account key in your project folder.
Step 2: Store Product Information
Ensure that all product IDs from the Play Store, along with their associated prices, are saved in your database. This will help in verifying and processing the subscription details accurately.
Step 3: Setup the APIs
Create two POST API endpoints:
/play_store/subscription/verify: For verifying the initial subscription purchase.
/play_store/subscription/notifications: To handle subscription status change notifications like renewals and cancellations from the Play Store.
Step 4: Install googleapis Package
npm install googleapis
Step 5: Logic to Verify First-Time Subscription
Ensure the request body contains the following parameters:
a. packageName: The application ID of your Android app.
b. subscriptionId: The ID of the susbcription (created in the play store) for which the payment was made.
c. purchaseToken: The token received after successful payment.
import { google } from "googleapis"; export class PaymentsController { /** * Verify play store in-app purchase subscription first purchase */ async playStoreSubscriptionVerify(req: any, res: any): Promise<void> { const {packageName, subscriptionId, purchaseToken} = req.body; const data = await this.verifyPurchaseToken(packageName, subscriptionId, purchaseToken); if(data) { // TODO: Handle the verification response res.send({message: "Subscription successful"}); } else { res.status(400).send({message: "Subscription failed"}); } } private async verifyPurchaseToken( packageName: string, subscriptionId: string, purchaseToken: string ) { const auth = new google.auth.GoogleAuth({ keyFile: "path/to/service/account/key/json", scopes: ["https://www.googleapis.com/auth/androidpublisher"], }); const androidPublisher = google.androidpublisher({ auth, version: "v3", params: { sandbox: true }, }); const res = await androidPublisher.purchases.subscriptions.get({ token: purchaseToken, packageName: packageName, subscriptionId: subscriptionId, }); return res.status === 200 ? res.data : undefined; } }
You will receive a response like this after verification.
{ startTimeMillis: '1715966748689', expiryTimeMillis: '1715968836900', autoRenewing: false, priceCurrencyCode: 'INR', priceAmountMicros: '490000000', countryCode: 'IN', developerPayload: '', cancelReason: 1, orderId: 'GPA.3340-0954-1746-39862..0’, purchaseType: 0, acknowledgementState: 1, kind: 'androidpublisher#subscriptionPurchase' }
Upon Receiving a Successful Response from Google Play:
1. Check for Duplicate Orders:
a. Verify if the order ID already exists in your database.
b. If it does, respond with an error message: "This subscription was already captured".
c. If not, proceed further.
2. Update User Subscription and save transaction details:
Update the user's subscription details with the start time and expiry time in your database.
Store userId, startTimeMillis, expiryTimeMillis, orderId, subscriptionId, amount, fees, etc., in your
transactions table.
*Note: The order ID will look like GPA.3340-0954-1746-39862..0 where the prefix (before ..) is constant for all renewals of this subscription. The suffix (after ..) represents the renewal number (0 means subscription start, 1 means first renewal, 2 means second renewal etc.).
Step 6: Handling Subscription Notifications
You will receive base64 encoded data from the Play Store.
1. Decode the Data
2.Verify the Purchase Token
3. Check for Existing Order ID
If the order ID already exists in the database, stop processing (this usually occurs for the first-time subscription purchase).
4. Process the Order ID
a. Split the orderId with “..” and append “..0” to the first segment to get the subscription_order_id (representing the first-time purchase of this subscription).
b. Find the user associated with this subscription_order_id in your database and update according to the notificationType. For example, if notificationType is 4 (subscription renewal), update the subscription expiry time with the new data.
The decoded data will look like this.
{ version: '1.0', packageName: 'com.example.demo', eventTimeMillis: '1715966749255', subscriptionNotification: { version: '1.0', notificationType: 4, purchaseToken: 'pnppabchkjpihkldbkkopjni.AO-J1Oy-P76JVQqc0N8w-sexdfDfbkNT5NAEdv2h', subscriptionId: 'subs_1' } }
async onPlayStoreSubscriptionNotification(req: any): Promise<void>{ const dataBuffer = Buffer.from(req.body.data, "base64"); const dataStr = dataBuffer.toString("utf-8"); const data = JSON.parse(dataJson); let notificationDesc: string; const notificationType: number = data["subscriptionNotification"]["notificationType"]; switch (notificationType) { case 1: notificationDesc = "Subscription was recovered."; break; case 2: notificationDesc = "Subscription was renewed."; const res = await this.verifyPurchaseToken( data["packageName"], data["subscriptionNotification"]["subscriptionId"], data["subscriptionNotification"]["purchaseToken"] ); // TODO: Handle the verification response break; case 3: notificationDesc = "Subscription was canceled."; break; case 4: notificationDesc = "Subscription was purchased."; break; case 5: notificationDesc = "Subscription on hold."; break; case 6: notificationDesc = "Subscription in grace period."; break; case 7: notificationDesc = "Subscription restarted."; break; case 8: notificationDesc = "Subscription price change confirmed."; break; case 9: notificationDesc = "Subscription deferred."; break; case 10: notificationDesc = "Subscription paused."; break; case 11: notificationDesc = "Subscription pause schedule changed."; break; case 12: notificationDesc = "Subscription revoked."; break; case 13: notificationDesc = "Subscription expired."; break; default: throw new Error( `Unknown notification type received: ${notificationType}` ); } }
Step 7: Create a Push Type Subscription
Create a push-type subscription for the Pub/Sub topic you created in Step 1, and set the notification endpoint to /play_store/subscription/notifications
This guide simplifies the process of verifying and handling Google Play Store subscriptions on your server. By following these steps, you can ensure that your users’ subscription statuses are accurately tracked and updated in real-time.