Steps to reproduce:
The notification I get for google play store is:
{
"version": "1.0",
"packageName": "package.name",
"eventTimeMillis": "111111111111",
"subscriptionNotification": {
"version": "1.0",
"notificationType": 4,
"purchaseToken": "purchase token",
"subscriptionId": "first_subscription"
}
}
When I call the subscription get api, I get this:
{
"startTimeMillis": "1635472371631",
"expiryTimeMillis": "1635472675112",
"autoRenewing": false,
"priceCurrencyCode": "EUR",
"priceAmountMicros": "4300000",
"countryCode": "IN",
"developerPayload": "",
"cancelReason": 1,
"orderId": "GPA.3388-8947-4636-69596",
"purchaseType": 0,
"acknowledgementState": 0,
"kind": "androidpublisher#subscriptionPurchase"
}
How am I supposed to identify the user if I neither have the obfuscatedExternalAccount id nor a linkedPurchaseToken to query?
EDIT:
Let me say this as plainly as I can:
Even though the button in Google Play says "Resubscribe", you see that your notification data says "notificationType": 4,
which is actually a PURCHASE for a brand new subscription. This is an "out-of-app" purchase scenario. The notification from Google is not going to tell you who purchased the app in this case.
I'm not happy about this either. Google is putting a lot on the developer to handle this use case.
Let's start with the normal use case: In-app purchase.
In my case, our app has a back end that handles authentication & authorization (we aren't using Firebase for authentication, which would probably simplify matters greatly if we did).
We have table that tracks the user subscription. It has the user id, the purchase token, and the expiration date (plus some other data). Purchase token is a unique key, this is important later on.
The first step is onboarding where the user signs up. After the user enters their credentials, the app sends a request to the server to create a new user identity.
The next step is subscription purchase. After the app has been notified of a purchase, it makes a request to the server to associate the purchase token with the newly created user. While that is happening, Google RTDN is making a request to our server for the purchase notification. And we don't know in which order these requests will occur (race condition).
(Could we use obfuscatedAccountId
to get the user id? I suppose, but that's still not going to help us with out-of-app purchases problem. Plus, the request from the app is what tells the server to send the request to Google to acknowledge the subscription purchase.)
The request from the app has the user identity. The request from RTDN has other data such as expiration date. Both requests have the purchase token.
Our system uses a INSERT ... ON DUPLICATE UPDATE
so that the first request will create a record with the purchase token key and the second request will update the already-created record. (The purchase token unique key makes that possible.)
By using "insert-or-update", the system doesn't have to care which order these requests come in. Once both requests have been processed, we have a complete picture of the user license/subscription.
When the user signs in again, we can refer to this data which says the user has a license based on their Google Play subscription.
This works great... until it happens that the app doesn't send its request. Then we have a null user id — which means the system has an "orphan" subscription and we don't know who owns it.
Which brings us to: Out-of-app purchase.
In this case we get the RTDN request but not the app request. We have a user's identity already in the system, but it doesn't get associated with the purchase token.
There's only one way this situation can be handled: Every time the app starts up, it has to call queryPurchasesAsync()
on the Billing Client Library to check if there is a Google Play subscription with no matching user license.
If the app detects this, it sends the purchase request right away to update the subscription table and assign the user to "orphan" subscription.
Hopefully that explains the issue more clearly.
I highly recommend examining Google's play-billing-samples code in depth to see how they use the Billing Client Library and handle the different use cases.
We are in the process of implementing subscriptions for our app, and I just ran into this.
After looking through all the docs, I've come to the conclusion that Google expects your app to query the Billing Client library on app startup, and if queryPurchasesAsync()
returns a SKU that your license data service says is expired, your app will send a request to your service to update your licensing data accordingly.
queryPurchasesAsync | BillingClient | Android Developers
I would recommend that request handler do a sanity check by querying the Developer API for the purchase token before updating your license data.
Method: purchases.subscriptions.get | Google Play's billing system | Android Developers
This means that both Google Play and your service are acting as sources of truth, so your licensing schema and processes need to support this odd use case: creating a subscription and assigning it to a user later.
Also, we've seen that the Developer API will send renewal notifications without our server sending a subscription acknowledgement, so apparently you do not have to acknowledge the subscription purchase in this case.