http://www.raywenderlich.com/36270/in-app-purchases-non-renewing-subscription-tutorial
This post is also available in: Spanish
There are three major types of In-App Purchases in iOS:
- Non-consumables: These are things the user buys once (and only once), and then has access to forever. Some examples would be an extra level pack, a permanent item, or some downloadable content.
- Consumables: These are things the user can buy multiple times. Often they are “used up” so the user can buy them again. Some examples would be currency in a free-to-play game, or consumable items like healing potions or extra lives.
- Subscriptions:: You can also provide access to content in your app on a time-limited basis – making the user purchase a subscription to continue to access your content. Some examples would be subscribing to an electronic magazine, or subscribing to unlock an extra feature in an app for a month.
Subscription models are definitely worth considering in your apps, because since users can send payments on a regular basis, it can have a higher chance of generating a sustainable revenue stream.
There are three types of subscriptions – auto-renewable subscriptions, free subscriptions, and non-renewing subscriptions. This tutorial will be focusing on the third, as it’s the most appropriate for non-Newsstand apps.
In this tutorial, you will be adding non-renewing subscriptions to an app called “In-App Rage”, an app that allows you to browse rage comics. You will also be using Parse as a back-end provider for the app.
Before beginning, you should be sure to complete, or have experience equivalent to:
- Introduction to In-App Purchases in iOS 6. You will be extending the In App Rage app from this tutorial. There’s always enough rage to go around!
- In-App Purchases in iOS6 Tutorial: Consumables and Receipt Validation. It’s highly recommended to use receipt validation when implementing subscriptions, so this gives you a great platform upon which to build.
- How to Easily Create a Web Backend for Your Apps with Parse. You’ll be using Parse as the backend service for storing your users’ subscription information.
If you’re ready to level-up your IAP mastery, read on!
When to Use Non-Renewing Subscriptions
It may seem obvious, but let’s discuss a bit more about the type of subscriptions in iOS.
Auto-renewable subscriptions
When a user signs up for an auto-renewable subscription, they continue to be charged until they manually cancel it. This is obviously great from a developer’s point of view, because it takes a lot more effort to cancel something than to just let it continue.
You might already be familiar with a class of apps that use auto-renewable subscriptions already: Newsstand.
Newsstand was first introduced in iOS 5, and allows content providers to easily distribute their newspapers and magazines. With it, Apple introduced the auto-renewable subscription model, which allows you to set a subscription duration and manage renewals automatically through the StoreKit framework.
However, Apple has placed some very strict rules around auto-renewable subscriptions, meaning their usage is (usually) exclusive to Newsstand apps.
So sadly, if you want to provide content or features for a limited duration, outside of Newsstand, then your only option is to use non-renewing subscriptions.
Non-renewing subscription
When a user signs up for a non-renewing subscription, they subscribe for a set period of time (1 month, 3 months, etc). When the time runs out, their access to the content ends – but to continue to access the content, they have to re-subscribe.
Obviously this is not as ideal from a developer’s point of view as it forces customers to have to continually make the decision to subscribe, but if you don’t have a magazine-style app it’s the best you can do at this point :]
Here are a couple of examples of when you might consider including a non-renewing subscription:
- An Optional Feature. Maybe you have a killer feature in your app, that you want people to be able to subscribe to on an optional basis. For example, Instapaper (shown on the right) allows users to sign up for full-text search on their documents on a time-limited basis.
- Periodic Content. Maybe your app delivers content periodically, such as extra levels or bonus playable characters in a game. You could allow the user to purchase a subscription to access this extra content.
Implementing Non-Renewing Subscriptions: Overview
All right, so you’ve decided you want to begin building your non-Newsstand subscription empire. What does this mean when it comes to the nitty-gritty of development?
Unlike auto-renewable subscriptions, where subscription durations and renewals are handled through the StoreKit framework, non-renewing subscriptions require you to do all the heavy lifting.
Here are some things to consider when implementing non-renewing subscriptions:
- The subscription duration is not managed for you by StoreKit, so you’ll need a way of calculating the duration at the point of purchase.
- As with consumable products, your users should be able to purchase items multiple times. Thus, you’ll need a way of determining if there’s time remaining on an existing subscription, and of including that time in any new duration, should a user choose to renew.
- You’re also required to make the subscription available to any device owned by the user. There are generally two feasible options you can use to accommodate this requirement:
iCloud. Since the user’s iCloud account is exclusive to them, but shared across their devices, this is a simple and effective option. However, if your app is cross-platform, or has an companion web app, this won’t be the best choice since iCloud is restricted to iOS devices.
Backend as a service, or BaaS. By requiring a user to create an account in order to subscribe, you can store any necessary data, such as the subscription expiry date, against their account on the server. This method will allow you to share a subscription across all platforms, simply by requiring a user to log in.
In this tutorial, you’ll be using Parse as the backend to store this information, as it is very popular and easy to use. So let’s get started!
Getting Started
When you’re ready to begin, download the starter project here.
Note: Be sure not to use either of the sample projects from the previous in-app purchase tutorials. For one thing, they do not include the Parse integration found in the above starter project.
Second, be aware that if you attempt to compile the sample project from the In-App Purchases in iOS 6 Tutorial: Consumables and Receipt Validation tutorial, you may well notice deprecation warnings. This is because Apple deprecated the UDID (a device-specific unique identifier) as of iOS 5, but the receipt validation code the sample depends upon relies on the UDID.
The good news is the receipt validation code was originally supplied by Apple and has since beenupdated to remove the reliance on the UDID. The starter project provided for this tutorial makes use of Apple’s updated code.
There are a few things in the starter project that need updating before you can get to work implementing subscriptions.
First, you’ll need to set up an app in Parse for this tutorial. To do this, do the following:
- Head over to Parse.com and either log in or sign up.
- If you’re taken straight to the dashboard, hit the + Create New App button. Otherwise, you’ll be prompted to create a new app. Enter In App Rage as the app name.
- You’ll then be shown the Getting Started dialog box, and from here you can find the Application ID and Client Key. Record this for later.
Note: An alternate way to find your Application ID and Client Key is to select your app from the dashboard, choose Settings and then Application Keys, as shown below:
Once you have the Application ID and Client Key, open AppDelegate.m and do the following:
- Locate the
application:didFinishLaunchingWithOptions:
method. - Find the
[Parse setApplicationId:@"AppID" clientKey:@"ClientID"];
line. - Replace AppID with your Application ID and ClientID with your Client Key.
Now update ITC_CONTENT_PROVIDER_SHARED_SECRET
in the VerificationController.h file to your own shared secret:
- Log onto iTunes Connect and click Manage Your Apps.
- If you followed our previous tutorial, choose the In App Rage app and click Manage In-App Purchases. Otherwise, just create a new entry for this app – follow the previous tutorial if you get stuck.
- Scroll down and click View or generate a shared secret. You will be able to view your existing shared secret here, or create a new one by clicking Generate.
What’s a shared secret? It’s a piece of data known only to the parties involved in secure communication. In this case, in order to verify a receipt with the Apple servers, your app has to provide the shared secret so it can be verified as a trusted source.
Open In App Rage-Info.plist and update your bundle identifier to match the one you created in your previous In App Rage project (or whatever bundle identifier you set up for this app).
If you can’t remember what bundle ID you used, log onto the iOS Dev Center and click Certificates, Identifiers & Profiles. Click Identifiers, locate the In App Rage app and note the value in the ID column. This is your bundle identifier.
Your final task is to replace all occurrences of the product identifiers found within the app with the product identifiers that you created on iTunes Connect for this app.
Here’s a useful tip: use the Xcode search navigator tab to do a project-wide find and replace. You’ll have those identifiers replaced in no time at all:
Build and run. When the app launches for the first time, you’ll be required to create a new account before the products list is displayed.
Follow the steps to create a new account. When you’re done, you should see something like this:
You’re now ready to begin implementing non-renewing subscriptions!
Creating Non-Renewing Subscriptions
You’re going to provide the user with a choice of two subscription durations, three months or six months.
Log onto iTunes Connect and click Manage Your Apps. Choose the In App Rage app. Click Manage In-App Purchases followed by the Create New button.
Find the Non-Renewing Subscription section and click Select.
Non-renewing subscriptions are, in principle, very similar to consumable products. The options should feel instantly familiar if you completed the In-App Purchases in iOS 6 Tutorial: Consumables and Receipt Validation.
Fill out the In-App Purchase form as follows:
- Set Reference Name to 3monthlyrage
- Set Product ID to com.[insert your bundle indentifier].inapprage.3monthlyrageface
- Set Price Tier to Tier 2
- Click Add Language. Set Language to UK English, Display Name to 3 Months of Rage andDescription to Purchase 3 Months of Rage
Then click Done to save the IAP details. Repeat the process for the six-month subscription using the following details:
- Set Reference Name to 6monthlyrage
- Set Product ID to com.[insert your bundle indentifier].inapprage.6monthlyrageface
- Set Price Tier to Tier 4
- Click Add Language. Set Language to UK English, Display Name to 6 Months of Rage andDescription to Purchase 6 Months of Rage
If you completed both previous IAP tutorials, you should now have a total of eight in-app purchases on your list:
Note: It is imperative that you specify the duration of any subscription-based IAP, and the most common way to do this is in the display name or description. There’s a good chance your app will be rejected if it fails to clearly state the duration of any subscription.
Adding Your Subscriptions to the Product List
The first thing you need to do is add the new product identifiers you’ve created to the set of existing product identifiers found in the starter project.
Open RageIAPHelper.m and add the two new identifiers to the productIdentifiers
set:
|
As mentioned earlier, you need a method that generates the expiration date of a subscription at the point of purchase. It makes sense to add this method to the existing IAPHelper
class.
Open IAPHelper.h and add the following just beneath the existing UIKIT_EXTERN
statement:
|
Then add the following method declarations below the existing ones:
|
Now open IAPHelper.m and add the following #import
statement at the top of the file:
|
Just below the #import
statements, add this constant, which you’ll need later:
|
Before it can generate an expiration date, the app needs to check if there’s an existing subscription, and if so, whether it has any time remaining. Add the following to the bottom of the IAPHelper.m file:
|
Here’s what’s going on in the code above:
- You retrieve the local representation of the current subscription’s expiration date from
[NSUserDefaults standardUserDefaults]
. Note the use of thekSubscriptionExpirationDateKey
constant you defined earlier. - You determine the number of seconds between the expiration date retrieved in step 1 and the current date.
- You calculate the number of days by dividing the number of seconds obtained in step 2 first by 60 (seconds per minute), then by 60 again (minutes per hour) and finally by 24 (hours per day).
- If the number of days obtained in step 3 is greater than 0, you return
days
, otherwise you return 0. This method will also return 0 if an expiration date isn’t found in[NSUserDefaults standardUserDefaults]
.
Note: Note that using NSUserDefaults to store the expiration date for the subscription isn’t a very secure way to implement this. It is relatively easy for someone with a jailbroken device, or access to software such as Macroplant’s iExplorer, to trick the app into providing a subscription they haven’t actually purchased.
There are two ways to think about this kind of thing – either don’t worry about piracy (with the thinking that most users are honest and will go the easy route of just purchasing something on the store if they want it and it’s available, and you can’t stop determined attackers anyway), or that a little bit of anti-piracy goes a long way.
In the end it’s up to you and your app. This tutorial favors simplicity over security, and you can use this as a foundation upon which to build your own, more secure implementation if you so desire.
Now that you can determine the current expiration date, if there is one, you can move onto implementing the getExpirationDateForMonths:
method. It accepts a single parameter, which represents the length of a subscription in months, and calculates the expiration date:
Still in IAPHelper.m, add this method:
|
There are two fairly simple steps in this method:
- Using the
daysRemainingOnSubscription
method you just implemented, you check to see if there’s an existing expiration date. If a date does exist and it’s valid, you use it as the origin date; otherwise you use today’s date. - You use
NSDateComponents
to add the length of the subscription to the origin date, and you return the freshly calculated date.
Note: NSDateComponents
is a Foundation class that is extremely useful when working with dates. By setting any of the properties that represent units of time, it can calculate dates in the past or into the future.
Here, you created the components manually and applied them to an existing date, but by using thecomponents:fromDate:
method of NSCalendar
, you can do the opposite and extract the date components from an existing date. This could be useful if you had to determine within what week of the year a date falls, for example.
NSCalendar
also provides dateFromComponents:
, a useful method that can generate a date in situations where you may not have all the necessary information, but enough for NSCalendar
to recognize it as a date. NSDateComponents
, and related classes, are incredibly useful tools to have in your armory.
While you don’t need it just yet, you’ll use getExpirationDateString
to generate the user-facing expiration date, including the amount of time remaining on the subscription, or an alternative message if the user isn’t subscribed. Add the following:
|
Using the daysRemainingOnSubscription
method you implemented earlier, you determine whether or not there’s a currently active subscription. If there is, you return a string containing the expiration date; otherwise you return the string “Not Subscribed”.
Now that the foundations are in place, you’re able to write the subscription purchasing method. Add the following:
|
Let’s break this down step-by-step:
- To begin with, you query Parse using the
PFQuery
class to retrieve any expiration dates it has stored for the current user. When the user logs in, theObjectID
for their account is stored locally and can be accessed via the[PFUser currentUser].objectId
property. - You store the expiration dates saved on Parse in an array. You’re simply interested in the last object of that array, since that’ll be the most recent subscription’s expiration date.
- Next, you compare the local date and the server date to determine which is more recent; if it’s the server date, the local date is updated to match. This avoids a potential problem where a user has renewed their subscription on one device and then tries to renew it on a different device, before any existing purchases are restored.
- You generate a new expiration date.
- You then save the new expiration date both locally and on Parse.
You now need to update the IAPHelper
class to make sure it’s aware of which IAPs are subscriptions. Add the following code to the provideContentForProductIdentifier:
method:
|
The method now recognizes any product identifier suffixed with monthlyrageface as a subscription. The entire product identifier is subsequently used to determine the duration of the subscription, and the purchase is then performed accordingly.
Build and run.
You should now see the subscriptions in the list. But before you try to purchase your newly-implemented subscriptions, there’s some more work to do. The app doesn’t provide any content yet, and there’s no way to tell whether or not there’s an active subscription and if so, how long before it expires.
Providing Subscription Content
You want to query the IAPHelper
class to make sure the user has a valid subscription before you provide any content. Open MasterViewController.m and modify prepareForSeque:sender:
to look like the following:
|
You’ll see you’ve added an extra condition to the if
statement, guaranteeing the content is provided if it’s been purchased or there’s a valid subscription.
While you’re in your MasterViewController.m file, update the productPurchased:
method to the following:
|
The above loops through your list of product identifiers. If the product is a subscription, it refreshes the full table; if it’s just a single purchase, it refreshes only that one line. You could refresh the entire table each time, but this way is cleaner.
That’s all you need to do for the non-consumables, but what about the random rage faces? You certainly don’t want your users missing out on those!
Open RandomFaceViewController.m and add the following #import
statement at the top:
|
Now modify refresh
as follows:
|
The UILabel
of RandomFaceViewController.m currently displays the number of random faces remaining. This will look a bit odd if a user is subscribed and therefore has unlimited random images. The modified code determines if there’s a valid subscription and, if so, uses getExpirationDateString
to set the label text accordingly.
Hang on, have you missed or forgotten anything?
Of course! The app has to actually provide those unlimited random faces if the user has a valid subscription. Make the following modification to buttonTapped:
to sort that out:
|
Build and run.
Although this takes care of providing the content, there’s still not a lot to see yet because the interface doesn’t inform the user if they’re already a subscriber. Let’s take care of that next.
Displaying Subscription Details
It should always be clear to a user what they’ve purchased. With a few modifications, you can achieve exactly that.
When the user purchases a non-consumable, the Buy button is changed to a checkmark. If the user purchases a subscription, the button remains a button, even though the content is now available. Make the following modifications to MasterViewController.m to fix this poor and confusing experience:
In the tableView:cellForRowAtIndexPath:
method, modify the if
statement:
|
The extra conditions make sure that subscription items always display a Subscribe or Renew button as appropriate. The new code also ensures that all other (non-subscription) items display a checkmark if the user has a valid subscription.
Now modify the else
branch of the same if
statement:
|
Originally, this block of code simply added a Buy button for all un-purchased items. Now it displays aSubscribe button for all subscription items, or a Renew button if there’s already an active subscription.
Build and run.
Much better. Tap one of the Subscribe buttons and admire the fruits of your labor. The usual confirmation dialog should appear:
Once you’re purchased a subscription, try tapping on a Renew button. You may not have seen this dialog before:
Note: If you have two or more subscription options, like you do here, it’s imperative to be aware of how the App Store behaves. Purchasing works on a per product basis, meaning if you were to subscribe to a three-month subscription and then subsequently renew with a six-month subscription, you wouldn’t see the renewal dialog as you may expect; you’ve actually chosen a different product. While this isn’t overly important, it does feel a little clumsy, is definitely something to be aware of, and may confuse your users.
The product list should now behave correctly. If you purchase everything, the Subscribe buttons should all change to Renew and the Buy buttons should all change to checkmarks:
There’s still something missing though – the subscription status isn’t clear. There’s no indication of how much time is remaining.
Open MasterViewController.m and directly below tableView:cellForRowAtIndexPath:
, add the following two methods:
|
The addition of these two methods creates a custom header for the table section so that the subscription information can be displayed at the very top of the tableview.
- First you create a
UIView
to become the section header, add aUILabel
subview and set its text to the expiration date string generated by the helper class. - Then you return the height of the header view.
Build and run. You should now see the subscription information in the header of the tableview section:
Note that this current design only works well if there’s a single thing you’re subscribing to – if you have multiple types of subscriptions in your app you’ll probably want to do things differently.
Restoring a Subscription
As a final check to make sure you’ve implemented everything correctly, delete the build from your device or Simulator and rerun the application. Log in as the same user and tap Restore.
Whoops! This button should restore a user’s purchases in the event that they have the same app on multiple devices, or if they delete and reinstall the app as you have. But you didn’t get your subscriptions back. Since you handle non-renewing subscriptions differently from consumables and non-consumables, you need to enhance the method that fires when a user taps the button.
Open MasterViewController.m. Find the restoreTapped:
method and add the following:
|
There are just a couple of simple steps:
- You determine if the current user is authenticated; if so, you query Parse to retrieve any expiration dates stored on the server.
- You save the most recent expiration date found on Parse in the
NSUserDefaults
object. You don’t care at this point whether the expiration date is valid, since thedaysRemainingOnSubscription
method handles that accordingly.
Now tap the Restore button again and make sure that all your goods have returned.
Where to Go from Here?
Here is the completed sample project for this tutorial.
Congratulations! You’ve now implemented every non-Newsstand in-app purchase type in your In App Rage app. You’re prepared for whatever business model you plan to integrate into your apps.
As mentioned in previous projects, for many simple apps this approach is more than sufficient. But if you want to take things even further and learn how develop a robust and extensible server-based system, check out iOS 6 by Tutorials.
I hope you enjoyed this tutorial – and if you have any questions or comments, please join the forum discussion below!