Transcript
[ Music ]
>> Good afternoon.
[ 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.
>> Thanks Dana.
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.
Excuse me.
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.
requestReview.
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.
Ross.
[ 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 ]