How to verify purchase for android app in server side (google play in app billing v3) - android

I have a simple app (needs user login with account). I provide some premium features for paid users, like more news content.
I need to record if the user has bought this item in my server database. When I provide data content to user's device, I can then check the user's status, and provide different content for paid user.
I checked the official Trivialdrive sample provided by Google, it does not provide any sample code for server-side verification, here are my questions.
I found the sample use my app's public key inside to verify purchase, it looks not good, I think I can just move the verification process to my server combined with user login credentials to see whether the user purchase completed, and then update the database.
Also there is purchase API I can use to query, what I need is to pass the user's purchaseToken into server.
I am not sure what method I should take to verify the user's purchase, and mark the user's status in my database, maybe both?
And I am afraid there is a situation, if a user bought this item from google play, but for some reason, just in that time, when my app launched verification to my server, the network connection is down or my own server is down, user just paid the money in google play but I did not record the purchase in my server? What should I do, How can I deal with this situation.

It sounds what you're looking for is a way to check if the user has premium features enabled on their account, so this is where I would start;
Ensure there is a flag of some sort on your database indicating if the user has premium features and include that in the API response payload when requesting account info. This flag will be your primary authority for "premium features".
When a user makes an in-app purchase, cache the details (token, order id, and product id) locally on the client (i.e the app) then send it to your API.
Your API should then send the purchaseToken to the Google Play Developer API for validation.
A few things might happen from here:
The receipt is valid, your API responds to the client with a 200 Ok status code
The receipt is invalid, your API responds to the client with a 400 Bad Request status code
Google Play API is down, your API responds with a 502 Bad Gateway status code
In the case of 1. or 2. (2xx or 4xx status codes) your client clears the cache of purchase details because it doesn't need it anymore because the API has indicated that it has been received.
Upon a successful validation (case 1.), you should set the premium flag to true for the user.
In the case of 3. (5xx status code) or a network timeout the client should keep trying until it receives a 2xx or 4xx status code from your API.
Depending on your requirements, you could make it wait a few seconds before sending again or just send the details to your API when ever the app is launched again or comes out of background if the purchase details are present on the app cache.
This approach should take care of network timeouts, servers being unavailable, etc.
There are now a few questions you need to consider:
What should happen immediately after a purchase? Should the app wait until validation is successful before providing premium content or should it tentatively grant access and take it away if the validation fails?
Granting tentative access to premium features smooths the process for a majority of your users, but you will be granting access to a number of fraudulent users too while your API validates the purchaseToken.
To put this in another way: Purchase is valid until proven fraudulent or; fraudulent until proven valid?
In order to identify if the user still has a valid subscription when their subscription period comes up for renewal, you will need to schedule a re-validation on the purchaseToken to run at the expiryTimeMillis that was returned in the result.
If the expiryTimeMillis is in the past, you can set the premium flag to false. If it's in the future, re-schedule it again for the new expiryTimeMillis.
Lastly, to ensure the user has premium access (or not), your app should query your API for the users details on app launch or when it comes out of background.

The documentation on this is confusing and weirdly verbose with the things that are almost inconsequential while leaving the actually important documentation almost unlinked and super hard to find. This should work great on most popular server platform that can run the google api client libraries, including Java, Python, .Net, and NodeJS, among others. Note: I've tested only the Python api client as shown below.
Necessary steps:
Make an API project, from the API Access link in your Google Play console
Make a new service account, save the JSON private key that gets generated. You'll need to take this file to your server.
Press Done in the Play console's service account section to refresh and then grant access to the service account
Go get a google api client library for your server platform from https://developers.google.com/api-client-library
Use your particular platform's client library to build a service interface and directly read the result of your purchase verification.
You do not need to bother with authorization scopes, making custom requests calls, refreshing access tokens, etc. the api client library takes care of everything. Here's a python library usage example to verify a subscription:
First, install the google api client in your pipenv like this:
$ pipenv install google-api-python-client
Then you can set up api client credentials using the private key json file for authenticating the service account.
credentials = service_account.Credentials.from_service_account_file("service_account.json")
Now you can verify subscription purchases or product purchases using the library, directly.
#Build the "service" interface to the API you want
service = googleapiclient.discovery.build("androidpublisher", "v3", credentials=credentials)
#Use the token your API got from the app to verify the purchase
result = service.purchases().subscriptions().get(packageName="your.app.package.id", subscriptionId="sku.name", token="token-from-app").execute()
#result is a python object that looks like this ->
# {'kind': 'androidpublisher#subscriptionPurchase', 'startTimeMillis': '1534326259450', 'expiryTimeMillis': '1534328356187', 'autoRenewing': False, 'priceCurrencyCode': 'INR', 'priceAmountMicros': '70000000', 'countryCode': 'IN', 'developerPayload': '', 'cancelReason': 1, 'orderId': 'GPA.1234-4567-1234-1234..5', 'purchaseType': 0}
The documentation for the platform service interface for the play developer API is not linked in an easy to find way, for some it is downright hard to find. Here are the links for the popular platforms that I found:
Python | Java | .NET | PHP | NodeJS (Github TS) | Go (Github JSON)

