Play Store Subscription Validation On Server Side(Ruby On Rails) Using Callbacks

Mirza Obaid
4 min readMar 25, 2021
Finding Solution Is the Best Part

As a part of our daily tasks as a developer, we always came across something new something interesting, same happened with me when I was asked to verify and handle play store app subscription. I started to googling things went to stack overflow as our favorite option but was unable to find any single complete solution for this problem, so after reading a bunch of articles and Google developer documentation I was able to solve this problem. So I thought to write this in a single article so any other will not have to read 7000 lines of documentations or to waste any of this precious time.

This Problem has 2 part of the solution first one is to configure your app in the play store so that it can be able to send a push notification or callback to the server and the second part is to listen to that callback and respond to the callback as required and acknowledge the play store.

For the first part, you can check details on this link

Now we will see what we need to do on the server-side, As in the first step we have added callback URL in our play store app configurations now each time when our product subscription will be purchased or any event happens as subscription canceled or user asked for a refund after that google play store will fire a callback on our server, we will now see how we will handle that response.

First, we need to define a function that will handle the callback as I did in the below function.

Note: google callback is a post request

post :play_store_response do
subscription_updates(params)
end

In this callback, we will have the following response

{
"message": {
"attributes": {
"key": "value"
},
"data": "eyAidmVyc2lvbiI6IHN0cmluZywgInBhY2thZ2VOYW1lIjogc3RyaW5nLCAiZXZlbnRUaW1lTWlsbGlzIjogbG9uZywgIm9uZVRpbWVQcm9kdWN0Tm90aWZpY2F0aW9uIjogT25lVGltZVByb2R1Y3ROb3RpZmljYXRpb24sICJzdWJzY3JpcHRpb25Ob3RpZmljYXRpb24iOiBTdWJzY3JpcHRpb25Ob3RpZmljYXRpb24sICJ0ZXN0Tm90aWZpY2F0aW9uIjogVGVzdE5vdGlmaWNhdGlvbiB9",
"messageId": "1613732183432"
},
"subscription": "projects/myproject/subscriptions/mysubscription"
}

After you decode the base64-encoded data field, the DeveloperNotification contains the following fields:

{
"version": string,
"notificationType": int,
"purchaseToken": string,
"subscriptionId": string
}

In the Above response, the notification type is the most important. On the basis of notification type, we will perform our further implementation. Google play will send 13 types of the notification you can find the detail on the following link

I will also explain few important notification types here. we can divide all these notifications into three types

  1. Subscription Renewed, Recovered or restarted or purchased, means that user has purchased or avail your app subscription
  2. Subscription Canceled, Paused, Expired, On_hold, means the user has ended subscription
  3. And the last one is user refunded this subscription and no longer wants his subscription to continue

So now we will check and handle all of these types one by one.

First, we will decode the response

response = JSON.parse(Base64.decode64(params[:message][:data]))

now will check the response notification type and implement our logic

def subscription_updates(response) is_valid, returning_response = android_validation(response)
#android_validation function defined below
if is_valid
subsciption_start_seconds
= (returning_response["startTimeMillis"].to_f / 1000).to_s
subsciption_start_date = Date.strptime(subsciption_start_seconds, '%s')
subsciption_expire_seconds = (returning_response["expiryTimeMillis"].to_f / 1000).to_s
subsciption_expire_date = Date.strptime(subsciption_expire_seconds, '%s')
if response["subscriptionNotification"]["notificationType"] == 4
# notificationType = (4) SUBSCRIPTION_PURCHASED
# its meaning any user has purchased our app subscription for the first time
Modal.create(
current_status: true)
elsif response["subscriptionNotification"]["notificationType"] == 5
# notificationType = (5) SUBSCRIPTION_ON_HOLD
Model.update(current_status: false, subscription_status: "hold")
elsif response["subscriptionNotification"]["notificationType"] == 3
# notificationType = (3) SUBSCRIPTION_CANCELED
Model.update(current_status: false, subscription_status: "cancel")
elsif response["subscriptionNotification"]["notificationType"] == 10
# notificationType = (10) SUBSCRIPTION_PAUSED
Model.update(current_status: false ,subscription_status: "paused")
elsif response["subscriptionNotification"]["notificationType"] == 2 || response["subscriptionNotification"]["notificationType"] == 7 || response["subscriptionNotification"]["notificationType"] == 1
# notificationType = (2) SUBSCRIPTION_RENEWED
# notificationType = (1) SUBSCRIPTION_RECOVERED subscription was recovered from account hold
# notificationType = (7) SUBSCRIPTION_RESTARTED if user resubscribe from manage subscription tab on play store
Modal.create(
current_status: true)
# we can store data of purchase in our model and update its status
Model.update(current_status: false , subscription_status: "refunded")
elsif response["subscriptionNotification"]["notificationType"] == 12
Model.update(current_status: false , subscription_status: "refunded")
end
end

Note: in current_status I am saving user subscription_status like the user has purchased the subscription or not and in subscription_status I am saving play store response in case of user subscription canceled or on hold or on paused

As you can see I have created a function named(android_validation) and passed the response to this function that will double-check the user purchased token from the play store. it's mandatory as the Base64 message that contains all the purchases can easily be decoded. so we need to double-check from the play store that this token is valid

def android_validation(response)
scope = 'https://www.googleapis.com/auth/androidpublisher'
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(json_key_io: File.open("my-app-file.json"),scope: scope)
authorizer_responce = authorizer.fetch_access_token!
access_token = authorizer_responce["token_type"] + " " + authorizer_responce["access_token"]
android_receipt_verify_url = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/#{response["package_name"]}/purchases/subscriptions/#{response["subscriptionNotification"]["subscriptionId"]}/tokens/#{response["subscriptionNotification"]["purchaseToken"]}" url = URI.parse(android_receipt_verify_url) http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
store_respone = http.get(url.path, {'Authorization' => access_token})
store_respone = JSON.parse(resp.body)
end

the last line in the above function will return the following

{
"kind": string,
"startTimeMillis": string,
"expiryTimeMillis": string,
"autoResumeTimeMillis": string,
"autoRenewing": boolean,
"priceCurrencyCode": string,
"priceAmountMicros": string,
}

you can find detail on this link

So if we see we just need to follow a few steps

  1. create a function that will listen to the play store callback in my case its play_store_response
  2. decode the google response and check notification type as I have done in the subscription_updates function. this function will also be responsible for saving the data in DB and updating your model.
  3. And last but not least we need a function that will verify the data for the play store in my case its android_validation.

--

--