How to Easily Configure In-App Purchases into a Mobile Application?
A Tutorial, Containing Step-By-Step Instructions (With Pictures and Code Samples)
Today we will talk about the implementation of built-in purchases into an application, the so-called in-app purchases. As a guinea pig I’ve chosen the “Plan To Table” application that helps to select dishes. So let’s see how it goes!
First let's define what exactly we put into this concept. In-app purchases are those purchases that users can make inside your application. Moreover, they are supported and implemented directly through the App Store.
Why talk about it at all?
I would single out not one but several reasons why I think that the material of this tutorial will be useful for you:
- the version of the “Plan To Table” app was approved only after the 5th moderation on the App Store;
- while implementing the in-app purchases, I encountered many small, uncomplicated, but completely non-obvious procedures that needed to be done;
- sooner or later you most likely will still meet in-app purchases and the material of this tutorial will come in handy, containing step-by-step instructions.
Why In-App Purchases?
This is a very good question! Why do we need them, if the App Store commission is 30%? It’s huge, no matter how hard you look! You won’t find such commissions anywhere else.
Well, the whole point is that by paying money through the in-app purchases the user does not provide the application with any kind of payment data. Thereby absolutely protecting himself from unfair developers who want to illegally use his payment details.
Thus, no matter how small, new, trustworthy and funky in general your application is the user can be sure that using the built-in purchases inside it nothing more than the amount he is going to pay will be taken from him. And this is a worthwhile argument, even if it’s the only one.
For example, for the “Plan To Table” application there was a choice between in-app purchases and payment through the Stripe payment aggregator. The Stripe commission is 3% + $0.30. But in the end, the choice was made in favor of in-app payments because users are more loyal to applications that do not require entering their billing information.
Let’s begin!
To create the test project we will use the following technologies: AngularJS 1.x, cordova, cordova-plugin-inapppurchase.
So, let’s begin creating the project! For a start, we need a developer account on developer.apple.com.
Go to the Agreements, taxes and banking transactions. In this section, bank, tax, and contact information must be completed and approved.
Correctly filled information should look something like this:
#1. Registering a new application
The first step is to select the package ID. Let’s name it “com.iap-service.app”. Then go to the developers’ portal and register it just like this: developer.apple.com/account/ios/certificate/ -> “Identifiers” -> “App IDs” -> “+”.
Also, don’t forget to enable the “In-App Purchase” for the created “App ID”.
After the registration, we receive:
App ID Description: Purchase ServiceIdentifier: SG5B7ZUFY4.com.iap-service.app
To add a new application, go to itunesconnect.apple.com in the “My Apps” section, then click “+” -> “New App”. In the opened window, fill in the information about the project (package ID — registered com.iap-service.app).
After creating the application, it automatically opens on the next page. After entering some additional information, go to the “Functions” tab -> “In-app purchases” -> “+”.
#2. Creating in-app purchases
Ok, here we go! In the opened window, we have to choose the type of the in-app purchase from the following:
- Consumable purchase (game gold)
- Non-expendable purchase (specific race track)
- Subscription with automatic renewal
- Subscription without automatic renewal
Looking ahead, I should note that Apple in addition to selling the goods that are present in the application independently monitors so that the user doesn’t pay several times by mistake for the already paid goods (except for consumable purchases, the number of which is not limited, of course).
Now let’s choose a subscription with automatic renewal.
After creating a subscription, add the subscription information: duration, free trial period, price.
Take a look at our subscription:
Now we should add the missing metadata. Let’s add localization for the subscription group and for the subscription itself:
Then it is necessary to load an application screen picture with the size of 1242*2208 for verification and write some small note next to it. Pay attention to the “Cleared for Sale” checkbox, don’t forget to tick it!
So now our purchase has the status “Ready to send”, which suits us.
We will receive the created list of the in-app purchases in our application (meanwhile consisting of one subscription). Therefore it is important to distinguish purchases one from another and obtain all the info about them. At any time, you can edit the description of the in-app purchases to add the necessary and current information. You need to know that the purchase object received from the App Store has 4 fields:
productId
title
price
description
Okay, enough with Apple.
#3. Registering test users
After we created the application and built-in purchases, we definitely need test users. Yes, not one, but several at once. Test users are special because when they choose to pay with the in-app purchases, real money isn’t drawn from their card.
To do it just go to the website https://itunesconnect.apple.com/ and choose the “Users and roles” panel.
Fill in the form with random data. In this case, please specify the mail that you did not registering before (any address that was not previously specified by anyone). In addition, I highly recommend first register the users from the USA. Then you can experiment with other countries.
Finally! We’ve managed to select an adequate email address and password after several attempts!
#4. Creating and installing certificates and profiles
Well, all we need is the “Production certificate” and respectively the “Distribution profile”:
After adding the certificate and devices to the appropriate section, we need to create the required profiles. In fact, now we are only interested in one profile.
Then we sign this profile with the certificate created above and import both of them. In the end, we need to choose them in the x-code settings.
#5. Adding the in-app purchases to the application
Let’s move on to the app creation!
To simplify the work, we download a specially prepared almost empty project:
git clone https://github.com/ShepelE/angular-blank
To add the built-in purchases to our application, first add the plugin:
cordova plugin add cordova-plugin-inapppurchase@1.1.0
Also, add functionality to use it. The documentation for the plugin can be found here: https://github.com/AlexDisler/cordova-plugin-inapppurchase.
For starters, we need available products. For this, create InAppPurchasesService
and add the loadProducts()
method to it, which will be called directly in app.js
on onDeviceReady
. Thus, we won’t waste time loading products later.
We need a list of their productId
to get a list of products from the App Store.
Let’s add them to the configuration that we will connect with the service.
angular.module('inAppPurchases.config', [])
.constant("IN_APP_PURCHASES",
{
iOS: [
'monthly_subscription'
],
Android: []
}
)
Here is how the code looks like:
var _self = this;// to save a list of in-app purchasesvar _IAP = [];var _subscriptions = [];var _receipt = {};//returns link to _IAP, so we don't care when _IAP will be updated :)this.getProducts = function () {return _IAP;};this.getReceipt = function () {return _receipt;};this.getSubscriptions = function () {return _subscriptions;};// this method is called in app.js, this is the first init callthis.loadProducts = function () {var _productIds = IN_APP_PURCHASES[device.platform];// get available productswindow.inAppPurchase.getProducts(_productIds).then(function (products) {if (products.length) {products.forEach(function (product) {var productInfo = {id: product.productId,name: product.title,price: product.price,// we don't use full desc, we use only first sentance of descdescription: product.description.substring(0,product.description.indexOf('.')),checked: false};_IAP.push(productInfo);});} else {// empty array - no available products}_self.getActiveSubscription();return _IAP;}, function (err) {// error getting a list of in-app purchases_self.getActiveSubscription();$q.reject(err);});};
Now we have a list of products that we can offer to users.
However, it would be nice to have an opportunity to monitor the status of user subscription. What if he was already issued one? By the way, here it should be noted that if you try to force a user to subscribe again — it will not work. To be fair, the App Store takes the duty to monitor everything too. Therefore, a user can subscribe or make a purchase just once.
Where is the code that knows about the subscription? Again, it’s all in app.js
. Therefore, after receiving a response or an error, we will add a call method to get an active subscription.
// here we get all bought products to be able to define// if need to redirect user to payments or not// this method saves results locally// has only resolve functionfunction _restore () {return window.inAppPurchase.restorePurchases().then(function(data) {// iOS provides the receipt isolated from the restore dataif (window.device && window.device.platform === "iOS") {return window.inAppPurchase.getReceipt().then(function(receipt) {FunctionService.cloneSafe(receipt, _receipt);return _receipt;}, function (err) {//returns _receipt anywayreturn _receipt;});} else { // Android provides the receipts with the restore dataFunctionService.cloneSafe(data, _subscriptions);return _subscriptions;}}, function (err) {//returns _subscriptions anywayreturn _subscriptions;});}
To receive active subscriptions on both iOS and Android, we use the _restore()
function, which asynchronously calls inAppPurchase.restorePurchases
. The difference is that for iOS you also need to call the inAppPurchase.getReceipt
method. The results of _restore()
are processed in the getActiveSubscription
.
// in app.js loadProducts is called,// so call restorePurchases() there, in loadProducts// because they can't be called together// here we use results from _restore() to define if need to redirect user to payments or not// has only resolve functionthis.getActiveSubscription = function () {// _restore doesn't have rejectreturn _restore().then(function (subscriptions) {//we are able to not use result, because we have it's local copyswitch (window.device.platform) {case "iOS":// return false or subscription cut objectreturn _receipt.purchaseState === 0&& {productId: _receipt.productId, receipt: _receipt};break;case "Android":var _activeSubscriptions = _subscriptions.filter(function (subscription) {return subscription.receipt.purchaseState == 0;});// return 0 or subscription objectreturn _activeSubscriptions.length && _activeSubscriptions[0];break;default: //this shouldn't be used because of _checkDevicereturn false; // return falsebreak;}});};
And we will place the call of this method in the loadProducts
method after receiving the result or an error. The result of this method can be:
- false, in case of an error or lack of active subscription;
- the object of active purchase, in case of successful subscription.
After saving the results in the service, we will use them after a successful login:
//if success - choose state to go tofunction _onSuccessLogin () {// check gotten active subscriptionif (!window.device|| !window.device.platform !== 'iOS'|| !InAppPurchasesService.getReceipt().productId) {$state.go('termsOfUse');} else {IonicPopupWrapperService.confirm({template: 'Would you like to subscribe?',ok_cb: function () {$state.go('inAppPurchases');}});}}
Thus, we have achieved that a user who does not have a subscription will immediately receive an invitation to subscribe. A user with an active subscription will be asked whether he wants to subscribe (once again).
When the InAppPurchasesCtrl
controller is initialized, we get the list of products stored in the service.
And now a small reservation: App Store requires a complete description of the in-app purchases.
Approximately the same text should be shown in the confirmation pop-up when you try to subscribe and pay for the subscription:
As a rule, such text is successfully tested by the App Store. Therefore, let’s display not just the names or amounts of subscriptions, but subscriptions with a full description on the webpage:
<ion-list class="list"show-delete="false"can-swipe="false"><ion-item ng-repeat-start="purchase in IAP"ng-click="check($index);"><i class="radio pt-color-balanced"ng-class="{'ion-ios-circle-outline':!purchase.checked, 'ion-ios-checkmark':purchase.checked}"></i><span>{{purchase.description}}</span></ion-item><span ng-repeat-end="">Subscribe to Purchase Service {{purchase.description}} for {{purchase.price}} per month.The subscription will give user a full application functionality.This subscription will automatically renew until canceled.</span></ion-list>
It is very important to block the “Submit” button from double clicking. As at payment the user will see the whole series of information pop-ups from the App Store. Moreover, who knows how a user will react to the unexpected error messages! You understand…
Finally, when the user confirms his consent to subscribe, we call the InAppPurchasesService.subscribe()
service method.
Having cut out all unnecessary buns from the similar method used in “Plan To Table”, we will receive:
this.subscribe = function () { return inAppPurchase .subscribe(_product.id) .then(function (data) { _restore (); return data; });};
Simply passing the ProductId
to the plugin method, in the future we will update the list of subscriptions stored by the service. App Store generously provides all sorts of pop-ups about success and mistakes.
That’s it! Now the only thing that remains is to submit the application to the App Store.
Let’s make an application build:
$ gulp$ сordova plugin add cordova-plugin-statusbar$ cordova plugin add ionic-plugin-keyboard$ cordova plugin add cordova-plugin-device$ cordova plugin add cordova-plugin-splashscreen$ cordova plugin add cordova-plugin-network-information$ cordova plugin add cordova-plugin-code-push@latest
The in-app purchases plugin is already installed.
$ cordova platform add ios$ ionic resources$ cordova build ios
Don’t forget to enable the in-app purchases in the “Capabilities” settings in X-Code!
#6. Terms of Use & Privacy Policy
And it will be rejected!.. That’s because you need to add 2 more pages to the application: “Terms of Use” & “Privacy Policy”!
As for these two pages, I think that the best option is to store the marked texts in the database. If necessary, you can change them. Notes on reading by users are also stored in the database and, if necessary, they can be erased.
Actually, here are the verified texts themselves (just find the “Plan to Table” piece and replace it with the name of your application).
[
{
"id": 1,
"name": "termsOfUse",
"content": "<p class=\"pt-bold\">Last updated: 11.01.17</p><p>Please read these Terms of Use (\"Terms\", \"Terms of Service\") carefully before using the Plan To Table app operated by Ron Wilson</p><p>Your access to and use of the Service is conditioned on your acceptance of and compliance with these Terms. These Terms apply to all visitors, users and others who access or use the Service.</p><p>By accessing or using the Service you agree to be bound by these Terms. If you disagree with any part of the terms then you may not access the Service.</p><p class=\"pt-bold\">Termination</p><p>We may terminate or suspend access to our Service immediately, without prior notice or liability, for any reason whatsoever, including without limitation if you breach the Terms.</p><p>All provisions of the Terms which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability. Create your own Terms of S</p><p class=\"pt-bold\">Links To Other Web Sites</p><p>Our Service may contain links to thirdparty web sites or services that are not owned or controlled by Plan to Table</p><p>Plan to Table has no control over, and assumes no responsibility for, the content, privacy policies, or practices of any third party web sites or services. You further acknowledge and agree that Plan to Table shall not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such content, goods or services available on or through any such web sites or services.</p><p>We strongly advise you to read the terms and conditions and privacy policies of any thirdparty web sites or services that you visit.</p><p class=\"pt-bold\">In-app purchases</p><p>Our Service provide in-app subscriptions. After registration user will have 14 days free trial period. After that our app will ask to make auto renewable subscription. It will cost 9.99 USD per month, and will be charged automatically for every month.</p><p>– Payment will be charged to iTunes Account at confirmation of purchase</p><p>– Subscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period</p><p></p><p>– Account will be charged for renewal within 24-hours prior to the end of the current period, and identify the cost of the renewal</p><p>– Subscriptions may be managed by the user and auto-renewal may be turned off by going to the user's Account Settings after purchase</p><p>– Any unused portion of a free trial period, if offered, will be forfeited when the user purchases a subscription to that publication, where applicable</p><p class=\"pt-bold\">Governing Law</p><p>These Terms shall be governed and construed in accordance with the laws of Country USA, without regard to its conflict of law provisions.</p><p>Our failure to enforce any right or provision of these Terms will not be considered a waiver of those rights. If any provision of these Terms is held to be invalid or unenforceable by a court, the remaining provisions of these Terms will remain in effect. These Terms constitute the entire agreement between us regarding our Service, and supersede and replace any prior agreements we might have between us regarding the Service.</p><p class=\"pt-bold\">Changes</p><p>We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material we will try to provide at least 30 days notice prior to any new terms taking effect. If you do not agree to the new terms, please stop using the Service.</p>",
"createdAt": null,
"updatedAt": null
},
{
"id": 2,
"name": "privatePolicy",
"content": "<p class=\"pt-bold\">Last updated: 11.01.17</p><p>Plan to Table, operates by Ron Wilson. This page informs you of our policies regarding the collection, use and disclosure of</p><p>Personal Information we receive from users of the App.</p><p>We use your Personal Information only for providing and improving the App. By using the App, you agree to the collection and use of information in accordance with this policy.</p><p class=\"pt-bold\">Information Collection And Use</p><p>While using our App, we may ask you to provide us with certain personally identifiable information that can be used to contact or identify you. Personally identifiable information may include, but is not limited to your name (\"Personal Information\").</p><p class=\"pt-bold\">Log Data</p><p>Like many App operators, we collect information that your sends whenever you use our App (\"Log Data\").</p><p>This Log Data may include information such as your device Internet Protocol (\"IP\") address, the pages of our App that you visit, the time and date of your visit, the time spent on those pages and other statistics.</p><p>In addition, we may use third party services such as Google Analytics that collect, monitor and analyze this.</p><p class=\"pt-bold\">Communications</p><p>We may use your Personal Information to contact you with newsletters, marketing or promotional materials and other information.</p><p class=\"pt-bold\">Security</p><p>The security of your Personal Information is important to us, but remember that no method of transmission over the Internet, or method of electronic storage, is 100% secure. While we strive to use commercially acceptable means to protect your Personal Information, we cannot guarantee its absolute security.</p><p class=\"pt-bold\">Changes To This Privacy Policy</p><p>This Privacy Policy is effective as of (add date) and will remain in effect except with respect to any changes in its provisions in the future, which will be in effect immediately after being posted on this page.</p><p>We reserve the right to update or change our Privacy Policy at any time and you should check this Privacy Policy periodically. Your continued use of the Service after we post any modifications to the Privacy Policy on this page will constitute your acknowledgment of the modifications and your consent to abide and be bound by the modified Privacy Policy.</p><p>If we make any material changes to this Privacy Policy, we will notify you either through the email address you have provided us, or by placing a prominent notice on our website.</p><p class=\"pt-bold\">Contact Us</p><p>If you have any questions about this Privacy Policy, please contact us</p>",
"createdAt": null,
"updatedAt": null
}
]
#7. IPv6
Finally, if the application works correctly, the App Store will definitely approve it. But if you have a backend, but there is no way to reach it through IPv6 — you’ll most probably get another rejection…
#8. Canceling automatic subscription
How can a user cancel subscription in your app? I’m glad to answer this question straightaway! It’s possible on Android, but not trivial. On iPhone, it’s impossible. The most that you can do for your users if they want to unsubscribe is to redirect them to the right page where they just need to press one button and the subscription will be canceled.
No, the subscription can not be stopped until the expiration of the paid period. Yes, after canceling the subscription the user will still be able to use your application until paid period is over.
Conclusion
Never promise to submit the app tomorrow! Especially when it has in-app purchases. If someone asks you why, just give a link to this article.
Therefore, submitting from the third time is also very good!
Enjoy our experience! We are happy to be useful!
If you liked this, show your support by clapping us to share with other people on Medium.
Follow us on Facebook, Instagram, LinkedIn, Behance, Medium and visit our corporate blog for more news and articles on smart solutions.
Any questions? Feel free to contact us!