Complete example of using Google API Client Library for PHP:
Setup your Google Project and access to Google Play for your service account as described in Marc's answer here https://stackoverflow.com/a/35138885/1046909.
Install the library: https://developers.google.com/api-client-library/php/start/installation.
Now you are able to verify your receipt the following way:
$client = new \Google_Client();
$client->setAuthConfig('/path/to/service/account/credentials.json');
$client->addScope('https://www.googleapis.com/auth/androidpublisher');
$service = new \Google_Service_AndroidPublisher($client);
$purchase = $service->purchases_subscriptions->get($packageName, $productId, $token);
After that $purchase is instance of Google_Service_AndroidPublisher_SubscriptionPurchase
$purchase->getAutoRenewing();
$purchase->getCancelReason();
...

You can try using Purchases.subscriptions: get server-side. It takes packageName, subscriptionId and token as paramaters and requires authorization.
Checks whether a user's subscription purchase is valid and returns its
expiry time.
If successful, this method returns a Purchases.subscriptions resource in the response body.

I answer to this concern
the network connection is down or my own server is down, user just
paid the money in google play but I did not record the purchase in my
server? What should I do, How can I deal with this situation.
The situation is:
User purchases 'abc' item using google play service -> return OK -> fail to verify with server for some reasons such as no Internet connection.
Solution is:
On the client side, before showing the 'Google Wallet' button, you check if the 'abc' item is already owned.
if yes, verify with server again
if no, show the 'Google Wallet' button.
Purchase purchase = mInventory.getPurchase('abc');
if (purchase != null) // Verify with server
else // show Google Wallet button
https://developer.android.com/google/play/billing/billing_reference.html#getSkuDetails

Marc Greenstock's answer is definitely enlightening, a few things to pay attention though which took me a long time to figure out (at least way more time than I expected):
I had to check "Enable G Suite Domain-wide Delegation" on Service Account settings. Without this I kept getting this error: "The current user has insufficient permissions to perform the requested operation"
Image with Enable G Suite Domain-wide Delegation option checked
For testing purposes you can create a JWT token for your service account here, just don't forget to select RS256 Algorithm.
The public key is the "private_key_id" from your downloaded JSON file. It also has the following format:
-----BEGIN PUBLIC KEY-----
{private_key_id}
-----END PUBLIC KEY-----
The private key is the "private_key" from your downloaded JSON file
The required claims for the JWT generation are described here.
Confused about what exactly a JWT Token is and how it is assembled? Don't be ashamed, check this link. Odds are you are just like me and took a long time to bother looking for what exactly it is, it is (way) simpler than it looks.

I had some serious problems using the suggested google API python library, but implementing the communication from scratch is not so hard.
First of all you have to create a service account at Google Play Console as described in all answers and get the JSON file containing the private key. Save it to your server.
Then use the following code. No need to obtain the google API client library. You only need the following (very common) python libraries Requests and Pycrypto
import requests
import datetime
import json
import base64
from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
jwtheader64 = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
#SERVICE_ACCOUNT_FILE: full path to the json key file obtained from google
with open(SERVICE_ACCOUNT_FILE) as json_file:
authinfo = json.load(json_file)
packageName = #your package name
product = #your inapp id
token = #your purchase token
#create the JWT to use for authentication
now = datetime.datetime.now()
now1970 = (now - datetime.datetime(1970,1,1)).total_seconds()
jwtclaim = {"iss":authinfo["client_email"],"scope":"https://www.googleapis.com/auth/androidpublisher","aud": "https://oauth2.googleapis.com/token","iat":now1970,"exp":now1970+1800,"sub":authinfo["client_email"]}
jwtclaimstring = json.dumps(jwtclaim).encode(encoding='UTF-8')
jwtclaim64 = base64.urlsafe_b64encode(jwtclaimstring).decode(encoding='UTF-8')
tosign = (jwtheader64+"."+jwtclaim64).encode(encoding='UTF-8')
#sign it with your private key
private = authinfo["private_key"].encode(encoding='UTF-8')
signingkey = RSA.importKey(private)
signer = Signature_pkcs1_v1_5.new(signingkey)
digest = SHA256.new()
digest.update(tosign)
signature = signer.sign(digest)
res = base64.urlsafe_b64encode(signature).decode(encoding='UTF-8')
#send it to Google authentication server to obtain your access token
headers = {'Content-Type': 'mapplication/x-www-form-urlencoded'}
payload = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion="+jwtheader64+"."+jwtclaim64+"."+res
r = requests.post("https://oauth2.googleapis.com/token",headers=headers,data=payload)
if r.status_code == 200:
authdata = json.loads(r.text)
accesstoken = authdata['access_token']
bearerheader = {'Authorization':'Bearer '+authdata['access_token']}
#Now you have at last your authentication token, so you can use it to make calls. In this example we want to verify a subscription
url = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/"+packageName+"/purchases/subscriptions/"+product+"/tokens/"+token
subscription = requests.get(url,headers=bearerheader)

