Hello there!
So you’ve spent substantial amount of time over something which seems so unproductive & quite illogical, right? Been there, done that & it sucks! I know this because I’ve been to dark side before!
Anyway, I assume you’d have little bit idea about Apple’s in-app purchases, right? So let’s take follow up of certain things:
- We’ll implement Automatically Renewable Subscription & Consumable type of in-app products.
- Your app will initiate the purchase.
- Your server should be responsible for verifying the purchase.
- Avoid purchase verification on the device using App Store server /verifyReceipt endpoint.
- Avoid purchase verification on your server without /verifyReceipt endpoint.
- After successful completion of the purchase, your app will hit your server’s API to verify this purchase.
- If your server is down or not accessible, make sure your app will try to verify successful but unverified purchases until server is back online.
- Your server should be responsible for providing benefits or premium features to the User.
- Your server should be responsible for updating user status by renewals, cancellations, restorations & payment failures.
- Your server should be responsible for letting users know of events of initiate, renewal, revoke & expiry.
Languages & Tools
We’ll be using following things to get started:
- Linux machine (I’m using Debian 9.8)
- An IDE preferably VS Code
- Ruby distribution (version >= 2.5.1)
- PostgreSQL
- App Store & Heroku account for configuring in-app purchases & deploying our app
- Heroku CLI (Optional)
I’m giving preference to VS Code because it’s easy to operate and offers a lot in terms of plugins & code management.
Also, you can use any language other than Ruby as overall procedure to implement in-app purchases is very much similar.
And lastly, we need App Store account which will be used for setting up iOS client app & configuring in-app products’ details. As this blog is targeted to server-side implementation of in-app purchases, it’s better not to explain how to set up Xcode or App Store for the iOS app.
Configuration
We’ll separate this into 3 sections i.e. App Store Product configuration, App Store Sandbox configuration and Server configuration.
App Store Product Configuration
First task in our hands is to configure App Store account for the Apple in-app purchases. I assume you’ve already completed the app related configurations. So let’s get started!
On signing in into your iTunes Connect account, click on your app. This will take you to the details screen. Click on second tab ‘Features’ on left-top corner of your screen. This is where we’ll manage our in-app products.
In order to create our first in-app product, follow the steps:
- Click on (+) button.
- Select Consumable or Automatically Renewable Subscription from dialog shown.
- Fill in Reference Name, Product ID, Price, Localization, etc.
- When Renewable Subscription is selected, remember to choose a Subscription Duration.
- If asked, enter anything in Group Reference Name that you find easy to remember like App Name.
- Finally, check the box saying Cleared For Sale.
Some points to remember here:
- Consumable are consumed only once & not renewed while renewable subscriptions are consumed multiple times & renewed too.
- Reference Name is only shown in certain reports & on App Store Connect.
- Display Name is shown on App Store.
- Without checking Cleared For Sale, your product will not be available to purchase.
- Product IDs cannot be reused even after they are removed.
- When multiple subscriptions are needed at the same time, Group Reference is used. We’re only implementing one subscription at a time.
With completing above steps, we’ll now have our in-app product(s) ready to be purchased!
As we are implementing both consumable & renewing subscription, ensure that different product IDs are created and are ready to be used.
For sake of our peace of mind, let’s just set product ID of our consumable product to 1_coin (1 coin) and that of renewing subscription product to 1_month_subscription (1 month subscription).
Now, go back to the screen where you can see all the products you created. Here, look for App-Specific Shared Secret. Click on it and generate one secret if not already. This secret is required to verify our purchases and we’ll set this as an environment variable.
App Store Sandbox Configuration
We’ll be using Sandbox environment to test in-app purchases without using actual money! Sounds great right? Sorry to break it to you, it doesn’t! Until now the procedure was simple but this is where it all gets ugly. Anyway, for using Sandbox, you’ll have to create a Sandbox user & use it whenever purchase is being made. Follow these steps to get started:
- Click on User and Access from My Apps drop-down found on left-top corner of your screen.
- Look for Testers under Sandbox & click it.
- Click on (+) button.
- Fill in all the details. Remember that email entered here must be verified in later step.
If for ‘some’ reason *cough*so called innovative leader in ground-disrupting technologies*cough*, you are stuck at an error ‘There are one or more validation errors below.’, then check if you fall under these scenarios:
- You actually are making some silly typing mistakes! ¯\_(ツ)_/¯
- You’re using existing Apple ID to create new Sandbox user.
- You’re using an email which is already added in Users list.
- You’re so naive that you’re still trying Apple’s in-app purchase to work.
If you’re not the forth one, then avoid doing it! Seriously Apple, get it together! One warning or instruction wouldn’t be so much trouble!
Once you’re done creating new Sandbox users ( Yes! You need at least five of Sandbox users for testing! ), verify your newly created Apple ID by clicking an link in the email you just received! Now you can use those to sign in on any Apple devices. Also, consider signing out of your actual accounts before signing in as a Sandbox user.
Quick Tip: You can use email aliases to create multiple Sandbox user accounts. For instance, if your primary email ID is test.user@gmail.com, then you can use test.user+1@gmail.com or test.user+yoda@gmail.com.
Server Configuration
Now let’s take some relief & find out how to configure Server-Side (or Dark Side? :D). We’ll be using Ruby as coding language and Padrino as web-framework. But first, let’s set the environment variable that we’ve talked about.
Execute following command in terminal or bash:
$ echo 'export APP_STORE_SECRET='8456ceb1aa3f48fc881...' >> ~/.profile
To check if above command succeeded or not, execute another command:
$ env | grep APP_STORE_SECRET
You’ll find the environment variable with its value. You can check out this repo created by me for basic skeleton of Padrino app. Repo has the master branch which contains full source code. Please note that you would have to create PostgreSQL database too, which we can only do via shell commands. For creating database you can follow this very useful guide and make sure your connection to the database is successful by executing below command:
$ bundle exec padrino console
If everything’s good, you’ll see the environment loaded Padrino console. The console is an interactive Ruby interface which is very useful when you want to try out code before running it on the server.
The connection URL should look like this:
postgres://localhost/sample_db?user=yoda&password=yoda
Anyway, checkout the basic-skeleton branch and execute following command to fetch any dependencies we’ve set.
$ bundle install
Now let’s get started with creating required files like controllers, migrations etc. They are required for building APIs which will be used to verify our in-app purchases. Open terminal and execute following command:
$ bundle exec padrino g controller users
Above command execution will create 2 files i.e users.rb & users_helper.rb. The first file is a controller whereas later one is just a helper class which is accessible from anywhere. If you aren’t familiar with MVC pattern, this explanation should be helpful for you. Here, Controller means it controls data flow between views & models & itself. View means anything that’s visible to users. Models are entities that actually constitute business logic. Now, execute following command:
$ bundle exec padrino g model user
With this command, you will have model User automatically created and derived from class Sequel::Model. Interesting thing about Padrino is that it knows what you need. You may find a new file 001_create_users.rb in db/migrate directory which is our first migration from nothing. Migrations are useful when you have to alter database structure in a certain manner. If something goes wrong, you can always go back to required migration number without losing all data. Note the naming conventions that I’ve used in above commands. Controller names should always be a plural entity while model name should be a singular entity. The following code tells rake to create user table in our database ‘sample_db’. Edit file 001_create_users.rb & put below code in migration block:
Sequel.migration do up do create_table :users do primary_key :id String :username, unique: true, null: false String :password, null: false TrueClass :is_premium, default: false DateTime :premium_start_time DateTime :premium_end_time Text :receipt_data String :environment String :transaction_id String :original_transaction_id DateTime :created_at DateTime :updated_at DateTime :deleted_at end end down do drop_table :users end end
Um, excuse me? What is Rake? And What is that code doing?
So…Rake is nicely explained here and here. About the code part, it’s blueprint of table that we’re asking it to create. There are 2 blocks here – up & down. When migration is run in upward direction, ‘up’ block gets executed & if migration is run in downward direction, ‘down’ block gets executed. Notice that we’ve used different data types for certain column types e.g. DateTime.
It’s very easy to create tables & add API endpoints, or is it? Again, Don’t worry! I’ll help you. Let’s start by creating users table first. If you remember, you’d know that we’ve used postgres as adapter while creating the project.
The following command creates user table in our database ‘sample_db’. Note down prefixed number of filename ‘001_create_users.rb’ & execute below command:
$ bundle exec rake sq:migrate{:to[001] OR :up}
Things in curly brackets are optional. By using number we’ve noted earlier or just by mentioning keyword ‘up’, we can tell Rake to migrate in upward direction. After successful execution you’d notice that our ‘sample_db’ has new table users. With this step, you can now run your server without any errors.
$ bundle exec padrino start (OR s for short)
You’ll see something like this in the terminal:
=> Padrino/0.14.4 has taken the stage development at http://127.0.0.1:3000
Go on click on the link & you’ll find your server up & running with following message! Sinatra doesn’t know this ditty.
Endpoint & API Setup
I know, this is getting a lot more complicated than you thought, right? But let me assure you my young padawan, we’re on the right path. In this section, we’re going to find out how to create APIs & setup their endpoints. You’d have known by now that we have a controller called users which will form URL like this http://localhost:3000/users/. Any functions we define in this controller can be used as an API endpoint. Let’s create a simple function for fetching user’s details:
AppleInappSample::App.controllers :users do get '/profile', with: :user_id do ret = {} begin validate_fetch(params) user = User[params[:user_id]] ret = { success: true, message: 'User found!', data: user.to_h } rescue StandardError => e status 400 ret = { success: false, message: e.message } end ret.to_json end end
I’ll explain little bit about above code block. We have first line i.e get ‘/profile’, with: :user_id do … – yeah you’re right Karen! It’s a GET request! It’s also a function with one query parameter user_id. The curl command for above API might look like this:
$ curl http://127.0.0.1:3000/users/profile/12
The function validate_fetch requires params as a parameter and it can found in users_helper.rb class. The line containing User[params[:user_id]] does what it seems to do i.e find an user having id of :user_id. Note that the User class is a Sequel.Model that we created earlier. Before proceeding to next step, add this line to your Gemfile.rb and then execute bundle install command.
gem 'venice'
Venice is a great gem for verifying Apple’s in-app purchases & it’s very simple to use too! We are going to use Venice in the class AppleManager found in lib directory of master branch.
Now we can add one more function like follows. This function will actually verify the purchases we would make from iOS client.
AppleInappSample::App.controllers :users do post '/verify_subscription/', csrf_protection: false do ret = {} begin validate_subscription(params) manager = AppleManager.new(params) manager.verify_receipt ret = { success: true, message: 'Subscription verified!' } rescue StandardError => ae status 400 ret = { success: false, message: ae.message } end return ret.to_json end end
The curl command for above API might look like this:
'user_id=1' -d 'receipt_data=ewoJInNpZ25hdHVyZSIgPSAiQXhze...' POST http://127.0.0.1:3000/users/verify_subscription/
The required parameters for this API are user_id & receipt_data. Receipt data is base64 encoded receipt of the purchase and it should always be generated on iOS client. Before using these parameters to verification, we’re validating them against null values by passing them to the function validate_subscription found in users_helper.rb class. You may also want to checkout AppleManager.rb class to know how Venice works with verification. With this step we have completed endpoint & API setup and yeah, I think next steps should be quite easy…
Getting our hands dirty!
Now that we’ve setup everything & our APIs are ready, we can test our first purchase. I assume you’re ready with 3 things – an iOS client app, the code that initiates in-app purchase flows and the code that hits our APIs. Also, make sure that you’ve signed out of your App Store account from test device and you’re signed in as Sandbox user by clicking Settings -> iTunes & App Store -> Sandbox Account -> Sign In.
Once you sign in, go to the app screen where you can start in-app purchase flow. When you are asked to enter account credentials, just enter the ones you’ve used earlier to sign in into Sandbox account. App Store kit will finally ask to confirm if you want to purchase the in-app product or not? Go on clicking the Continue button and after successful completion, you should check for the purchase receipt from App Store kit.
You can also check out the official documentation about how to get encoded receipt_data from bundle. Also note that field password mentioned need not to be send to server every time as we’ve already configured server’s environment and set that password as variable APP_STORE_SECRET.
The process is ‘simple’ –
- You click to buy a product say 1_month_subscription
- App Store kit completes purchase & return receipt
- Encoded receipt data is built
- App sends this receipt data to API /users/verify_subscription/
- Server validates it & updates user row as per details in latest receipt
- App updates the UI to notify user.
- After automatic renewal of the subscription, update to the user is triggered. (Explained later)
- When a subscription is expired, update to the user is triggered.
- App updates the UI to notify user.
For processing auto-renewals of a subscription, using a Rake task sounds good which will be triggered at a certain time. ‘Common’ practice is that this task should be triggered only at certain times such that it falls between x days before and x days after subscription’s expiry time. You don’t want to waste server resources every day, do you? Then again, Apple doesn’t really care about anything (except money!) I guess. When it comes to check for renewals, they recommend to rely on renewal receipts sent from iOS client devices. I think status polling is efficient for basic use-cases like ours.
In the task I mentioned above, similar procedure is to be followed for checking renewals. If Apple returned latest version of receipt and it contains a transaction of same original_transaction_id that we’ve in database but with different transaction_id, then we can update the database with this transaction as a ‘Renewed‘ subscription. If in that transaction, expires_at is in past, then it can be considered as ‘Renewed but expired’ subscription.
require 'venice' ## This task is triggered each day to check if a subscription is renewed or not? # Expired subscriptions are also updated in database. # This is just basic implementation & could vary based on different cases. class ExpireUpdateSubscriptions @queue = :high def self.perform users = User.where { end_time < DateTime.now } .exclude(transaction_id: nil) .exclude(receipt_data: nil) .not_deleted .all users.each { |u| verify(u) } end def self.verify(user) params = {} params[:user_id] = user.id params[:receipt_data] = user.receipt_data manager = AppleManager.new(params) manager.verify_receipt end end
Now, how to trigger this task is totally up to you. There are some options like resque-scheduler which can be really handful sometimes.
Conclusion
I hope this is how Apple’s in-app purchases are implemented but this might not be the best way in your case. I must say that Apple would not care to make their in-app purchases easy to implement in near future, so I would recommend to research & try until you find the implementation best suited to your case. I would totally love to write about the consumable part of the in-app in another blog – may be… it could be easier than this ‘Phantom Menace’! Just kidding! It is lot easier than the subscriptions, I assure you.
If you have an idea of app which requires Apple subscriptions, feel free to contact us. We’ll help you translate them into reality.