WWDC2016 Session 702

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Music ]
>> Hello.
[ Applause ]
>> So my name is Dana DuBois.
I'm an engineering
manager on the team
that writes the frameworks
that help power the App
Store on iOS and tvOS.
And today we're going
to talk about one
of those particular
frameworks, and that's StoreKit.
Many of you know,
StoreKit's been
around for a long, long time.
It was introduced in
iOS 3 for the iPhone.
Since then it's been used by
thousands and thousands of apps
to power all sorts
of business models.
Everything from newspapers
and magazines, games,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Everything from newspapers
and magazines, games,
even dating apps
are using StoreKit.
It's really widely used.
And we've talked about
StoreKit a lot at WWDC,
but this year we're going
to do something a little new
and we're going to talk
about it using Swift.
So that's really exciting.
We've got some new APIs.
There's a couple other things
that are new, so Swift API,
so that's really great.
Last week, many of you probably
also heard that we have a lot
of big announcements
around subscriptions.
This is something we're
really excited about as well.
I think a lot of developers are
going to end up taking advantage
of these new enhancements
with subscriptions.
So I'm going to give you
a quick overview on them.
First up, categories.
We're really you know
excited to announce
that category will no longer
be a factor if you're choosing
to use auto renewing
subscriptions
in your applications.
There are still going
to be criteria behind how
you can use auto renewing
subscriptions, but category
will no longer be a factor.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
subscriptions, but category
will no longer be a factor.
Second, everyone's got
to be excited about this,
we're giving more
proceeds to developers.
If you keep your
subscribers for more
than one year, that's
really great.
And I think that's
going to be a big deal.
[ Applause ]
Pricing. A lot of control
now we're going to be giving
to developers through iTunes
connect to set the pricing
for their subscriptions
based on territory
to territory, region to region.
If there's a business reason you
might need to adjust the price
in Europe compared to the
US, there's a lot of control
that you now have over that.
And then along with pricing,
we're giving you the ability
to save the price, to
preserve the price for users
who may have already been
using your subscription while
adjusting it for new users.
So, if you have early adopters
that you want to reward
and you want to keep them using
your subscription, but you want
to adjust the price for newer
users, you have that power now.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to adjust the price for newer
users, you have that power now.
And then upgrades
and downgrades,
this is us giving the
user a lot more power
to control exactly what level
of subscription they might
want within your service.
So if they want to go up
to the platinum level,
or go back down to the basic
level, they can do that right
in the Manage Subscription
UI right on the device.
So that's a quick overview of
what's new in subscriptions.
There's a lot more to it.
I think if you're going
to have subscriptions
in your application,
you really should go
to Introducing Expanded
Subscriptions in iTunes Connect,
that's at Pacific
Heights today at 4.
Really, they're going to get
a lot more in-depth on how
to set this up, how to manage
this in your application
and I really highly
recommend you go to that.
So that's subscriptions.
There's one other new thing that
I'm really excited to announce,
and that's iMessage apps.
We announced that
yesterday at the keynote.
We're bringing apps, a whole
new class of apps right
into the iMessage app, iMessage.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
into the iMessage app, iMessage.
You'll be able to build custom
extensions, and we are going
to allow in-app purchases
right in those extensions,
right in iMessage aps using the
exact same StoreKit APIs you
would use in a normal
application
that you have in
the store today.
So if you have additional
content that you want to market
or provide right inside
the iMessage experience,
that's now supported.
And this will all be
available in iOS 10,
so that's something we're
really excited about
and I think developers are going
to take a lot of
advantage of that.
So as I said before, StoreKit's
been around for a while.
People have been using in-app
purchases for a long, long time,
but maybe some of
you are new to it.
So I'd like to just
give a quick overview
of what exactly in-app
purchases are.
In-app purchases are
digital content or service
that can be bought right
inside your application.
However, to be clear, it's
not for physical goods.
There's other ways
of doing that.
This is really just about
digital content or services.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This is really just about
digital content or services.
Now when we're talking about
digital contents or services,
there's a bunch of
different types.
When you're configuring this in
iTunes connect, you have a lot
of options and you
need to make sure
that you choose the right
type that fits your content.
So let's take a look
at what those are.
Consumable products.
Everybody's played a game and
they have coins in that game,
currency, or gas in your
racing car, or something
that they user's going
to buy, and buy again,
and buy again, and
use up over time.
That's what a consumable
purchase is.
A non-consumable product is
something that sticks around,
it will stick around for as long
as the user wants to use it.
They'll be able to restore it.
They'll be able to move
it from device to device.
So that's a little different.
And again in a game
you might have a sword,
or you know some sort of weapon,
or you know a racing car,
or a level, or if you
have a utility app,
you might have a basic edition
of your app and you might want
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you might have a basic edition
of your app and you might want
to offer a pro-edition
of your app.
That's kind of a
non-consumable product.
We also have two
types of subscriptions
when we're talking
about subscriptions.
There's non-renewing
subscriptions,
as the name suggests
these are subscriptions
that don't automatically
renew, it's up to you
and your application, and
your back end to manage how
to renew those subscriptions.
But then we also have auto
renewing subscriptions.
And these are subscriptions that
Apple will bill to the customer
on a periodic basis, based
on what you configure.
So if it's a monthly
subscription,
we'll bill the user every month,
as long as they continue to opt
into that subscription.
So these are the types,
let's get into the heart
of adding in-app purchases
to your application.
So, what I'm going to do is I'm
going to do a quick overview
of all the various steps,
and then we're going to dive
into each one and
we're going to talk
about how to do this in Swift.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
about how to do this in Swift.
And you know exactly what you
might need to worry about as far
as pitfalls, or any concerns
you might have along the way.
There's a lot of things that
can get kind of tricky sometimes
when you're developing
against StoreKit.
A couple of things
to keep in mind.
So first off you're
going to want to figure
out exactly what it is
that you're going to sell
to the consumer, the
user of your app.
So this is done just by
determining the identifiers,
those in-app identifiers
of what you're marketing
to the user right
then and there.
Once you have the identifiers,
you go off to the App Store
and you fetch the information,
localized product
information that's related
to those identifiers.
And that's key.
This is localized information.
This is a global marketplace,
you want to make sure
that you're displaying
the information
to your users in
a localized way.
And now once you have that
information you're going
to show off the products.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to show off the products.
You're going to market them.
You're going to sell them.
These are your products, this is
your application, you're going
to build the best UI you
can to show them off.
The users then are going to be
enticed to make that purchase.
They're going to be you
know, sold, and they're going
to select the product
right then and there.
But it's up to you to
then create a payment
for that product and add
it to the Payment Queue.
And we'll be talking a lot more
about the Payment
Queue as we go on.
As the payment is going through
the Payment Queue your app will
be notified about that
transaction and you're going
to have to make some
response to it,
and that's how you
process these transactions.
Finally, when the payment is
completed, you're going to make
that product available.
Give the user what
they paid for.
And then finally tell the
Payment Queue you're done
with the transaction,
everything's completed,
the product's available,
so that's finishing
the transaction.
So those are all the different
steps you need to take.
Real quick.
We're going to dive into each
of those as we go through,
but first special note
about the Payment Queue.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but first special note
about the Payment Queue.
The Payment Queue is the center
of your in-app purchase
implementation.
It's the source of truth
for state about purchases
and payments as they're
going through all the way
from when the user selected that
product to when it's paid for.
And it's important to keep
in mind that you should rely
on the queue and only the queue
to know about transactions
that are progress,
payment updates,
and if you're using
hosted downloads,
the Payment Queue will tell
you all about those downloads.
And then another thing
to keep in mind is
that if the Payment Queue
tells you about a payment,
it's a valid and real payment.
In fact, there are
cases we've seen
where developers might
have their own side cache,
where they've noticed that a
user has clicked on a product
and they're managing
state about that payment.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then their app will crash,
or something will
happen along the way,
the user will close the
application before finishing the
payment, and they'll come back
into the game, or whatever it is
that they were purchasing
then there.
And they don't necessarily
respect the response coming
from the Payment Queue
because they weren't listening
to those payment transactions
because their own queue no
longer had them in state.
So you should really just
rely on the Payment Queue
to tell what's going
on with those payments
as they're happening.
And in fact it's very
important and we're going to get
into some code here, to listen
and observe the Payment Queue
right when your app starts up.
This can be done in the
example we have here,
we have
didFinishLaunchingWithOptions is
the first thing that
happens with your application
when it starts up and we create
a payment transaction observer,
in this case it's the
app delegate itself.
And we set it right
on the Payment Queue,
we add it to the Payment Queue.
And then if there's
anything in the Payment Queue
that may have been there
before our app launched,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that may have been there
before our app launched,
we'll get notified
about it right away.
Another kind of case that comes
up is there are applications
that have in-app purchases
where they have redeemed codes.
So the user might have
actually gone into the App Store
and redeemed that in-app
purchase completely outside
of your app.
The user then is exciting about
consuming that in-app purchase,
they launch your application,
and if they're not listening
to the Payment Queue that
product won't become available.
You got to do it right
when the app starts up.
So that's a quick note
about the Payment Queue.
Let's get back into the process
and understand exactly how
to add this to your application.
So the first thing you want
to do is load the identifiers
for your in-app purchases.
These are the same identifiers
that you set up in
iTunes Connect.
You define them, you name them.
And you just need to
get a list of them.
There's a couple
different ways of doing it,
if your app is very simple,
you might just bake
the identifiers right
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
you might just bake
the identifiers right
into your application.
If you have an application
that has a basic version
and a pro version, you might
just have one consumable
and you just baked it right
into your application.
However, we've seen more and
more applications out there
where they're interested
in going out
and fetching those in-app
identifiers from a host
that they provide so that
over time maybe they can
change what they're marketing
to the users, or exactly
what appears where.
That's okay to do.
Completely valid to do.
The thing we want to call
out is if you're doing
that make sure your host
is scalable and reliable.
This is the beginning of
your in-app purchase process.
The worst thing to show a user
right now is just a spinner
while they're waiting
to make a purchase.
This should be, and if you can
fetch in advance, that's great.
If you can cache on the
device that's even better.
Be very cognizant of
how you are fetching
and preserving these
identifiers.
Now that you have the
identifiers you need to go
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now that you have the
identifiers you need to go
to the App Store to fetch the
localized product information
associated with those products.
This can be done with
an SK Product Request.
You pass in the identifiers
that you're interested in.
Again, for the smoothest
experience possible,
you want to do this in advance.
You want to anticipate when
the user might be interested
in looking at your
in-app purchases,
and have that information
available ahead of time.
So first thing you do, create
your SK Product Request.
You pass in the identifiers
you're interested in.
You set your delegate.
This will give information
back to your application
about when those
products are loaded.
And then you just call start.
That simple.
The delegate looks like this.
We have product request,
didReceive response.
This will pass in
all of the products
that you requested
from the App Store.
And each of those products
will retain information
like a localized
title and description.
As I said before this is
a global market place,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
As I said before this is
a global market place,
you're marketing your in-app
purchases around the world.
So it's important to make sure
that you're actually using
localized information
to make your sales.
So localized title
and description,
even more important is
price and price locale.
You want to present the user the
price of your in-app purchases
and you want to make
sure it's presented
in a format that
they understand.
Currency is represented
differently around the world
and you know it's important
that you do it right.
We'll get into that in a moment.
And then finally if you're
hosting your in-app purchase
content, this is something that
you've set up in iTunes Connect,
we actually give information
about the download
contact length
and versions associated
with those purchases.
So as I mentioned the
price, it's very important
that you do the right thing when
presenting those price strings.
When you're presenting those
prices, you want to use
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
When you're presenting those
prices, you want to use
that price locale and pass that
into an NS number formatter,
and that will get
you information
about that pricing information.
That NS number formatter, will
take that price information,
take the locale associated
with it.
If you set it to a currency
format, you can make sure
that you're getting that
pricing information correct.
The other thing you want to do
is to not bother doing any sort
of currency conversion
of your own.
The App Store knows
how to localize,
convert those currencies.
So you don't need to
bother with that at all.
Just present the price using
the NS number formatter as is.
The next step after that
is to present your UI.
Not really going to talk
too much about that,
these are your products,
these are your applications.
You know how to present
them in your UI
to make the best
sales pitch possible.
However, once the
purchase has been complete,
or once the UI has been shown,
we're going to jump right
into making the purchase itself.
And that can be done by adding
and creating an SK
payment object
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and creating an SK
payment object
and adding it to
the Payment Queue.
Once you've created that SK
payment object and add it
to the Payment Queue,
you'll get a callback
as the payment is
being processed
through updated transactions.
So this is that Payment Queue
Observer you added right
when the application started.
You created your
Payment Queue Observer.
It called Payment Queue
updated transactions.
And you'll get information
about the payment as it's going
through the process so all
you did was you created your
SK payment.
Added it to the Payment Queue.
The information started
coming in and you're going
to get information about all
the transactions in process.
You might have one,
you might have many.
You should iterate through those
transactions and then listen
to the state for each
of those transactions.
It's a couple different states,
the one you're probably
most interested
in is the purchase state.
This is where you know that
the purchase was completed
and should go ahead and
validate that purchase,
we'll get into that
in a little bit,
and make that content available.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and make that content available.
However, there's one other state
I'd like to talk about as well
and that's the deferred
transaction state.
Deferred was added
back in iOS 8 and it's
for the Ask to Buy feature.
This was to allow
iCloud families
where children could
attempt to make a purchase,
such as an in-app purchase,
and their parent or guardian,
their approver, will have
to decide whether or not
that purchase will go through.
The deferred state is the state
that you're in-app
purchase lands
on when it's currently pending
approval from the parent.
One thing we've seen
a lot and we want
to make it very clear is you
should not block your UI if it's
in the deferred state.
You should handle that in a way
where maybe the purchase
hasn't even occurred yet,
just let the child who's
actually attempting
to use your app make that
purchase, just let them go back
and continue to use the game
or whatever it is
they're interested in.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Because it might be hours,
it might be days before
the parent goes through
and approves that purchase.
So no spinner, no
modal dialogues.
Just treat deferred state as
if the purchase hasn't even,
you know hasn't even
started yet.
One thing I also want to get
into is it's very important
to test your applications
when using in-app purchases,
just like testing your
application any other way.
And this can be done
through the iTunes store
Sandbox environment.
This is a special environment.
If you're running
your application
and it's a developer signed
application, any purchase
that you make will go to
the Sandbox environment.
However, if you're interested in
testing deferred transactions,
there's really no good
way of creating a family
so that you can actually
test the Ask to Buy flow.
So if you want to test your
deferred transaction in Sandbox,
it can be done using
SimulatesAskToBuyInSandbox.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This is where you create your
SKMutable payment object.
You pass in that product
and then you set the
SimulatesAskToBuyInSandbox flag.
This is a flag that's going
to tell the App Store, hey,
treat this as if a
child was buying this
and that child was
part of a family.
Once you add it to the
Payment Queue that gets sent
up to the App Store and the
App Store will respond back
with a deferred state
to your application.
This is your way of knowing,
hey my app is going to work
when kids are using it.
We also should talk a little
bit about handling errors.
Not everything is
going to go right.
There's going to be error
states in your application.
Especially with in-app
purchases.
One thing to keep in mind is not
all errors are created equal.
I really encourage you to check
out the In-App Purchase
Programming Guide,
or the StoreKit Framework
Reference Guide
to get a good sense of
all the possible errors
that can be returned in
your application over time.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
One thing to keep
in mind is most
of these errors aren't
something that you need
to inform the user about.
Most of these are just errors
that we're informing you
to handle in your application.
A great example of that
is I'm going through,
I'm using your application,
I'm browsing around,
I'm doing a little bit
of window shopping.
I click on purchase.
I'll get the price confirmation
dialog that StoreKit will show.
And then I change my
mind, I want to go back
and see what else is there.
And I hit cancel.
Cancel error, StoreKit will
return a user did cancel error
to your application.
There's no need to
present the user
with a user did cancel alert.
And we'll see this in
applications from time to time.
The user knows that
they cancelled.
There's no reason to
repeat that back to them.
Let StoreKit handle
the transaction flow
as much as possible.
Again, we got into
the Payment Queue.
The Payment Queue is where
the payment will be processed.
The App Store and StoreKit
for you will confirm the
purchase with the user.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for you will confirm the
purchase with the user.
They'll present a price.
That price should match
what was inside your app.
So that's why isn't important
to make sure you localize the
pricing information correctly.
We'll authenticate the user.
If there's any issue with
their billing information,
we'll handle that.
No need for you to
pop a confirmation
or do anything special, let
StoreKit handle that for you.
So we talked a little bit about
before the transaction queue
and we talked about the states
that can be returned
for your payment.
Once your payment has made
it to the purchase state,
you have a number
of choices you need
to make in your application.
Specifically, around validating
that that purchase is backed
by a real monetary transaction.
So we'll get into a couple
different ways of doing this.
But again, it's your
application,
they're your products.
So it's really important to keep
in mind exactly what
technologies you're using
and what level of
security you want to use
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and what level of
security you want to use
to validate your purchase.
So receipt validation, this is
the receipt that is returned
to the application at
the end of the purchase.
It's just like a bill that a
consumer might get when shopping
at a mall or something
like that.
It includes all this information
about the purchasing of the app
and the purchasing
of your in-apps.
And there's kind
of two main ways
that you can attack
validating that receipt.
First is on the device itself.
This receipt comes down, you
can write code right there
on the device to pull
it apart, inspect it,
make sure you know authenticate
that it came from Apple,
make sure that it's from
a valid monetary source.
And this can be done
kind of generally is
for just unlocking features and
content right within the app.
So again, this is one of the
choices you have to make.
Is this a purely client side,
right inside your
app type of purchase?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
right inside your
app type of purchase?
Or if you have a server
side, you might want
to actually have the
server validate the receipt
because you want that
to be the gateway
for your content or service.
And this can be done by taking
that receipt that was sent
to the device, uploading it
to your server and then having
that server send the receipt
over to the App Store,
there's a backend API
to verify that receipt.
And that can be done
server-to-server.
One note with that though,
is yes the App Store does have
a backend API that can take
in that receipt and validate
it, but you should never send
that receipt to that API
directly from the device.
That is not a secure
mechanism and there are limits
to how trustworthy that can
be from your application.
So if you're going to use
the verify receipt endpoint,
it should really be
from your hosted server,
right into the App Store itself.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So let's get a little bit
deeper into what the receipt is.
The receipt is a trusted record
of app and in-app purchases.
It contains information
that will allow you to know
that this app was bought by
this user on this device,
and those in-app were bought
by that user on that device.
Stored right on the device,
right into the application
container of your app itself.
It's issued by the App Store and
it contains information in it
that allow you to verify
that it actually came
from the App Store itself.
And again, it's for your
app, for that device only.
If your app is running
on a different device it will
get a very different, you know,
it will get a different
receipt for a different user.
This is kind of, this
just gives you a sense
of what it looks like.
Again, stored in the app bundle.
We give you an API to get it.
We'll see what that
looks like in a moment.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And it's a single file.
Old, old, old versions
of the receipt there used
to be multiple files for
each in-app purchase,
but since iOS 7 we've
had a unified receipt
that contains all this
information in one single file.
It also, again, contains
certificates and signatures
to allow you to verify
that this receipt is valid.
We do this through a Public
Key Cryptography Standard
7 Container.
This is the container that
contains all this information
in it and this is
an open standard.
So there's a lot of information
that you can use out there
on how to work with
a PKCS7 Container.
The payload of that container,
the thing that contains
the actual information
that you're going to be
parsing out, that's encoded
in an ASN1 format, again
public open standard.
And there's a lot of
great options out there
for verifying this, OpenSSL
is a very common one.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for verifying this, OpenSSL
is a very common one.
Sometimes people
create their own.
But again, something
to stress here is
that this is your
application, this is your money,
this is your business.
So you've got to make
good choices on how best
to verify this receipt and
what technologies you're going
to use.
However, whatever you choose,
there's some similar processes
that you're going to go through.
First up, is you're going
to locate the receipt
in the application
using an NS bundle API.
And again, this is what
it looks like in Swift.
You can call right
in that NS bundle,
and there's an App
Store receipt URL
that will be returned by that.
This is a local URL,
local to the file system.
Once you have that URL,
you pass it into NS data
and you get the binary
payload of that receipt.
Now that you have
that binary payload,
a couple things to keep in mind.
The certificates on there
will have an expiration date,
but you've got to
be very careful
about checking that
expiration date.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
about checking that
expiration date.
Just because it was issued at
a certain time, doesn't mean
that that expiration
date is valid
for all time in the future.
So if you're checking
the expiration date,
compare it against to when
the receipt was issued
from the App Store, not to
what the current date is.
It's not like an SSL
handshake, where you want
to check it every time.
It's only valid for when it
was issued, or it's only valid
to check the date for
when it was issued.
Or it's perfectly valid not
even to check the date at all.
But what you do want
to do is check
up to the root certificate
authority on the receipt.
That will verify that
it came from Apple.
That you can trust it
because it is Apple's receipt.
So check up to the root.
So again, this just kind
of gives you an idea
of what it looks like
inside the receipt.
The payload of it, that ASN1
format that I was talking
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The payload of it, that ASN1
format that I was talking
about is a series of attributes.
You can think of it
almost as an NS dictionary.
It contains types and values.
And we're going to highlight
some of those types and values.
There's more in there than
we're going to talk about today.
And you can get a
lot of that online
through our Receipt
Validation Programming Guide.
But some of the main ones you're
going to want to use are type 2
and type 3, that includes
the bundle identifier
and the bundle version that
the receipt was meant for.
Hopefully that matches
what's in your application
and you should check it.
So you should check
the bundle identifier.
You should check
the bundle version.
And then one thing to keep
in mind is you should use hard
coded values right inside the
binary of your app.
It's a lot easier for me to go
in and change the info P list,
if that is what you're using to
match a phony receipt than it is
for me to change the payload
of your application
to match the receipt.
So if you really want to make
sure that that receipt is
for your application,
use hard coded values.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for your application,
use hard coded values.
Now that you know that this
receipt is for your application,
you want to make sure it is
for the device that's
running the receipt right then
and there.
And this is a little trickier.
What you want to do is take
a look at the SHA-1 hash
that is returned in type 5.
Type 5 contains this hash.
The hash is made up of
pieces of information
that the App Store knows
at the time of purchase.
And pieces of information that
you know a time of verification.
That specifically
is the bundle ID.
So again hard coded value
you want to keep in your app,
the bundle ID of your app.
The device identifier.
This is a, there
are APIs for that.
It's a little different on
iOS versus macOS, but you want
to load up that device
identifier.
And then this Opaque Value.
This Opaque Value is
basically it's a little bit
of cryptographic entropy.
It's a secret salt that is
included in the receipt.
It allows the hash to change
over time even if the bundle ID
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It allows the hash to change
over time even if the bundle ID
and the device identifier
aren't changing.
You should basically take
the Opaque Value that's
in the receipt and
include it in your hash.
Once you have that, SHA-1
hash compare it to attribute 5
and if they match, you know
it was for your device.
And again it's unique to
your app on that device.
The receipt also contains
information about all
of your in-app purchases.
So what we see here is a whole
bunch of, more than one Type 17.
Type 17 is for each and every
purchase that the user's made
with your application it
will include one record
for that in-app purchase.
The pay load for Type
17 won't be a string,
it will actually be
another ASN1 document
and that will have its
own values inside of it.
So what do those look like?
Well 1701, that's the
quantity of purchases made.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Well 1701, that's the
quantity of purchases made.
So we talked about
consumables before,
the user could have
purchased 10 bags
of coins inside your
game, or 100 bags of coin.
That's going to be the quantity,
the number of purchases
that was made against that
particular identifier.
The product identifier, we
talked about this before.
This is that identifier
that you use
to load your in-app purchase
data from the App Store
and what you set them
up in iTunes Connect as.
The identifier, this is
a transaction identifier.
This is a unique identifier for
the transaction that was used
to make that purchase.
The date of the purchase.
And then finally one
new thing, it's not new,
but one thing I want to
highlight is we also include
for auto-renewing subscriptions,
the subscription
expiration date.
So if you're switching your
app over to subscriptions,
that field's important to you.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
You're going to use that to
kind of, you're going to use
that to know is this
subscription still valid?
Do we need to ask the user to,
or you need to double check
that the user is still
in a valid subscription?
So that field is very important.
And then one other note.
If you have an app that's
currently in the Store,
and it's a pay for app
and you're interested
in switching it over to
a subscription model,
one thing you want to make sure
you check is inside the main
receipt, the app receipt
itself is Type 19.
This includes the original
application version
that that app was bought with.
So if I bought it as version
1.0, which was a pay for app
and in version 2.0 you
moved to subscriptions,
I want to take a look at
that original application,
because I want to make sure I
treat that user as the paid user
that they are and not as
the subscription user.
I think it's a really bad
experience if the model moves
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I think it's a really bad
experience if the model moves
over to subscriptions
and you don't respect
that they purchased
that app originally.
So it's important
to keep in mind
if you're switching
to subscriptions.
A couple other notes
about the receipt.
The receipt is issued
at the time of purchase.
But it actually is
also renewable.
But it's only renewable
for certain types
of in-app purchases.
So when we're talking
about consumable
and non-renewing subscriptions,
they are not going
to be refreshed with
the receipt.
So consumable and nonrenewable
subscriptions only appear once.
They appear right after
that transaction was made.
And they're not going to be
there when the user refreshes
that receipt either on that
device or when they switch
to a different device.
However, non-consumable and
auto renewing subscriptions will
always be in the user's receipt.
And you can get those back
and should get those
back via a StoreKit API.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and should get those
back via a StoreKit API.
We can take a look at
what that looks like.
It's a little different
on IOS versus macOS.
On iOS, the receipt
doesn't exist or is invalid,
you're going to want to use
this StoreKit API to refresh it.
But keep in mind that you're
going to have to have network.
We have to make a
connection to the App Store.
We have to validate the user.
We've got to know
that they're the ones
who have actually
bought this thing.
So they're going to have to
either enter in their password,
or use their thumb print.
And if you're doing anything
like refreshing the receipt
every time the app launches,
which is really not a
great pattern to follow,
or if you're doing anything
where you're checking
the receipt is invalid
and then fetching it because you
know maybe there's something,
something doesn't look
right, you want to be careful
that you don't get stuck in
a loop over and over again.
If your app crashes and you're
constantly checking your receipt
on launch.
Or if you keep checking
and it's invalid
and there's something
not quite right
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and there's something
not quite right
about your validation code, you
don't want to validate and fetch
that receipt over, and
over, and over again.
So that's something
to be careful about.
This could be done through
again, on iOS, this can be done
through an SKRecipt
Refresh Request.
You set your delegate
and you call start.
This will inform your
application right
on the delegate when the receipt
is updated and you can go
and process the transactions
in that receipt just
as if they were purchased
by the user.
On macOS a little different
if the receipt is invalid,
you're going to want
to exit with code 173,
this will tell Finder, hey
go fetch the receipt for me.
Again, it's going to require
a network and the user's going
to have to be signed
into the Store.
And this can just be
done by calling exit 173.
And then I just want
to re-highlight again
with server-to-server
validation.
If you're doing an auto
renewing receipt and you have,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If you're doing an auto
renewing receipt and you have,
an auto renewing subscription
of if you have content that's
available on the server,
you probably have a hosted
implementation backing
up your in-app purchases.
And so if you're doing
server-to-server validation this
will allow you to validate on
the server side that the receipt
that the user got for their
in-app purchases were valid.
So again, you take that
receipt that was on the device
and you upload it to the server.
However, I'm going to
highlight it one more time,
don't send the receipt directly
to the verify receipt endpoint.
It's not secure and you
should not be doing that.
One thing to note the verify
receipt endpoint returns a JSON
payload and that will
actually include information
like if the receipt you had
was maybe a little out of date,
you'll get the latest version
right back from the App Store.
And it will include some
information about was it valid
or if there was some other
state wrong with the receipt.
So again, there are
documentation online
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So again, there are
documentation online
about exactly how the verify
receipt endpoint works.
Okay so now we've gotten through
that, we've made the purchase,
we've processed the transaction.
It's up to you to decide exactly
how you're going to verify
that receipt and what level
of security you're going
to put behind it and what
technologies you're going
to use.
You validated that
that purchase was real.
That there was a monetary
transaction behind it.
It's up to you to make what
the user purchased available
to them.
Make good on your
end of the deal.
And so there's a couple
ways of doing this
and we talked about it before.
You might just have
functionality that's hidden
in your app that
you're about to unlock.
A basic version of
your application.
A free basic version that now is
a pro edition and you just need
to set that state and make
that available to the user.
Or if you have content that's
coming down from the server,
you need to download it.
Whole bunch of different
technologies,
whole bunch of different
ways of doing that.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
whole bunch of different
ways of doing that.
I want to highlight
a few of them now.
One that's a little newer
that Apple provides is
on-demand resources.
This is a way of
taking your application
and having the initial
download be as small
and compact as possible.
Just the binary.
And if you're going to have
other resources associated
with that application, if
your games have a bunch
of different levels and
they have you know graphics
or videos associated with
them, you can break that up
into different components
within your apps manifest.
On-demand resources is hosted in
the App Store, so you don't have
to worry about hosting.
It's scalable and reliable.
We have caching all
over the world for this.
Your users will be able
to get their content fast.
One thing to keep in mind is
it can contain any type except
executable code.
So all that code has to be
right in your original binary,
but it can contain any other
content you might want to have.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but it can contain any other
content you might want to have.
And it's also available
only on iOS and tvOS.
macOS doesn't currently
support on-demand resources.
So that's a technology
you may choose to use
to fetch additional content
after a purchase has been made.
If you're interested in
learning more about it,
I highly suggest you check out
Optimizing On-Demand Resources.
It's in Mission, this
room, Thursday at 10 a.m.
Another technology that Apple
provides is hosted in-app
purchase content.
This is content that
you can associate right
with your in-app purchase, right
in iTunes connect and again,
it's hosted on Apple servers.
So you don't have to worry
about hosting them yourself.
Scalable and reliable.
Around the world caching.
One thing to keep in mind
with this and as with the ODR,
is that this will download
your content in the background.
Your app doesn't
need to be running.
So the user makes a purchase,
and then leaves your app,
launches another app, and you
have to download this content,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
launches another app, and you
have to download this content,
that will keep running.
You get up to 2 gigabytes per
in-app purchasable product.
So that's not 2 gigabytes
for your whole application
that's 2 gigabytes per product.
And this is supported
on iOS, tvOS, and macOS.
So, again another great way
of having Apple host content
for your in-app purchases.
Let's take a look at what that
might look like a little bit.
A payment came through,
the user, you validated it
and you're ready to
download content.
You're going to be in the
updated transactions call back
of your payment observer.
And you're going to go down
a little bit and you're going
to look at your transactions and
you're going to notice that one
of those transactions
contains one or more downloads.
And all you've got to do is
pass that download right back
into the Payment Queue and that
will kick off the download.
If your application
is interested
in using hosted in-app
purchases, you're going to want
to have a Payment Queue updated
downloads method inside your
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to have a Payment Queue updated
downloads method inside your
Payment Queue Observer.
And this will give you all this
information about the downloads
as they come in, including
things like progress will call
in and let you know
progress and you'll be able
to use this to update UI.
Or time remaining.
Or if an error occurs and
you have to react to that,
that information will be there.
And then once the download is
finished a URL will be provided,
again that's a local path URL,
where it is on the file system,
so you can grab it and interact
with your content
right then and there.
So that's how hosted
in-app purchases work.
But you may also want to
self-host this content,
perfectly valid thing to do.
If you have a catalog
that changes over time,
of if you have different things
you don't necessarily want
to have Apple host,
perfectly valid
to do self-hosting content.
One thing to keep in mind is
there are background download
APIs that you should
be using NSURL Session.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
APIs that you should
be using NSURL Session.
It's the way to host
this content.
Content is downloaded even
when your app is not active.
If you're still using
NSURLConnection
that's deprecated.
And that's not going to be
as great of an experience
for the user as NSURL Session.
So it's very important
that you use these APIs.
This is how it looks in Swift.
You're going to create
a session configuration.
This is a configuration you
want to name is something unique
to your application for what
that session is all about.
You're going to create
your session.
You're going to pass that
configuration in, you're going
to pass in a delegate, this
will be whatever object you want
to get information back
about those downloads.
And you're going
to give the queue,
this operation queue is
actually what's going
to call into your delegate.
What thread or what
operation queue is going
to call into your delegate.
So you're going to want to make
sure you create a dedicated
operation queue for that.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
operation queue for that.
And then to kick off the
downloads, you create a task
and you hand in the
request and URL associated
with you in-app purchase.
And you call resume.
That will kick off
in the background your
self-hosted in-app
purchase download.
As the download is occurring
your delegate is going
to get information,
it's going to get called
into URL session didWriteData.
And that will give
you the ability,
you can take this
information coming in
and it will give you the
ability to update UI.
You might have a progress bar,
or something that you want
to tell the user about how
your downloads are going.
But it's also important
to keep in mind
that your application
may be exited during the
downloading process.
And so when your
application is re-launched,
you want to actually reattach
to those background
download sessions.
And so in your application
delegate, you're going to want
to implement handle events
for background URL session.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to implement handle events
for background URL session.
This will allow the
operating system to wake
up your application and tell it
about hey this background
download just completed,
or is in progress, or
something's going on with that.
So handle events for
background URL session.
It will pass in the identifier
for that configuration
that you used earlier.
You just recreate
that configuration
with that identifier.
Re-create a session
that will re-attach
to that background session.
And then what you want to do
is there's a completion handle
that's passed into that app
delegate method and you're going
to need to call that completion
handler once you're done doing
whatever you need to
do with that download.
So if the download's complete
and you need to move it
into your container, and
unpack it, and do work,
you need to call that completion
handler once you've done
everything with it
and let the OS know
that you're done doing
what you need to do.
So that's making
assets available,
but you're not quite done yet.
Even though you've
made all this available
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Even though you've
made all this available
and the user has
what they bought,
you've got to do one more thing
and you've got to tell StoreKit
that hey you're done
with the transaction.
This is important because
we keep that transaction
in our purchasing queue, we
keep that in our state machine
and if your application
dies and gets relaunched
or if the user leaves and comes
back, the Payment Queue is going
to keep telling you about it.
It actually might
continue to do it
on your behalf background
API calls, so that's going
to use up user's data.
So it's important to keep
things clean and finish
up the transaction
when you're done.
But you should also
make sure that you do it
when the content is
finished downloading.
If you finish the transaction
and then kickoff the download,
we're not going to tell you
about that transaction
ever again.
So if the download fails,
or if something goes wrong,
you're not going to
get updates on it.
So you want to make sure
that the download is complete
and then call finish
transaction.
So, again, the payment will stay
in the queue until
you finish it.
And if you're downloading hosted
content, another thing to keep
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And if you're downloading hosted
content, another thing to keep
in mind is yes you need
to finish downloading it
and if you finish the
transaction before the
download's complete we
will stop downloading.
And this can be done simply
by calling SKPayment Queue,
Finish Transaction and passing
in that transaction right
into the Payment Queue.
So, the user has
been using your app,
they've been buying
in-app purchases,
they love your content,
and they decide,
you know what I'm
running out of space,
I'm going to delete the app,
maybe I'll re-download it later.
Or they switch to
a different device.
It's always important that
they can get back those
non-consumable and auto-renewing
in-app purchases right back
into the app.
They need to be able to
restore that content.
So restoring transactions
allow the user
to restores non-consumable and
auto renewing subscriptions.
Consumable and non-renewing
subscriptions,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Consumable and non-renewing
subscriptions,
that's up to you to
manage the state.
So it might be something
that you just save locally,
it might be something that you
store in the Cloud, or you store
in your own hosted backend.
That's up to you guys to
manage how exactly you want
to restore those, but
for non-consumable
and auto renewing, you can
restore those using the
restore APIs.
Again, we offer APIs for
that SKPayment Queue Restore
Completed Transactions.
That will pull down a brand
new updated version of the app
and in-app purchase receipt.
Again, you're going to
want to observe the queue.
You added that to
your app delegate,
so of course you're
observing the queue.
You're getting information
about those transactions
as they're coming in.
Because we're going to call
Payment Queue Restored Completed
Transactions Finished.
That's going to tell you all
the restores that occurred
and you're just going
to want to process those
as if they were just
purchased by the user.
Or we're going to give you an
error that something went wrong
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Or we're going to give you an
error that something went wrong
and maybe you need to ask
the user to try it again,
or update your UI accordingly.
So again, it's important
to observe the queue.
Again, once the restore is
completed, you take a look
at the receipt and
unlock content
and features accordingly.
So now we're going to
get into a little bit
of okay you're built your
app, you're ready to submit it
to the App Store you
want to make sure
that you pass app review.
A couple things to keep in mind.
We've just been talking
about restoring.
You really need to have
a restore mechanism.
It doesn't have to
be a UI button,
you need to have some mechanism
inside the app to allow the user
to get their content back, or
else it won't pass app review.
Again, for non-consumable and
auto renewing subscriptions,
you're not going
to get other types
of in-app purchases
back on restores.
One thing to keep in mind though
is we want to keep this separate
from purchases itself.
Restores are free.
The user shouldn't get confused
that if they do a restore
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The user shouldn't get confused
that if they do a restore
and they're getting a password
prompt from the App Store
that it's going to
cost them anything.
This has got to be very
obvious inside your application
that this restore operation
they're doing is not a purchase,
it's completely separate
from that.
So it's one thing you want to
make sure your UI reflects.
Some other information
you want to keep in mind
for auto renewable subscriptions
are about policies,
keeping the information
in marketing text.
And making sure that once
they've subscribed that they get
that content right then and
there, that they don't have
to wait until the next period
begins for anything to appear.
If you subscribe you should get
some information right away.
And you should get
the information
that you should only be able
to get through a subscription,
not bundled products or content
that they would have been able
to get free elsewhere.
So, these are some of
the kind of policies.
I highly recommend
that you check
out the App Review
Policy Guidelines.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
out the App Review
Policy Guidelines.
I've heard there's even a
comic book version of that now,
so I would highly recommend
you learn all about these,
because they're changing all the
time and you really should keep
up to date with what's
going on with that.
And with non-renewing
subscriptions there's some other
policies you want to keep
in mind, about privacy
and making sure that he user
has the ability to opt in
or opt out accordingly.
And then finally the
most important thing
to pass app review is
if you have purchases inside
your app they should work.
You make the purchase,
the content should appear.
They should get that
information, they should get
that content right
then and there.
If it's buggy or if
it's error prone,
or if there's something going
on when the reviewer you
know is reviewing it inside
that app review environment,
you're going to have issues
with app review right
then and there.
So test it.
Test it in Sandbox, make
sure everything works
as you want it to.
So just to summarize a little
bit of what we went over today.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So just to summarize a little
bit of what we went over today.
Always observe the
Payment Queue.
I can't stress this enough.
If you're interacting with
in-app purchases in your app,
one of the first things your app
should do is set a Payment Queue
observer to get information
about what's going
on with the payments.
You need to fetch
localized product information
from the App Store.
This is through an
SKProduct request.
That will return
localized information.
So the information for the
users in their country related
to billing that's associated
with their App Store account.
And again use the
product's price locale.
There's the SKProduct that comes
back, has a locale associated
with that, you need to use
that for displaying the pricing
and currency information.
Use the receipt to
validate your purchases.
Again, they're your purchases,
this is your business.
You need to make
decisions about how best
to validate the receipt,
whether you do it locally
or on the server.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
or on the server.
What technologies that back it.
Be aware exactly what the
pros and cons of each are
and make your decisions
with that.
And finally make the
content available.
You can just unlock it in your
app right there on the device,
or you can make a hosted request
from the App Store itself,
or you can make a request to
your backend hosting APIs.
Make that content available.
But do it in a reliable
and quick way.
If you're hosting it yourself,
make sure your servers
are scalable
and you know cache appropriately
and are, you know, really ready
for users making
great use of your app.
And then finally, always
finish the transaction.
The last thing you
need to do is make sure
that that transaction is no
longer in the Payment Queue
or else your observer is going
to continually be notified
about it when your
app starts up.
And that will incur weird you
know possibly the user might
have to authenticate
at certain times,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
have to authenticate
at certain times,
or there might be backend APIs
that are occurring on behalf
of your app, using
up data for the user
if you don't finish
that transaction.
So always do that.
And then, allow users to
restore completed transactions.
You need to have a restore
mechanism inside your app,
if you have non-consumable or
auto renewing subscriptions.
This will allow the user
to get back what they've
already purchased.
More information will be online
at the developer website.
I highly recommend you go back
and take a look at
that later on.
And then a couple related
sessions we mentioned today,
if you're using subscriptions
in your app,
you should definitely check
out Introducing Expanded
Subscriptions
in iTunes Connect later today.
Or if you're interested in
on-demand resources check
that out Thursday morning.
And that's it.
Thanks a lot.
[ Applause ]