the network connection is down or my own server is down,
You don't have to think like this.
Client knows own's consume product. so, client can send all tokens back to the server.
Just re-check token with produce id and transaction id.
And Server checks consume product.
if you fail check
make UI button client can re-send token.
server re-check token for items.
It's done.

Related

In-app purchase verification: error 401 Insufficient Permissions

Consider me a beginner in the dev world.
I am trying to clone my Android app client, but keeping the same server. So far so good, but when I reached the in-app purchases part it seems like my server just cannot verify the orders from the cloned new_app package - error '401 The current user has insufficient permissions to perform the requested operation.'
The order itself from the new app goes through. It is just that I verify if it is successful on server side before I give the item.
I am (I would like to) using the same webserver ClientID and Secret from both apps (why should I need separate ones?), and I suspect that corresponding access token generated which I pass in the curl URL is just not accepted for the new_app case:
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://www.googleapis.com/androidpublisher/v3/applications/com.new_app_package/purchases/products/' . $sku . '/tokens/' . $purchaseToken . '?access_token=' . $token, ....
Is there some strict linkage between the webserver client / service account and the app package which I miss? I've been struggling already weeks with this dilema with no results... Hints will be appreciated
As I side note: I have given Editor rights to the service account (GoogleApis IAM); also I have given the service account emails the 'Full' rights to the app in google play console (settings/apiaccess+userpermissions) - no results

Protecting my Google App Engine API Endpoints

