[ Music ]
[ Applause ]
Welcome to Best Practices and What's New with In-App Purchases, my name is Dana DuBois, I'm an App Store engineering manager.
In-app purchases are an essential part of how so many apps do businesses in the App Store.
Whether you have a subscription model where you're maybe a video streaming service or a newspaper or magazine or you offer consumables, content that the user can buy over and over again like in-game currency or non-consumables, something where the user buy once and uses from then on.
It's important to make that user experience great, this is your business after all.
So we're going to cover some best practices in order to help you make that experience great.
We're also going to talk about some stuff that we have been doing to make this great for you.
So first, I'm going to invite my colleague Ross up on stage, he's going to talk about Introductory Pricing, this is a feature we launched last December and it allows you for your subscription business to entice new customers in.
I'm going to give a quick update on trials in the App Store.
We're going to go over ratings and reviews and how your app can get customers to give feedback into the App Store.
I'm going to give an update on how you can use the Apple App Store Sandbox environment for testing your in-app purchases.
Ross is going to come back up and talk about processing transactions, this is when you're actually buying those in-app purchases.
And finally, I'm going to give you an overview about the in-app receipt.
So to begin here's my colleague Ross.
In iOS 11.2 and macOS 10.13.2 we added the Introductory Pricing feature to in-app purchase subscriptions.
This allows you to create a one-time use discount for new subscribers.
We set up introductory pricing in App Store Connect and each subscription can only have a single introductory price at a time.
When an eligible user purchases the subscription in your app the discount is applied automatically by the App Store.
Each user is eligible for a single introductory price based on the subscription group.
I'll go into that a little bit more later.
Along with Introductory Pricing we added several new types and properties in StoreKit.
These APIs reflect the data that you've set up in App Store Connect and you can use them to format your UI to display the terms of the subscription to your users, as well as to determine which users are eligible for the introductory prices.
For example, here's what it looks like in the App Store when you set up an introductory price on a promoted in-app purchase.
You can see that it clearly naturally lays out the terms of the subscription to the user, the first year for 19.99, then 39.99 for each year after that.
This is also a great benefit of promoting your in-app purchases that have introductory prices.
Users can see the discount before they even install your app.
Today I'm going to go over these new APIs with you and look at a couple ways you may want to set up your introductory prices.
First, there's a new property on SKProduct which is called fittingly enough introductoryPrice.
This is a new class that's called SKProductDiscount.
This class contains all the information about introductory price you've set up.
And as you can see it's an optional property that's because only subscriptions can have introductory prices and not all subscriptions will have them.
So let's dive into SKProductDiscount.
SKProductDiscount reflects the data you've set up about the introductory price in App Store Connect.
It has a price and a price locale property and these behave the same as the properties with the same names you're familiar with on SKProduct.
Also as a subscription period property, this is a new class called SKProductSubscriptionPeriod and this reflects all the data about the billing and renewal terms of the introductory price.
It achieves this with two properties of its own, the first is unit which is an enum that can be day, week, month or year.
The next is number of units.
So for example if you have a unit of month and a number of units of 2 that means that the introductory price renews every 2 months.
And back on SKProductDiscount there's also a numberOfPeriods property.
So this reflects the number of those subscription periods the introductory price is valid for.
Say if you have that 2-month introductory price, the number of periods is 3, that means the introductory price is valid for a total of 6 months.
Finally, on SKProductDiscount there's a payment mode property.
This is another new enum that has three values.
The first value is payAsYouGo, this behaves like a normal subscription where the user pays once per renewal period.
The renewal period for these does have to be the same as the renewal period of the base subscription you've set up the introductory price on.
So if you had a month-long subscription you couldn't have a 2-week long introductory price in this case.
You can use these types of introductory prices to offer your users a longer introductory duration but with less friction up front.
For example, if you have a 3-month renewing subscription that's billed at 9.99 you could set up a 6-month introductory price that renews twice at 1.99 each.
The billing period would look like this, the user purchases the subscription at the introductory price of 1.99 and receives 3 months of access.
After that they can renew at 1.99 for another 3 months of access.
When that's over they'll renew at the normal subscription terms of 9.99 for 3 months.
Here's what the data from StoreKit would look like in this case.
You can see the subscriptionPeriod has a unit of month and the number of units is 3, so it'll renew every 3 months number.
And then the numberOfPeriods is 2 giving us that total of 6 months of introductory.
The payment mode is payAsYouGo.
And the price for the introductory price is 1.99.
The next payment mode is payUpFront.
With this type of introductory price the user pays just one time and receives the entire introductory duration.
This is not limited to multiples of your subscriptions renewal period.
So in this case, you could have a 1-year renewing subscription and offer a 1-month introductory price.
So if we can take our previous example of our 3-month auto renewable subscription, this time we'll offer a payUpFront introductory price for 6 months.
Charging 3.99, so it's about the same overall cost as the previous example, but this time the user will pay 3.99 upfront and receive the entire 6 months.
After that's up we'll renew with the normal terms of the subscription at 9.99 for every 3 months.
Here's what the data from StoreKit would look like in this case.
The subscriptionPeriod's unit is still month, but this time we have 6 units.
The numberOfPeriods is 1 because it's a payUpFront introductory price.
The final payment mode is freeTrial.
This behaves the same as the old freeTrial behavior where the user pays nothing and receives the entire introductory duration.
This is available under durations varying from 3 days all the way up to 1 year.
Also in iOS 12 and macOS 10.13.2 we've added the subscriptionPeriod property to SKProduct itself.
This way you have access to the billing and renewal terms of all of your auto renewable subscriptions not just the introductory price.
Also, in iOS 12 and macOS 10.14 we've added the subscriptionGroupIdentifier to the SKProduct.
This is important because introductory pricing eligibility is based on the subscription group not the subscription itself.
Each user is eligible for a single introductory price per subscription group.
This is because subscription groups are meant to offer the same features or content just a different at renewal periods and price points.
For example, many apps offer both a yearly and a monthly option for the same content with the yearly one being less-expensive over time.
Now it wouldn't really make sense for a user to receive an introductory price to both the monthly and the yearly option for exact same content.
Since introductory prices are applied automatically by the App Store at the time of purchase it's important that your app correctly determines the user's eligibility when displaying the price.
For more information about determining eligibility you can see the engineering subscription session which is just after this one at 3 o'clock.
Now with these new APIs you'll have access to all the information you've set up in App Store Connect for your subscriptions, so your app can reflect any changes you make without updates to your binary or to your server code.
I'd like to invite Dana back on who has a few tips and improvements for you.
[ Applause ]
Thank you Ross, so Introductory Pricing is a great way to attract new customers if your business model is a subscription.
But what if it doesn't make sense for your app and your app's business model to offer a subscription service?
Well, starting today we're excited to announce that you can offer free trials for non-subscription based apps.
This will allow you to have a try before you can buy experience.
So how do you do this?
Well you take your payUpFront app and you make it free in the App Store.
And then you add a non-consumable in-app purchase to unlock.
So if your app is 9.99 in the App Store you're going to want to make a non-consumable in-app purchase of 9.99.
But it's important to let the user decide when to begin the trial period, so to do that you need to make a second non-consumable in-app purchase at price tier 0.
This will be a free non-consumable that the user will use to begin their trial period.
The naming convention of that free non-consumable should detail out how long that introductory period is.
So in this example it's 14-day trial.
Before the user begins their free trial, they should be clearly informed of how long that trial is, in your app's UI what the ultimate cost is going to be to unlock at the end of that free trial.
And finally, what features or content is going to be lost if they let the free trial expire and choose not to purchase the full unlock.
That information needs to be presented up front.
So this gives you a quick overview of the change, I'm going to talk more about how you can implement this inside your app when I go over receipts.
Right now however I'd like to give an update on requesting ratings and reviews.
Whether or not your app has in-app purchases if it's in the App Store you care about the feedback your users are going to give.
And there are ae few ways the App Store and StoreKit can get you that information.
First, in iOS 10.3 we introduced the SKStoreReviewController.
This is a powerful API and it allows an app to give a quick and easy prompt for the user to select a star rating or maybe even type in a review and get right back to using your app.
But with great power comes restrictions.
First, we limit the number of times that this prompt can appear per app, per device and per year.
Second, if users choose never to see these rating prompt they can just go into App Store settings and disable them altogether.
So with those 2 restrictions in mind what are some strategies you want to use for when to determine is the best time to call into the review controller.
Well first and foremost, don't interrupt.
If the user is in the middle of playing that level and fighting the big boss at the end of it it's not a great time to ask them to rate your game, wait until they're done.
You should also wait until both they've had enough experience with your app to actually have a good opinion about it, as well as wait until they've had a positive experience.
Maybe they just finished fighting that boss, maybe they just ordered food and it got delivered through your app, wait until that positive experience.
Finally, I mentioned before that we limit the number of times we will prompt this review her app, per device, per year, but that doesn't mean we do any sort of rate limiting.
So if within one session you call the SKStoreReviewController 3 times and the user keeps clicking not now they will see that 3 times in a row.
It's up to you to add your own rate limiting to your app.
So how does this look in code?
Well as I've been saying, wrap any call to the StoreReviewController around your own app's business logic.
Here's what you're going to want to check, is this a good time to do it, are they in the middle or have they just completed a task.
Have they just had a positive experience and have you prompted recently, that's where you want to have your check.
As soon as all of those pass you simply call SKStoreReviewController.
It takes no input and it gives back no output, it's a simple but powerful API.
We've gotten immense response from developers about these APIs, I'm happy to share a few with you.
Zappos have said they're seeing 10 times the amount of reviews that they used to using the SKStoreReviewController and it's giving them the confidence to know that they're delivering the right things to their customers.
Frosty Pop, maker of such great games like Kingpin Bowling and Ninja Attack have said that they're getting greater visibility in the App Store and because of a larger sample size they have a much greater and more relevantly engaged user base giving feedback.
I hope this type of feedback convinces you that you shouldn't be using your own prompts to request review you should move over to the SKStoreReviewController, it really has been making tremendous gains.
As I'm sure you saw yesterday, we announced a beautiful new Mac App Store and we know you guys are all going to rush off and make great new Mac apps.
And so with that we're excited to announce that we brought the write a review API to the Mac starting with Mojave.
[ Applause ]
So I mentioned there were a few ways you can get users to write reviews for your apps.
Well another way of doing it is you can deep link right into the App Store.
We also introduced this in iOS 10.3 and we're also bringing that to the Mac this year.
It's basically just a link right into your product page, you tell the App Store bring up the write a review sheet and the user can fill it out right then and there.
As an alternative to the SKStoreReviewController this works much better from any sort of UI you might have where the user has to take an immediate action.
So for example, if you had a button inside a settings page within your app basically asking the user to kindly write a review this would be a great time to use a deep link right into your product page.
So how does this work?
Well pretty simple, you take your product page URL and you add action=write-review to the end of it.
This informs the App Store to bring right up the write a review sheet.
If you don't know how to get a product URL for your app linkmaker.itunes.apple.com is a great facility for finding that.
You can get a lot more information about ratings and reviews, including how to respond to your customer reviews right on the developer website.
I'd like to do a deep dive into the App Store Sandbox next.
So what do I mean by the Sandbox?
Well first what don't I mean, I'm not talking about the app Sandbox, that part of the operating system in kernel that limits what resources your apps have access to.
What I'm really talking about is a dedicated environment, it's an entire copy of the App Store commerce engine on the server side that's there for you to test your apps and in-app purchases.
StoreKit knows when to go to the Sandbox environment based on how your app is signed.
So for example, you're building your app in Xcode, it's signed with the developer's certificate StoreKit knows right then and there take all of your requests to the Sandbox environment.
If the user downloads the app from the production App Store in that case it's going to be production signed.
StoreKit knows to go right to the regular production environment.
So what makes the Sandbox environment different?
Well first we don't charge, this is an environment for you to test your in-apps.
You're going to test them over and over and over again, there's no reason to charge.
Second, we have specific dedicated Sandbox accounts that you create in App Store Connect for your developer to use in the Sandbox environment, they're not the same as your regular iTunes in-apps accounts.
We also as I mentioned before have a completely different backend environment which means there's a completely different URL from when you're doing server to server receipt validation.
This is important to know if you're taking that development test receipt from your device, sending it to your QA server and wanting to have that go to the Verify Receipt endpoint for validation.
I'll talk more about that a little bit later on.
We also offer some test modes right in StoreKit.
So for example, SKReceiptRefreshRequest can take in an argument to get an expired receipt.
This way you can actually test hey what happens if I get an expired receipt later on.
We also have an ability for you to simulate what would happen if a kid is asking their parent for an in-app purchase.
We have simulatesAskToBuyInSandbox.
Probably one of the biggest differences is that when you're working with in-app subscriptions we can track down how long it takes to auto renew.
If you have an annual subscription in your app it would be ridiculous for us to make you wait a year to test your annual subscription renewing.
A general rule of thumb is 1 year in real time equals 1 hour in Sandbox.
You can also see that if you have a monthly subscription that's only 5 minutes.
We also limit the number of auto renews in Sandbox to 5.
So when you buy that initial subscription purchase you'll have 5 automatic auto renews working up this cadence and at the end it'll just stop.
This is to simulate what would happen if a user goes into managed accounts and disables that subscription, they just turn it off or what we call voluntary turn, they've decided they don't want to use your service anymore.
So how do set all this up, how do you work with the Sandbox environment?
Well first you go into App Store Connect, you create your users.
You create your products that you're going to sell, you're going to need to get that up on the server side before your app starts working with that.
You build and sign your app in Xcode just like you do all the time.
And you launch your app and you go find a product to buy.
And then when prompted you sign in with your Sandbox account.
Now if you're paying attention you might have thought I missed a step.
What would happen if I'm already signed in to my production app or iTunes Store, don't I need to sign out before using a Sandbox account?
Well starting in iOS 12 that's no longer the case.
[ Applause ]
Down here we've separated out your production account from your Sandbox account, you can manage it separately just like how we use the receipt to decide which environment to use we use the I'm sorry, just like we use the certificate to know which environment to use we know to use the Sandbox account when you're in development mode.
So look forward to that in iOS 12 I think it's going to make a huge difference for carrying these test apps on your personal devices for long-term testing.
So now I'm going to invite Ross back up and he's going to talk about some best practices, some things you want to keep in mind when processing your transactions.
[ Applause ]
I would like to go over a few common questions and scenarios we see and discuss the best way to handle them.
First things first, you should add your Transaction Observer to the default gaming queue as early as possible in the life cycle of your app.
This is a common issue we see where apps don't add a Transaction Observer until the user navigates to the in-app purchase UI or even until they begin a transaction.
In fact we recommend adding it right in the application didFinishLaunching WithOptions method of your AppDelegate.
Well why we recommend this.
Well the Transaction Observer is StoreKit's way of communicating changes in transactions the user is making in their app and pretty much all of these changes are important.
And it's a good user experience and generally good business to make sure you handle any transaction promptly.
There are a few cases when transactions can become interrupted.
For example, if the user leaves your app in the middle of a transaction, maybe they got caught playing games in class.
Your app could be terminated by the system later on or even by the user and then when it's opened back up again StoreKit won't know to continue the transaction until you add the Transaction Observer.
If the user decides to buy something else this could result in confusion when they receive two prompts or they could receive a prompt out of nowhere when you finally add the Transaction Observer again.
Not great [inaudible].
Another common case where the user needs to leave your app in the middle of a transaction is if they have to edit their billing info, this happens all the time.
You'd have to leave your app and you want to make sure to smoothly continue the transaction when they do return.
Finally, it happens your app can crash, in this case you want to make sure to smoothly continue as well.
Now there are some other reason as well that you want to make sure to add this as early as possible.
There are several types of transactions that can actually come from outside your app.
For example subscription renewals come through the Transaction Observer.
When an auto renewable renews successfully you'll receive a transaction in the payment queue.
You definitely want to make sure to receive this as early as possible so you don't interrupt the user's service when they actually have paid for it.
Also promoted in-app purchases, users click on these in the App Store and then the transaction is handed off to your app.
So you want to make sure to have a smooth transition there.
Finally, if you set up promo codes for your in-app purchases these are redeemed from inside the App Store and sent to your app as well.
Another question we get all time is when should I call Finish Transaction.
Well the general rule is you should call Finish Transaction after a transaction has completed successfully and you have downloaded and delivered all the content to the user or after the transaction has computed unsuccessfully.
To get more specific, let's go through all the transaction states and talk about how to handle them.
The purchasing state you don't need to do anything at the moment, just continue to observe the payment queue and wait for the state to change.
In the purchased state the transaction has completed successfully.
You should download and deliver all of the content to the user and then call Finish Transaction.
In the failed state the transaction has completed unsuccessfully.
You should inspect the error and handle it appropriately, maybe update your UI, record analytics, whatever you need to do, and then you should call Finish Transaction.
And it's also important to note here that transactions come back in the failed state if the user cancels them.
So it's very important that you do inspect the error and make sure it's not the canceled error because you shouldn't show any UI in that case.
You know the user just canceled they don't need to know that it failed.
The restored state, this is very similar to the purchased state.
It indicates the transaction has completed successfully, so again you should download and deliver all of the content to the user and then you should call Finish Transaction.
Finally, the deferred state, this is similar to the purchasing state except it means that the transaction is waiting on some outside action to continue.
For example, if a user has Ask to Buy turned on, so Ask to Buy is a feature that allows parents to manually approve or decline transactions made by their children.
Any user can have Ask to Buy set up, but it can happen to any of your in-app purchases so it is important that you handle this case appropriately.
Speaking of Ask to Buy, if your transaction is deferred due to Ask to Buy the user will receive a message from the App Store saying that their parent has been notified to approve it.
If it is approved and then it will be returned to your Transaction Observer in the purchased state.
You should let the user know it's been approved, of course deliver the content, and call Finish Transaction.
If it's declined it will be returned to your Transaction Observer in the failed state.
So here you should let the user know that it was declined and then call Finish Transaction to finish it up.
However, if there's no action taken within 24 hours then the transaction fails silently, this means that nothing will be returned to your Transaction Observer.
Furthermore, all Ask to Buy transactions within that 24 hours are consolidated into a single ask.
So the important takeaway here is that you should not wait on deferred transactions or even expect them to come back at all, you should make sure you don't lock the UI and allow the user to continue using your app as much as possible.
Another important tip when working with in-app purchases that have Apple hosted content via SKDownload is that you should always finish downloading the hosted content before calling Finish Transaction.
This is because once you call Finish Transaction all SKDownloads will be canceled and no longer valid for redownloading.
If you do this on accident then you have to call Restore Transactions to get a new download to finish.
So even if your download fails you should withhold calling Finish Transaction until you retry it and you actually successfully download it and deliver that content to the user.
Finally, if you're using Receipt Verification you should always make sure to do this before calling Finish Transaction as well.
This is especially important for consumable in-app purchases because consumable in-app purchases only appear in receipt as long as they're unfinished.
Once you do call Finish Transaction they will no longer appear in their seats and you won't be able to validate them.
So if you're using Receipt Verification make sure that it's a real Apple signed transaction the user paid for, always do this before calling Finish Transaction.
I'd like to hand it back to Dana to discuss some tips for managing the receipt.
[ Applause ]
Thanks again Ross.
So what is the receipt?
It's a lot like its name implies, it is a record of your app in in-app purchases.
It's a lot like the piece of paper you get when you buy some retail good.
It's stored on the device, it's a file, it's a file that we actually get from the App Store.
It comes from the server, StoreKit doesn't make it and we store it on the device and your app can actually read it.
It's signed by the App Store so that's how you know it's authentic.
And finally, it's for your app and for that device.
The receipt is an important way for making sure that that app is valid for the device it's trying to run on.
How do you validate the receipt?
Well there's two things you can do.
First, you can look at on-device receipt validation, this is where you can just use cryptography right on your device to validate that receipt and unlock content right then and there.
Alternatively, you can do server-to-server validation.
This is where you take the receipt, send it up to your trusted server, and it'll manage it for you.
In this case it'll call itunes.apple.com/verifyreceipt, send that receipt over and the App Store will validate it for your trusted server.
This is great if you have a subscription service on the backend.
However, I'm going to focus more for the purposes of my talk for on-device validation.
If you have a subscription service I highly suggest you check out engineering subscriptions here in Hall 1 today at 3 o'clock right after this talk.
But I do want to make one point about server-to-server validation.
As I said, it's completely correct, it's completely appropriate to take that receipt from the device and send it up to your server.
But you should never send the receipt from your device directly to Verify Receipt.
You don't control either end of that connection, you don't control the user's device and what connections it makes and you don't control the other end with the App Store.
There's no way to make a trusted connection from your app right then and there.
You should send it, always send it up to an intermediate server.
So what is the structure of the receipt?
Well it has some purchase information, the certificates and signatures.
We give you an API to pull it off the file system.
And as I said it's a single file.
We use opensource standards, PKCS Cryptographic Container and ASN.1 to store the metadata within the receipt.
These are all well documented and well published open file formats.
And there are tons of options out there for verifying and reading the receipt, OpenSSL, asn1c, there's tons more, you can even build your own.
How do you read that receipt no matter what API you're going to use?
Well you call bundle.appStoreReceiptURL, this returns a URL to your app, but again it's just a path to the file in the file system.
There you're going to want to read in that content, get that blob of data into memory, and if you're doing server-to-server validation send it up to your server, otherwise you can process it right there on the device.
When you are processing it if you're using OpenSSL here are some tips you want to follow.
First, build a static library not a dynamic one, it much more secure if you use a static library.
Finally, if you're going to include the Apple Root CA Certificate, which is what you need to validate the receipt you can grab that online, there's tons of documentation out there, but watch out for the expiration of that receipt.
So as I mentioned before there's tons of solutions out there for this, but keep in mind convenience comes at a price.
This is your business, you need to understand and know the risks that come to any solution you implement.
If you're using some popular third-party solution out there and there's an exploit within there your app is going to be vulnerable to that as well.
It's your revenue stream, make sure you understand what products you're choosing.
So I mentioned that there's a signature and there's certificates within the receipt.
One best practice is don't check the expiration date of those certificates within the receipt against the current clock.
First of all, the user can actually change the clock on the device, but more importantly the receipt isn't necessarily invalid just because the certificates within it are expired.
What you should be comparing it to is the dates of the transactions within the receipt.
As long as all the transactions occurred before the certificate is expired it's a valid receipt.
So let's dive in and get a sense of what is inside the receipt.
That purchase information contains just a bunch of types and attributes.
Here you can see there's a bundle identifier and a bunch of other values that go along with it.
If you're going to check that the receipt is for your application first you want to check that bundle identifier and you want to check type 3, which gives you the version that goes with the app.
But a best practice here is always use hardcoded values, don't just read from your app's Info.plist.
It's too easy to change the Info.plist to match an invalid receipt to spoof your app into running.
Now that you've matched that bundle identifier and version to the receipt you're going to want to make sure that it's valid for your device and the way to do this is to use attribute 5.
Attribute 5 is shawl SHA-1 hash of a couple values, including the bundle identifier, the device identifier, the thing that represents the hardware that is running your application, and an opaque value.
This is a bit of cryptic or [inaudible] entropy, it changes over time and we store that in the receipt as type 4.
You're going to take those 3 values, generate your SHA-1 hash and compare and if it matches what you generate with what's in the receipt you know that that receipt was generated for your app on that device.
Well what happens if you get an invalid receipt or it just doesn't exist for whatever reason?
StoreKit gives you APIs to request a new receipt from the App Store.
Again, the receipt comes from our commerce backend so you're going to need a network in order to be able to complete this operation.
In order to make sure that we are using an authentic receipt from an authentic user a sign-in may be required.
When you get an updated receipt you need to be careful to avoid any sort of continuous loop of validate and refresh.
If you get an updated receipt check it once and if it's still invalid error out then.
So what does this look like in code?
You simply call SKReceiptRefreshRequest, you set your purchase queue delegate right there on the ReceiptRefresh, you call start and as that receipt gets updated we'll call back and let you know when it's down.
On macOS you can also call exit 173, that 173 code will tell the operating system and tell StoreKit to refresh a receipt on your behalf.
There the app will get relaunched once a new receipt comes down.
As I mentioned earlier, we're now allowing non-subscription apps to do free trials and the receipt is a great way to understand is the user in the middle of a free trial or have they bought the full unlock non-consumable IAP.
To do this you're going to want to look for type 17 within the receipt.
This includes all of your subscription consumable and non-consumable IAPs.
Within type 17 there's a couple subdictionaries of data.
Type 1702 is going to be the product identifier that's associated with your non-consumable IAP.
This is where you're going to want to check to see is it my non-consumable that represents the beginning of the free trial or the non-consumable that represents the full unlock of the app.
You're also going to want use type 1704, this is where you can check the purchase date of when that consumable was actually made.
So what's the algorithm you're going to want to follow?
Well first, you're going to iterate over all those in-app purchases, all of those type 17's.
If you find any within the receipt that matches the product identifier that you've represented the Full Unlock of the app you're done.
You know that the user has purchased the Full Unlock of your app, give them the access to it.
Alternatively, if you find a Free Trial Product Identifier, the product identifier that represents the user beginning their free trial you're going to want to look at the purchase date associated with that free trial.
Here's where things get a little tricky.
You can't always trust the clock on the device, again the user can change that.
So you might want to use your own backend server or some sort of time server to make sure that it's still within your free trial period.
There's also device check APIs which I highly recommend you check out that help you determine whether or not the user has previously completed their free trial window on that device.
Lastly, if neither are present this is your signal to display that free trial upsell to begin the free trial, but again you need to be careful to make sure you represent the length of that free trial, the content that they're going to lose if they don't buy that Full Unlock, and the ultimate price of the Full Unlock.
If you already have an app in the App Store and you're selling it or you have a subscription model and you want to move to a new free trial there's some things you can also use the receipt for to make that easier for you.
So if you're paid up front and you want to move to a subscription or if you're paid up, paid up front and you want to move to a free trial use type 19.
This will tell you the original version that the user purchased that app with from the App Store.
Even if they delete the app and redownload it over and over again that type 19 will tell you exactly what version they bought their app with.
If the user did originally pay up front make sure to give them the functionality that they bought.
Just because you move to a subscription model if they bought it when it was a paid for app in the App Store you need to give them that access to that content they originally paid for.
So again use type 19 within the receipt to know what version they bought that app with.
So we went over a lot of things today, we talked about Introductory Pricing, this is a great means of enticing new customers into your subscription, you can show content right on the App Store if you're merchandising your subscription IAPs.
I also talked about how you can now offer non-subscription free trials within your apps.
We talked about SKStoreReviewController and how developers have seen such a great response to that and how we brought it to the Mac App Store this year.
Talked about the Sandbox environment and how now we manage your Sandbox account separately from your regular iTunes and App Store production accounts.
Ross gave a great overview of things you need to keep in mind when processing your transactions, including making sure to observe early and knowing when the right time to call Finish Transaction is.
And finally, I gave you some good points on how to manage that in-app receipt for on-device validation and making sure your free trials go smoothly using the receipt.
For more information I highly suggest you stick around for Engineering Subscriptions if you have a subscription business model.
We're also going to be at the labs today at 4 o'clock and then Thursday morning at 9 a.m. We'd love to get your questions and help you out as best we can.
Thanks so much for coming.
[ Applause ]