I have been doing a lot of research recently on securing my app engine. Currently, I've been reading through the question below and the links in that question:
How do I restrict Google App Engine Endpoints API access to only my Android applications?
However, it doesn't answer my problem. My question is similar to the question above, restricting access to my endpoint API to only my app. The guy seemed to have got it working when he inputs a correct email into the credentials.
My question is if I can achieve the same results without having to input any credentials. I want it so that only my app can use my endpoint API so to prevent other apps from abusing it and using up my quota. I already got a client id for my android application, and have placed it within my #API annotation. To test if it worked, I made a random value for the client id in the #API notation of another api class. However, my app was still able to use methods from both class. Any help?
-Edit-
From reading from the docs and researching further, the endpoint way of authorizing apps is by authenticating the user and for my API to check if user is null. My question is that in the process of authenticating the user, is Google somehow able to read my app's SHA1 fingerprint and authorize it to its list of client ids? If so, how can I replicate this process in my endpoint so that I check the SHA1 fingerprint of the app making the request and compare it to a set value? I don't understand the mechanics behind the endpoints very well, so correct me if I am understanding this wrong.
If the android app has access, then the user has access. A motivated party has many options for inspecting your protocol, including putting the device behind transparent proxy or simply running the app through a debugger. I do suggest running your app through ProGuard before publishing, as this will make the process [a bit] more difficult.
Ultimately, you'll need to make your appengine API robust against untrusted parties. This is simply the state of the web.
How you can protect your endpoint API is described here: http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html
The secret is that you request a token from Google Play using the following scope: audience:server:client_id:9414861317621.apps.googleusercontent.com where 9414861317621.apps.googleusercontent.com is your ClientId.
Google Play will look up the id at your endpoints app and return a Google-signed JSON Web Token if it finds the id. Then you pass that id in with your request. Above article says you should pass it in with the body. I would possibly rather add another parameter for that because otherwise you can't pass your own entities anymore. Anyway, your server backend receives the token, and you ask Google as described if it is authentic, before you process the API request.
If you pass in the token using an extra parameter, you can catch it on the server side by adding HttpServletRequest to your endpoint signature and then using request.getHeader("Yourname") to read it out. Make sure you never add the parameter as a URL parameter as it may be logged somewhere.
public void endpointmethod(
// ... your own parameters here
final HttpServletRequest request
) throws ServiceException, OAuthRequestException {
request.getHeader("YourHeaderName") // read your header here, authenticate it with Google and raise OAuthRequestException if it can't be validated
On the Android side you can pass in your token when you build the endpoint api, like this, so you don't have to do it with each and every request:
Yourapiname.Builder builder = new Yourapiname.Builder(AndroidHttp.newCompatibleTransport(), getJsonFactory(), new HttpRequestInitializer() {
public void initialize(HttpRequest httpRequest) {
httpRequest.setHeader(...);
}})
Hope this helps you make your endpoints API secure. It should.

How do I restrict Google App Engine Endpoints API access to only my Android applications?

I am an Android developer building my first Google App Engine (java) back-end for my apps. I don't want anybody else to access this API other than my app. (I plan to use App engine for verifying InApp purchases in my Android app). My data is not relevant to users so,
I don't want users to be able to access my API even if they are logged in with their Google accounts (on web or Android devices).
I followed the steps mentioned in - "Specifying authorized clients in the API backend"
(https://developers.google.com/appengine/docs/java/endpoints/auth)
like generating client IDs and add them in #Api (clientIds and audiences)
except "Add a User parameter" - since I don't need user authentication.
Then I deployed App engine and I am still able to access the API through API explorer (https://your_app_id.appspot.com/_ah/api/explorer)
(I haven't added API_EXPLORER client ID)
I tested with the APK that was built with the endpoint libs before adding client IDs and can still access the API.
Is adding a "User parameter" to all endpoint APIs a must? to achieve my purpose (restrict API to only my Android apps).
Can I pass null as userAccount name from Android client and ignore user parameter value on server (since it will be null)? Will this ensure that the API is accessible only from my android apps (since the client ID is generated for my package name and SHA1 of the APK?)
Should I use something like a service account for this purpose?
The documentation says for Android, both Android and Web client IDs must be added and audience must be the same as web client ID. Does this open access to any other web client? can I skip mentioning web client ID and still achieve my purpose?
Appreciate your time and help.
...... updating with my further investigation ...
I did the following:
Added User parameter to APIs on backend - but did not check for null value. API can still be accessed without passing any credentials (from Android debug APK and API explorer)
Then, I tried
mCredential = GoogleAccountCredential.usingAudience(this, "server:client_id:" + WEB_CLIENT_ID);
mCredential.setSelectedAccountName(null);
and passed this credential to API builder (as suggested in some other posts)
Caused FATAL EXCEPTION. So, we can't pass null account name.
I could call the API using API explorer without OAuth. But when I enabled OAuth, it gave error saying this client ID is not allowed! ( I haven't yet added com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID in client_ids{})
Then I added code to throw OAuthRequestException on the backend if the user is null. This resulted in API explorer getting errors without OAuth. It works with OAuth enabled after adding API_EXPLORER_CLIENT_ID to client_ids)
Added code to pass valid user account name(email) from my Android app. Then, I am able to access API only with my release APK. Even the debug APK gets exceptions! - which is what I expected..So, I assume no other Android apps will be able to access this API.
So, not checking for null user on back-end API is a bad idea (as suggested in other posts). It is as good as not mentioning any client_ids and not having User param.
Only question I have at this moment is: If some one can figure out the WEB_CLIENT_ID from the APK, will they be able to use it to build a web client to access my API (I haven't mentioned client secret anywhere in the code. So I am thinking this is not possible).
I did search Google groups and Stackoverflow, but still it is not clear.
(Authenticate my “app” to Google cloud endpoints not a “user”)
Authenticate my "app" to Google Cloud Endpoints not a "user"
(How do I protect my API that was built using Google Cloud Endpoints?)
How do I protect my API that was built using Google Cloud Endpoints?
(Restrict access to google cloud endpoints to Android app)
Restrict access to google cloud endpoints to Android app
I had a similar issue, not between Android and App Engine, but between a separate server and App Engine. The way I handled it was to add a signature hash field as a parameter to each API call. If the request had an improper signature, it would be denied.
For example, suppose your API end-point is example.com/api/do_thing?param1=foo. I would hash the entire url, along with a secret key, and then append the result of the hash to the request: example.com/api/do_thing?param1=foo&hash=[some long hex value].
Then, on the server side, I would first remove the hash from the url request, then run the hash on everything that was remaining. Finally, you check whether the calculated hash matches the one that was sent with the request and if they don't, you can deny the request.
It is very important however that your secret key remain secret. You have to be careful with this on Android because someone could attempt to decompile your APK.
Facing the same problem than you ! Authenticate Android End point without Google User Account is just impossible !
So here is my way to resolv this problem, without any user interaction (Maybe not the right but that works, and you've got strong authentication (SHA1 + Google Account)):
HERE IS MY ANDROID CODE
Get and Build Valid Credential
//Get all accounts from my Android Phone
String validGoogleAccount = null;
Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
Account[] accounts = AccountManager.get(context).getAccounts();
for (Account account : accounts) {
if (emailPattern.matcher(account.name).matches()) {
//Just store mail if countain gmail.com
if (account.name.toString().contains("gmail.com")&&account.type.toString().contains("com.google")){
validGoogleAccount=account.name.toString();
}
}
}
//Build Credential with valid google account
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(this,"server:client_id:301991144702-5qkqclsogd0b4fnkhrja7hppshrvp4kh.apps.googleusercontent.com");
credential.setSelectedAccountName(validGoogleAccount);
Use this credential for secure calls
Campagneendpoint.Builder endpointBuilder = new Campagneendpoint.Builder(AndroidHttp.newCompatibleTransport(), new JacksonFactory(), credential);
HERE IS MY API BACKEND CODE:
API Annotation
#Api(
scopes=CONSTANTES.EMAIL_SCOPE,
clientIds = {CONSTANTES.ANDROID_CLIENT_ID,
CONSTANTES.WEB_CLIENT_ID,
com.google.api.server.spi.Constant.API_EXPLORER_CLIENT_ID},
audiences = {CONSTANTES.ANDROID_AUDIENCE},
name = "campagneendpoint",
version = "v1"
)
Method code:
public Collection<Campagne> getCampagnes(#Named("NumPortable")String NumPortable, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
return CampagneCRUD.getInstance().findCampagne(NumPortable);
}
For the moment, it only works on Android (I don't know how we gonna do on IOS..)..
Hope It will help you !
Google provides ways to do this for Android, web and iOS
The steps involves:
Specifying a client Id for apps you want to allow to make requests to your API
Adding a User parameter to all exposed methods to be protected by authorization.
Generating the client library again for any Android clients
Redeploying your backend API.
Updating the regenerated jar file to your Android project for your Android client.
These steps are laid out in clear detail on Google's Using Auth with Endpoints and also on this blog
Facing the same problem, here are the result of my research :
Added Android cliend id with SHA1 fingerprint in Google console
Use of it in the API annotation
BUT :
If i dont add user parameter to methods : the check about android app client id does not work
If I add the USER parameter but do not ask the user to choose its google account to create the credential ... also it does not work ...
Conclusion : It seems to be mandatory to connect a user account for the check about the app client id to be executed ... I really do not understand why because no link exist between the 2 processes
Access this site
Choose your project, go to credentials section
Create a new api key
Create a new android key
Click on "Edit allowed android applications" and enter your SHA1 key; your android package name
Let me know if this solves the issues.

What is the proper way to validate google granted OAuth tokens in a node.js server?

I'm trying to authenticate a mobile application for the Android platform to a custom node.js server api. I would like to use Google OAuth2 tokens for this rather than roll my own authentication, since Android devices with Google Play installed make this available to app developers. I'm using the GoogleAuthUtil.getToken call from the Google Play Services library, documented here. I'm trying to follow the advice outlinedin this android developers blogpost
The getToken method is returning in my case a long 857 byte string. If I try to pass this token to Google's TokenInfo endpoint, it returns:
{'error': 'invalid_token', 'error_description': 'Invalid Value'}
What am I doing wrong here? In the 'scope' of the getToken call, I am sending:
audience:server:client_id:**i_put_my_clientid_here**. I have a clientid generated for "installed applications". Using this client id, the call to getToken doesn't work at all. When I generated a client id for a "service account", the call succeeds, but I get an 857 byte token that fails when passed to the TokenInfo endpoint as described above.
EDIT:
I also created a client id for "web applications", as it appears that is the right client id to use when calling getToken. But the behavior is the same, I get back an 857 byte token that doesn't validate when calling Google's endpoint.
How can I properly get a valid auth token using Google Play services on Android? Once I have the right token, what is the right node.js library to validate it server side? Can I use passport-google-oauth ?
Hm, this is really a comment rather than an answer, but I can’t put newlines in those:
it has to be the web-side Clent ID that goes in the put_my_clientid_here spot
if GoogleAuthUtil.getToken() gives you a String withou throwing an Exception, it really ought to be valid. When you hit tokeninfo, did you use ...tokeninfo?id_token=<857-byte-value-here>
if you’re a rubyist, grab the google-id-token gem and see if it can validate your 857-byte token.
If you just want to read the contents of the data returned by GoogleAuthUtil.getToken then the process is very simple. The returned data is simply a JWT. So all you'd have to do is split the data by the . character, and then base64 (url) decode each piece.
It gets slightly more complicated if you want you want to verify the message's authenticity. Simply use your favorite crypto library to do the verification. The 3rd component of the JWT is the signature of the data and the Google certs are publicly available; that's all you need to verify the message.
For a week I have been looking into how to validate GoogleAuthUtil tokens received in Android Client application at Node.js server using passport.js
Finally I came across passport-google-token passport strategy which perfectly performs the task.
https://www.npmjs.com/package/passport-google-token
More details are present in the above link.
The official node SDK lets you do that now.
Here's the link: https://github.com/google/google-auth-library-nodejs/blob/master/lib/auth/oauth2client.js#L384
I'm not too familiar with the details of how Android works with respect to handing a token from the device to the server. My general impression, however, is that you don't go through the typical web-based OAuth dance. Instead, you directly call the "user info" endpoint, which will return the info corresponding to the user who holds the token, or reject the request if the token is invalid. There's some discussion on this related question:
validating Android's authToken on third party server
In effect, the token becomes a secret that is shared between both the device and your server, so its important to protect it.
There are a couple strategies for Facebook and Twitter that were developed to do similar things using tokens from iOS devices:
https://github.com/drudge/passport-twitter-token
https://github.com/drudge/passport-facebook-token
You can take some inspiration from them and tweak it to talk to Google's endpoints. Let me know how this turns out. I'd love to see a similar "passport-google-token" strategy, so if you implement one, let me know and I'll link to it!

Am I getting the steps right for verifying a user's Android in-app subscription?

I am making an app that does not require a user account/login, and allows the user to purchase a subscription. I want to use the Google Play Developer API to verify whether or not a user has a purchased/active subscription. From all of the documentation, I've gathered the following steps.
Are they correct, and could you answer the two questions in them?
Create a Service Account in the Google APIs Console.
Save the private key that is given to me (where? surely not in my code/on the device as this sample code suggests)
Use Google APIs Client Library for Java to create and sign a JWT with the private key (how? the docs give me this, but that is not Java code... What do I do with it?)
Construct an access token request, and get access to the API
Application can now send a GET request to the API to find out whether or not the
user has a subscription
When the access token expires, go back to step 3.
Also, I have a web service, though I know nothing about web services or web service programming... I only know enough to be aware that it is probably necessary to use here.
EDIT: These steps were not correct. See my answer below for the correct steps. However, note that this only applies to using a service account (because I did not want to require a user to have to explicitly allow API access)
As it turns out, my steps were not correct. It took me weeks to figure this out and it doesn't seem to be documented anywhere else. You're welcome:
Create a Web Application account in the Google APIs Console. Put any website as a "redirect URI"; it doesn't matter since you will not really be using it. You will get a client id and client secret when you create the account.
In a browser on your computer go to https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=[YOUR REDIRECT URI]&client_id=[YOUR CLIENT ID] and allow access when prompted.
Look in the address bar. At the end of the URI you entered originally will be your refresh token. It looks like 1/.... You will need this "code" in the next step. The refresh token never expires.
Convert this "code" to a "refresh token" by going to https://accounts.google.com/o/oauth2/token?client_id=[YOUR CLIENT ID]&client_secret=[YOUR CLIENT SECRET]&code=[CODE FROM PREVIOUS STEP]&grant_type=authorization_code&redirect_uri=[YOUR REDIRECT URI]. You can save the resulting value right in your program; it never expires unless explicitly revoked. (this step inserted by #BrianWhite -- see comments)
Make sure you are using POST.(inserted by Gintas)
In your code, send an HttpPost request to https://accounts.google.com/o/oauth2/token with the BasicNameValuePairs "grant_type","refresh_token", "client_id",[YOUR CLIENT ID], "client_secret",[YOUR CLIENT SECRET], "refresh_token",[YOUR REFRESH TOKEN]. For an example look here. You will need to do this in a separate thread, probably using AsyncTask. This will return a JSONObject.
Get the access token from the returned JSONObject. For an example look here. You will need to get the string "access_token". The access token expires in 1 hour.
In your code, send an HttpGet request to https://www.googleapis.com/androidpublisher/v1/applications/[YOUR APP'S PACKAGE NAME]/subscriptions/[THE ID OF YOUR PUBLISHED SUBSCRIPTION FROM YOUR ANDROID DEVELOPER CONSOLE]/purchases/[THE PURCHASE TOKEN THE USER RECEIVES UPON PURCHASING THE SUBSCRIPTION]?accesstoken="[THE ACCESS TOKEN FROM STEP 4]". For an example look here.
.NET Users: I hope this answer saves someone a ton of grief.
As #Christophe Fondacci noted on 2015, the accepted solution worked great a few years ago.
Now it's 2017 2020 and the process is far easier and faster.
My use case is to validate in-app subscriptions, where my mobile app sends subscription purchase information to my RESTful server, which in turn contacts Google to validate a subscription purchase.
The strategy is to create a Service Account that will operate on your behalf.
Sign into your Google Play Dev Console and click the app you're setting up.
Visit Settings->API access
Under Service Accounts, hit the Create Service Account button.
As of Jan 2017 a dialog with directions on setting up a service account appears. The dialog takes you to the Google API Console; from there,
A) Click Create Service Account
B) Create the service account name that makes sense. Since we're interested in accessing Android Publisher Services, I chose "publisher".
C) For Role, just choose something - you can change this later.
D) Choose "Furnish New private key" and choose P12 for .Net implementations. Don't lose this file!
Now you're done with #4, you'll see your new Service Account listed; click "Grant Access" to enable it.
Tap on the link to "View permissions". You should modify permissions based on your needs and API.
To validate in-app purchases, visit the Cog->Change Permissions and enable the GLOBAL "Visibility" and "Manage Orders" permissions.
OK at this point you have configured everything on Google's end. Now to setup your server to server stuff. I recommend creating
a .Net Console App to test out your implementation then offload it where needed.
Add the Android Publisher Client Library from Nuget[1]
PM> Install-Package Google.Apis.AndroidPublisher.v3
Add the P12 file to your project root
Change the P12 Properties so "Build Action" is "Content" and "Copy To Output Directory" to "Copy if newer".
Implement something like this to test your access and fine tune [1] .
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Services;
using Google.Apis.Auth.OAuth2;
using Google.Apis.AndroidPublisher.v3;
...
public Task<SubscriptionPurchase> GetSubscriptionPurchase(string packageName, string productId, string purchaseToken)
{
var certificate = new X509Certificate2(
"{{your p12 file name}}",
"{{ your p12 secret }}",
X509KeyStorageFlags.Exportable
);
var credentials = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer("{{ your service account email }}")
{
Scopes = new[] { AndroidPublisherService.Scope.Androidpublisher }
}.FromCertificate(certificate));
var service = new AndroidPublisherService(new BaseClientService.Initializer()
{
HttpClientInitializer = credentials,
ApplicationName = "my server app name",
});
return service.Purchases.Subscriptions.Get(packageName, productId, purchaseToken).ExecuteAsync();
}
Good luck, hope this helps someone.
Sources:
Using OAuth 2.0 for Server to Server Applications
.Net Client Library for Google.Apis.AndroidPublisher.v3[1]
1 Updated 04/11/2020 - Google.Apis.AndroidPublisher.v2 EOL'd, use Google.Apis.AndroidPublisher.v3.
If you are like me, and want to do this in PHP, here is the procedure how to do it... Thanks to Kalina's answer it took me only three days to work out how it works :).
Here goes:
go to google developers console https://console.developers.google.com/ and create a web app. Put 'developers.google.com/oauthplayground'as a "redirect URI"; You will use it in step 2. You will get a client id and client secret when you create the account. Make sure you have the Google Play Android Developer API added.
go to the Google oauth2 playground https://developers.google.com/oauthplayground/. This great tool is your best friend for the next few days.
Now go to settings : make sure Use your own OAuth credentials is set. Only then you can fill in your client ID and client secret in the form below.
In Google oauth2 playground go to step 1 Select & authorize APIs fill in the scope in the input field https://www.googleapis.com/auth/androidpublisher. I couldnt find the Google Play Android Developer API in the list, maybe they will add some time later. Hit AUTORIZE APIS. Do the authorisation thing that follows.
In Google oauth2 playground go to step 2 Exchange authorization code for tokens. If all went well you will see a authorization code starting with /4. If something didnt go well check the error message on the right. Now you hit 'refresh access token'. Copy the Refresh token... it will start with /1...
Now you can always get an access token! here is how:
$url ="https://accounts.google.com/o/oauth2/token";
$fields = array(
"client_id"=>"{your client id}",
"client_secret"=>"{your client secret}",
"refresh_token"=>"{your refresh token 1/.....}",
"grant_type"=>"refresh_token"
);
$ch = curl_init($url);
//set the url, number of POST vars, POST data
curl_setopt($ch, CURLOPT_POST,count($fields));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//execute post
$lResponse_json = curl_exec($ch);
//close connection
curl_close($ch);
Now you have an ACCESS TOKEN hooray... the JSON will look like this:
"access_token" : "{the access token}", "token_type" : "Bearer", "expires_in" : 3600
Finally you're ready to ask google something! Here is how to do it:
$lAccessToken = "{The access token you got in}" ;
$lPackageNameStr = "{your apps package name com.something.something}";
$lURLStr = "https://www.googleapis.com/androidpublisher/v1.1/applications/$lPackageNameStr/subscriptions/$pProductIdStr/purchases/$pReceiptStr";
$curl = curl_init($lURLStr);
curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$curlheader[0] = "Authorization: Bearer " . $lAccessToken;
curl_setopt($curl, CURLOPT_HTTPHEADER, $curlheader);
$json_response = curl_exec($curl);
curl_close($curl);
$responseObj = json_decode($json_response,true);
The JSON returned will contain two timestamps, the initiationTimestampMsec and validUntilTimestampMsec the time the subscription is valid. Both are the nr of millisecs to add to the date 1/1/1970!
I don't know in 2012, but in 2015 you should not do any of these steps manually. I had a very hard time to find the documentation so I am posting here in case it helps anyone.
You should only query in-app purchases from your server for security reasons as otherwise you can trust none of the 2 ends of the purchase process.
Now on the server side (I think you could still use the same code from your app if you absolutely need to), include the google-api-services-androidpublisher client library to your project (see https://developers.google.com/api-client-library/java/apis/androidpublisher/v1)
As you mentioned, you need a service account with a P12 file (the client library only accept P12 file).
Then the following code will authenticate and get purchase information nicely:
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = new JacksonFactory();
List<String> scopes = new ArrayList<String>();
scopes.add(AndroidPublisherScopes.ANDROIDPUBLISHER);
Credential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
.setServiceAccountId(googleServiceAccountId)
.setServiceAccountPrivateKeyFromP12File(new File(googleServicePrivateKeyPath))
.setServiceAccountScopes(scopes).build();
AndroidPublisher publisher = new AndroidPublisher.Builder(httpTransport, jsonFactory, credential).build();
AndroidPublisher.Purchases purchases = publisher.purchases();
final Get request = purchases.get(packageName, productId, token);
final SubscriptionPurchase purchase = request.execute();
// Do whatever you want with the purchase bean
Information on Java client authentication can be found here:
https://developers.google.com/identity/protocols/OAuth2ServiceAccount
I may misunderstand your question, but I don't see a reason for you to be using the links you're referencing to get In-App Billing for an Android app working. This page is much more helpful:
http://developer.android.com/guide/google/play/billing/index.html
You can try out the demo application they include (Dungeons -- http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-download). That uses products (one-time purchases) rather than subscriptions, but you should be able to modify to test for what you want.
I think the key, for you, would be the restoreTransactions method they provide in the sample to see if the Google Play account has any subscriptions for your app:
#Override
public void onRestoreTransactionsResponse(RestoreTransactions request, int responseCode) {
if (responseCode == BillingVars.OK) {
// Update the shared preferences so that we don't perform a RestoreTransactions again.
// This is also where you could save any existing subscriptions/purchases the user may have.
SharedPreferences prefs = getSharedPreferences(my_prefs_file, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(DB_INITIALIZED, true);
edit.commit();
} else {
Log.e(TAG, "RestoreTransactions error: " + responseCode);
}
}
If anyone is having issues with the accepted posts final step (#7), i found ?access_token= to work instead of ?accessToken=
Too bad stack overflow won't let me make that comment directly to the thread...
As you have a web service which your app can call, I would recommend storing your private key securely on your server. You should look to moving as much of the in-app stuff to service calls, as possible, see this link. I've implemented in-app subscription, but it was before this part of the API was out. I had to do my own registration and security verification but it looks like this API does most of that for you, using OAuth, although it looks like you are still responsible for storing the subscription request/verification.
Where it talks about signing your JWT's with an existing library, they do appear to provide you with links to a java library, a Python library and a PHP library - it depends what your web service or server component is written in (mine is C#, so I'm using RSACryptoServiceProvider) to verify signed purchases. They're using JSON objects for the actual transfer of data.

Categories

Resources