Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
>> Good afternoon.
[ Applause ]
Thanks. Hi.
My name is James Wilson.
I'm an Engineering Manager
and one of the things
that my team works
on is the frameworks
that power the App Store
and the iBook store in OS X,
which of course includes
StoreKit.
And we've run a StoreKit session
at WWDC over the last few years,
pretty much since
we debuted StoreKit
for doing In-App Purchases.
But the session has typically
been aimed at beginners
and intermediate level for
developers to get a feel
for how they can implement
In-App Purchases using StoreKit.
We know obviously from the
absolutely massive uptake
of In-App Purchases that it's
time to move on from that.
So this year I wanted to
do something different.
What we're going to focus
on in this session is how
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
What we're going to focus
on in this session is how
to optimize your In-App
Purchase implementation.
And the kind of optimization
we are looking at is not
so much using less
memory, using less memory,
making it faster etcetera.
What we're specifically looking
at is optimizing
your implementation
to be trouble-free, reliable,
smooth, and a great experience
for the user every single time.
But if you're not
familiar with StoreKit,
here's the three key things
that StoreKit does for you.
Of course we all know and
love that StoreKit allows us
to do In-App Purchases
in our apps.
This is for both consumable
and non-consumable items,
as well as for subscriptions.
One other thing that StoreKit
does is it has this thing called
the StoreKit product
sheet, which is a way
in which you can sell apps and
other content from the iTunes
or the App Store
through your app.
And lastly it can be
used for receipt renewal.
Not receipts are a
really powerful way
that you can enforce your
business model, prevent piracy,
etcetera, right within your app
as well as within your service.
And StoreKit's involved
there as well.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And StoreKit's involved
there as well.
Before we launch into optimizing
In-App Purchases using StoreKit,
I wanted to cover a few
things that are new this year.
The first is that that
StoreKit product sheet
that I just mentioned before,
it allows you sell content
from other developers or
other apps or content you have
in the store through your app.
That StoreKit product sheet now
supports the affiliate program.
This is really great
for you as a developer.
We debuted the product sheet,
I think it was last year.
But at that time it was simply a
means by which you could market
and sell those other product
and content through your app,
but now with the StoreKit
product sheet supporting the
affiliate program,
if you're part
of that affiliate program you
can get paid for those products
that you are selling
through your app.
The second new thing that
I wanted to call out is
that in StoreKit we're
introducing a new transaction
state called deferred and we'll
look at a bit more in a second,
but this has come about due
to the ask to buy feature
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but this has come about due
to the ask to buy feature
that was implemented as part
of our family sharing feature.
So family sharing allows
you to add up to six members
of your family and
form a family,
and that family can share
purchases and a payment method.
And when you have children as
part of that family that are
under 18 you can enable ask to
buy on them so that they can ask
for permission to buy
things from the store,
as well as In-App
Purchases in your app.
And then the parent can approve
that purchase remotely
from their devices.
So when the transactions in
that state we call it deferred.
And that's why we introduced
this new transaction state.
We'll touch on this briefly
now and I'm going to look
at an example of how to
handle it and what sort
of things you should do
in your app later on.
But it's called
SKPaymentTransactions
StateDeferred.
What this means is when
you see a transaction
into the deferred state it
means it's neither purchased nor
failed, yet.
You will receive a further
update about this in the future
as in the transaction will
enter that final state
of either purchases or failed,
but it will be an
indeterminate time
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but it will be an
indeterminate time
between when the purchase begins
and when it enters
that final state.
The most important message
I want to get across for you
as app developers is
that to implement support
for handling this deferred
transactions state you need
to engineer it in a way that
your app can still be used
by the user while it's
in that deferred state.
It's also worth mentioning
that we do actually support
and allow you to repurchase
that item, that is attempt
to make subsequent purchases
for that item while the
permission is still pending,
or while the transaction's
in that deferred state.
Wherever possible though,
especially in regards
to the deferred transaction
state,
you should let StoreKit
handle the user interaction.
We'll handle a lot of the
messaging and dialogues
that are required for
the user in regards
to the Ask to Buy feature.
This is how ask to buy works,
so I've got a child device
here and a parent device.
When a child wants to
buy something such as one
of your In-App Purchases, they
attempt to make the purchase
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of your In-App Purchases, they
attempt to make the purchase
by pressing the Buy button
and they'll be prompted
to ask permission for that item.
That causes a message to
be sent via the App Store
to the parent's device, in
fact all the parent devices
or approver's devices.
And they receive a notification
that this request has
been to buy this item.
When it enters that state,
that's when the transaction goes
into the deferred state.
So once the child has tapped
the button to say yes they want
to ask permission, and
the notification is sent
out to the approvers' devices,
that's when the transaction
becomes deferred.
Now once the parent
goes ahead and approves
or declines this
purchase request,
we'll send a message back
down to the child's device
or the requester's device.
And at that point, that's
when the transaction will
enter the familiar purchases,
or failed states.
Now let's talk about how
to optimize your In-App
Purchase implementation.
This next section is what I
call stock it in 60 seconds.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This next section is what I
call stock it in 60 seconds.
It designed to give you a
really brief introduction
to the processes involved
in completing an In-App
Purchase using StoreKit.
The first thing you've got
to do is know what
you're going to sell.
You need to load those In-App
Purchase product identifiers.
Then once you know what
you're going to sell,
in terms of those
product identifiers,
you need to ask the App Store
for the localized information
about those products.
And we do that with
SKProduct request.
That gives us back a
bunch of SKProduct objects
that we can then go and draw
our really beautiful In-App
Purchase UI.
Once we've shown our beautiful
In-App Purchase store UI
and we've enticed the user
to make that purchase,
we proceed on to
converting that product
into an SK payment
object and adding
that into the payment queue.
The payment queue will then
give us updates about how
that transaction is progressing
and we will need to process
that transaction as it moves to
the purchase or failed state.
Assuming they purchase the
item, we then go ahead and make
that asset or that content and
feature available to the user.
And then lastly we
finish the transaction.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And then lastly we
finish the transaction.
So that's it.
It's a pretty simple process
when you string it
together like that.
What we've noticed though
is though from all the apps
that we see submitted, from all
the apps I've used personally,
from the feedback we get from
developers and tech support,
as well as from meeting
a lot of developers here
at WWDC is we know that there
are definitely some danger zones
in here.
There's areas of this process
where the problems can crop up,
or there's common
pitfalls or got you's.
So the focus on this
session is to look at those,
and to help you get over those.
To really bring your In-App
Purchase implementation
up to the next level.
We want it to be as
smooth as possible,
a great experience
every single time.
To start off with let's
look at the user interaction
in particularly in
the pre-sales piece
of making an In-App Purchase.
That is when you're
showing your store UI.
Remember the first thing we have
to do is know what
we want to sell.
We need to load those In-App
Purchase product identifiers
from somewhere.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
from somewhere.
Now these are the product
identifiers that you set
up in iTunes Connect when you
defined your In-App Purchase
saleable items.
So there's two options for
how you can load these product
identifiers up.
One is if you've got a very
static catalog of items
that you're selling, that is
you might only have a handful
of things you're selling and
you know they won't change
throughout the lifetime
of the app,
then it might be
simple enough for you
to just bake those identifiers
directly into the apps binary,
or include it as a pay
list that's part of the app
that you submit to the store.
That's the simplest
implementation.
For a lot of developers though,
we know that that doesn't
suit you well enough in terms
of giving you enough flexibility
and what we see is that a lot
of developers, the first
thing that happens during
that In-App Purchase
flow is they make a call
to their own service to fetch
that list of product identifiers
that they're going to
display to the user.
Now if you're doing that, you
need to think really carefully
about how you're caching
and loading that data.
Because it's the first step
in making an In-App Purchase.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Because it's the first step
in making an In-App Purchase.
And that In-App Purchase process
is such a fleeting moment
of trying to transform the
customer's intrigue and interest
into a sale that's
obviously a benefit to you,
but is also to help with
their enjoyment and usefulness
of the app that they're using.
Even more important is the
reliability of those servers
that are hosting
your list of products
that you're going to sell.
You've got to make sure that
if you're going to have them
on server and you're going
to make an HTTP request
or whatever it might
be to load them,
that you have a super
reliable, font tolerant,
really performant server
platform that is able
to issue those product codes
really, really quickly.
Because that's not the way
to start an In-App Purchase.
So often when, if I'm playing
games, or I'm using an app
and I'm enticed to make
an In-App Purchase,
probably because I'm not
doing very well at it,
I tap the button to
see what it's about
and I'm stuck at a spinner.
There's no need for that.
If you have to make a network
request in order to enter
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If you have to make a network
request in order to enter
that In-App Purchase
flow, anticipate it
and do it ahead of time.
For most of you, you
know when it's likely
that the user is
going to be presented
with these In-App Purchase
offerings, so just ahead
of time do those
network requests you need
and avoid them getting stuck
at a spindle like this.
So how do we load that
product information?
You do that -- well sorry once
you've got those identifiers,
you've got to load the
product information about them.
This is when you transform
that simple identifier
that you defined
in iTunes connect
to getting the localized product
information, product price
from the app-store that
the user is signed into.
And you do that using
SKProduct request.
You pass into this the set of
identifiers for which you want
that localized product
information back.
Now when you do this, as I
said just before, always try
and anticipate the presentation.
This will be another
round trip on the network.
There's always, always going
to be some delay there.
So it's best if you
can anticipate this,
load it just ahead of time, or
at a point in time when you know
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
load it just ahead of time, or
at a point in time when you know
that you can safely do that
without interrupting the user.
So that you can present that
in-app UI really quickly.
Now what you get
back when you make
that SKProduct request is a
bunch of SKProduct objects.
The SKProduct object will
contain the localized title
and description, the
price and locale,
and if you're hosting
In-App Purchases with us,
then you'll also get back
the content size and version.
The most important bit in the
SKProduct object you get back
in terms of making for a really
good In-App Purchase experience,
is that localized title, and the
localized pricing information.
You would be amazed at how
many different ways there are
to represent the simple concept
of currency around the world.
This is just a very
small subset of examples.
This is all different ways
to represent 1234 and56
in currencies around the world.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now currency is something that's
always near and dear to people,
especially when you're offering
them something for sale.
So make sure you can
present the currency
for what you're offering
through In-App Purchases
in a way that's comfortable
and familiar to that user.
It makes for a much, much
nicer experience to make
that user feel like they
are first-class experience
in your app, regardless
of wherever they are
around the world.
Here's a neat trick
for doing that.
You can show that you
can localize price using
NSNumber formatter.
Here I've created my
NSNumber formatter
and I've set the number
style to be in currency,
because that's what
we want to display,
but here's where
the magic starts.
I set the locale for that number
formatter, to the price locale
that I got back from
the SKProduct object.
I don't make any assumptions
about how the device
is configured,
I don't make any assumptions
about where the user
might be based
on any other criteria
whatsoever, because we want
to show the price that's
correct for the App Store
that they are signed into.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And you get that by using the
price locale that you get back
from that SKProduct object.
Now the second piece to this
puzzle is when you go to create
that formatted string by
calling the string for number,
is you should pass into
that the price that you get
from that SKProduct object.
You see that SKProduct object
has everything you need
to present this item
perfectly suited
for the user no matter
what region
or what country they're in.
So use it to make sure that
it's the most comfortable
and familiar experience
for the user.
But really importantly is
this absolutely no need
and you definitely should
not be doing any currency
conversion yourself.
When the user signs into
the store with their account
in their region or country, the
App Store is automatically set
up to deliver you the pricing
information that is correct
for the region they're in.
Don't try to convert
currencies yourself.
Another thing that I often
see go wrong is how developers
handle errors especially during
this really important pre-sales
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
handle errors especially during
this really important pre-sales
part where you're making
the pitch to the user
about what they want to buy.
The important message here is
that not all errors are equal.
I really encourage you
to checkout either the In-App
Purchase programming guide,
or probably even better,
the StoreKit framework
reference guide.
Because it will have a fairly
small and easily digestible list
of error codes that the
framework may vend to you.
Now errors can happen at
a lot of different times.
And sometimes that error is
something you, the developer,
needs to deal with and message
to the customer, but more often
than not it's an error message
to just provide you feedback
at your apps level of
what happened during
that transaction.
More likely than not, we've
already told them what happened.
We've already presented
the dialogue to the user.
We're already trying to take
them through the process
of rectifying whatever
might have gone wrong.
So make sure you know what
those error codes are.
Check what the code is and know
for sure what errors you need
to message the user about
and what you can rely
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to message the user about
and what you can rely
on StoreKit having
taken care of for you.
And the most popular example
that I see of this and the one
that gets me every single time
I use an app that it does this,
is when I'm seeing something
I want to buy in your app,
tap the Buy button, I'm
all excited about it.
Dialogue comes up to sign
into the store or touch ID.
I've done that and
then I get the dialogue
to confirm my purchase.
And maybe for whatever reason
I've got a bit of cold feet
and I hit the Cancel button.
So many apps I see then follow
that up with a second dialogue
that says purchase failed,
you cancelled the transaction.
I know I cancelled
the transaction.
I cancelled it.
Okay? Yes your app got an
NS error back that told you
that they cancelled
the transaction,
but you really don't need to go
reinforce the point to the user
that they got cold feet and
backed out of the transaction.
In fact, it can be really
damaging for your, you know,
possibility to keep
that momentum going
and make that sale.
Because if the first time
through a purchase I'm not sure
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Because if the first time
through a purchase I'm not sure
about it and I decide
to back out,
if my experience there wasn't
great, then I'll tell you what,
I'm really not convinced that
I should be spending my money
on this app if they
can't make a good job
of letting me back
out of a purchase.
So just being aware of that.
It's one of a really good
example of where it's important
to know exactly what
these error codes mean.
Wherever possible just let us --
let StockKit handle
the transaction flow
as much as possible.
We see an increasing
number of apps starting
to introduce dialogs
and messaging ahead
of the In-App Purchase flow.
If that's something
you feel you need to.
That's fine but I'd
strongly encourage you
to come along tomorrow to a
session given my Chris Espinosa
at 3:15 about apps and kids.
It has some really good
tips there about how
to handle this sort
of interaction.
So now let's move on
to making the purchase.
You have done a great job
of showing this glorious
In-App Purchase UI to the user.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of showing this glorious
In-App Purchase UI to the user.
It was perfectly presented
to them, no rough edges,
no silly error messages.
They've tapped the Buy button
and they want to move forward.
So when you go and make that
purchase, this is when you start
to really get into
using StoreKit.
StoreKit revolves around and is
centered around a payment queue.
And the most important
thing you've got to do
with that payment queue
is observe it always.
The payment queue is where
you'll get all information
about how a transaction
is progressing,
whether it was purchased
or failed, whether it's
in progress, whether
it's deferred.
You'll also get information
about restored transactions,
etcetera.
It should be the center of your
In-App Purchase implementation
and it's the only
source of truth for state
about transactions
as they occur.
There's absolutely no need
for you as a developer to try
and integrate some sort of
complicated state machine,
or caching of state about
transactions that are in place,
because you can get all of that
from the payment queue itself.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because you can get all of that
from the payment queue itself.
You can get transactions
that are in progress.
You can know about the status of
a transaction as it progresses.
And if you're hosting In-App
Purchases with it, the queue is
where you'll also get that
download status information.
I think one of the most common
reasons I see developers attempt
to tack on a side cache of
state about In-App Purchases,
rather than just holy and
solely trusting the queue is
because they feel that in
order to update UI elements,
and you know, communicate
the progress
of the In-App Purchase flow
through the app that they need
to somehow stash that
payment object away or try
and tack some extra state on to
it somehow so that they can keep
that UI up-to-date
and congruous,
but in fact that's not true.
And we will see in one
of the examples I'm going
to give you how you
can harness the queue
and trust only the queue
and still achieve all that.
But just as you've got to
rely on the payment queue
for all those updates and all
those pieces of information,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for all those updates and all
those pieces of information,
you also need to be aware
that any and all transactions
that appear in the queue
are real and valid.
Just because your app didn't
start a payment or your --
the running instance of your
app didn't start a payment
or for whatever reason
you don't think
for some reason this
transaction is valid,
then you are likely
leaving your customers
out from a real monetary
transaction
that has taken place.
So make sure you trust
the queue completely.
Any and all transactions
that you see in there,
any and all updates you
see are real and valid.
Now if you're concerned
about how to ensure
that they are not
an unauthorized
or a fraudulent transaction,
that's a different matter.
Please come along on
Friday morning at 10:15,
I'm doing a session
about preventing unauthorized
transactions using receipts
and you'll see that it's the
receipt that holds that source
of truth about whether
something was a real valid,
monetary transaction.
But otherwise for all intents
and purposes, we
trust the queue.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And how do we do that?
On launch, as soon as possible,
ideally in your application
deep finish launching,
you should be calling
SKPaymetQueue
addTransactionObserver.
The object you add in there will
be your object that conforms
to the SKPaymentQueue observer
protocol and that's your object
that forms the center of your
In-App Purchase implementation.
It's going to receive
all the updates
about how payments are
progressing in the queue
and it's how you can handle
everything from dealing
with errors, to updating
UI, etcetera.
So I'm going to walk you
through a quick example here.
Not so much because you need
to get this beginner sort
of level information,
but because I want
to show you how it leads
into a really bad design
pattern and why that's wrong.
First thing we've got to do is
of course we make this call
making to SKProduct request
to get information about the
products we want to sell.
Now once we've done that and we
set our delegate and call start,
we're going to get that
product information back.
We're going to get a bunch
of SKProduct objects.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We're going to get a bunch
of SKProduct objects.
When the user taps the Buy
button and wants to end
that purchase flow, we
take that SKProduct object,
and we transform
it into a payment
by calling SKPayment
payment with product.
One little thing to note here
is I've heard some feedback
from DTS in particular is
that they've seen a lot
of developers try and handcraft
their own SKProduct objects
to create the payment with.
That's totally not
the right thing to do.
What you need to do is
get your identifiers
of what you want to sell.
Use SKProduct request to get
that localized information
and the SKProduct objects.
Then you hand those SKProduct
objects into SKPayment
to get the payment object.
Don't try to craft your own.
You've got to use those
real live SKProduct objects.
So the first step was that
we call SKPayment payment
with product.
The next step is we take
that payment and we add it
into our payment queue
to start that process.
It's very, very deliberate that
there is no step three here.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It's very, very deliberate that
there is no step three here.
Because at this point in time
you are completely hands-off
and you should step back and let
StoreKit handle this transaction
for you.
In fact the transaction
will take on a life
of its own completely
outside of your app.
StoreKit and its background
processes will handle the actual
purchasing process, the
dialogues, and the sign
in with the user
outside of your app.
That means that payment takes
on a life outside of your app,
even if your app crashes,
even if your app is quit,
or other circumstances
that cause a long delay
in that payment process
transacting, it will continue
to happen even if your
app's not running.
So what we need to do is
we've got to handle the events
from the queue to know
where this payment is at.
The most important
SKPaymentQueue observer method
to implement is payment
queue updated transactions.
This is what gets called
every time a payment starts
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This is what gets called
every time a payment starts
progressing through the process
of becoming an actual In-App
Purchase that is made.
And we can implement
this like this.
So here I've got a full
loop that loops over each
of those transactions that
I'm receiving an update for,
and for each transaction I
set up a switch statement
because I want to
particularly know
which state this
transaction is in.
Then I can do things like
looking for the state
of SKPayment transaction
purchased.
That means the purchase
has completed
and I can unlock
features and content.
I can go ahead and
check the receipt,
make sure it's a real
monetary transaction,
not a fraudulent
one that's occurred
and then unlock those features
and content accordingly.
But don't do this.
Don't set up that pay
statement to look for the state
of purchased and then try
and find a matching payment
in some side cache that
you've got of payments
that you thought
were in progress.
Because once you've created that
payment object and thrown it
in the payment queue
to start the process,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in the payment queue
to start the process,
that object should
be dead to you.
You should forget about it.
You'll get information
about those payments
and those transactions
through the queue.
So if you did something
like this and tried to fetch
that payment out of some cache
that you were maintaining
of what you thought was in
progress, and then even worse,
did something like
this and said, "Huh?'
"I don't know this payment, I've
got no idea where it comes from.
I'm just going to ignore
it and skip over it."
You will have left
the customer out here.
Okay. If something
arrives in the queue,
it's because someone
bought something.
Even if you don't think it
originated from your app,
or the running instance of your
app, there's lots of reasons
of why a transaction can
suddenly appear in the queue.
They're always real.
They're always valid.
You should process them.
So why not?
Why wouldn't we do that?
Because what if your
app crashes?
Obviously then you've lost
your cache of information.
And not only will the user have
to endure a crash in your app,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
but next time when they launch
if you then ignore the
subsequent update you get
about the payment, then not only
have they put up with a crash,
they've missed out on
what you've sold them.
It's just as valid.
Always process those
transactions.
Now, getting back to what
we talked about before.
This new state of the
SKPayment transaction deferred.
Here's an example
of how to handle
that transaction being deferred.
So remember in this
state, a child that's part
of a family sharing unit
has asked permission
to buy something in your app.
And the transaction has moved
from SKPayment state
purchasing to deferred.
Now when you see
that status come
through as SKPaymentTransaction
StateDeferred,
here's what you should do.
There's three important things
that we'd really encourage you
as developers to do in your
handling of this situation.
The first is allow the
user to keep using the app.
It would be an absolute
buzz-kill for the kids
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It would be an absolute
buzz-kill for the kids
if they are sitting there in
the app, absolutely delighted
about the possibility that
mom or dad might just say yes
to this purchase
they've asked for,
but all the while they
have to sit there waiting.
Spinners up.
Waiting for permission.
They're going to quit.
They're going to
go to another app.
You're going to lose them.
Okay. Make sure they can keep
using the app in some way.
Because it may be
some time before
that transaction is updated.
It could be up to 24
hours before the approval
or decline comes
through from the parents.
So really don't get them stuck
in that modal purchasing state
where they can't do
anything with your app.
In fact one way to think
of it is you've got a
very captive audience
at this point in time.
Someone wants something
in your app
and they're waiting
for approval for it.
Make use of that time.
But at the end of any
transaction you perform
with StoreKit, whether it
gets purchased or failed.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
You always must call
FinishTransaction.
When you call FinishTransaction
that tells the App Store
that you're done with
this transaction.
You've seen it, you've
processed it,
you don't need to see it again.
It removes it from the
queue and then it makes sure
that we don't keep calling the
Updated Transaction Method each
time on launch or at other
points when your app is running.
Now this is most common cause
that I see for bad app behavior
on launch, especially things
like really slow launch time,
or a launch time
where it launches,
then it's hanging
there for a while.
Or worst of all when the app
launches and I'm presented
with a series of dialogues that
just don't make sense to me
because in fact what's happening
is because you didn't remove
that transaction from the queue,
when your app launches we get --
and you add that
transaction queue observer
in the first thing we do is
go and look at the history
of transactions that
are still pending
and haven't had Transaction
Finished called on them,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we'll go and
replay all those update
through to your app.
So if you then start
processing them and have reason
to message something to
the user and still fail
to call FinishTransaction
that means every single
time your app launches,
it's going to go
through this process.
It's a bad user experience
and it's one
that gets worse the more
the user uses your app.
So always call FinishTransaction
to get those items
out of the queue.
Two tips about the
SKPaymentQueue object itself.
One is going back to this notion
that the SKPaymentQueue is
your single source of truth.
It has a property on
it called transactions.
And you can call that property
and get the list of transactions
that are currently inflight.
So for example, if
you wanted to --
when you show your In-App
Purchase UI make sure
that you correctly
represented any items
that are still purchasing.
You could call transactions
to get the list
of payments in progress.
As opposed to trying to track
that state yourself on the side.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
As opposed to trying to track
that state yourself on the side.
The other one is keep in mind
that in-app purchasing can
be disabled on the device.
And it's a really bad experience
for the user if they're enticed
to play your game or use
your app enough to want
to make an In-App
Purchase then only to find
that they get some error
message telling them
that that's been disabled.
So you can call it
can make payments
on SKPyament queue it's a class
method for returning a BOOL
to know whether or not In-App
Purchase can actually be made.
Now I want to give you a
quick demo of a few things
that we've just talked about.
Okay. I have here a very,
very simple app for OS X
that does an In-App Purchase.
Everything I talk
about in this session,
including this demo applies
equally to iOS and OS X.
StoreKit is the same
on both platforms.
But in this app a few
things to call out.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But in this app a few
things to call out.
In my application
deep finish launching,
first thing I'm doing
is I'm adding an object
in to observe the queue.
And down here you'll see I will
have implemented my payment
queue updated transaction
method.
It's in here that I start
inspecting and dealing
with all those events
that happen
as purchases progress
through the queue.
I deal with things like
entering the purchasing state,
when things first start,
when it gets purchased,
where it's failed or restored.
So for the sake of this demo,
I'm going to run my
really, really simple app.
All it does is it sells
a .99 cent banana via an
In-App Purchase.
Really exciting.
I'm going to go ahead
and purchase that.
Now as soon as I hit
the Purchase button,
before any other
user interaction,
I straightaway my
breakpoint was hit that I set
in my payment queue
updated transactions method.
So that's really
important to know,
because if you were thinking you
had to track UI state yourself,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
because if you were thinking you
had to track UI state yourself,
this demonstrates you don't.
As soon as that button
was clicked -- bang.
We're into the payment queue and
observing an event occurring.
And in fact the event that
we're observing is the fact
that this payment or this
transaction has entered the
state of purchasing.
So this would be a time when I
could go and update the state
of my Buy buttons whatever else
I need to do to inform the user
that yes this transaction
is underway.
I'm going to take that
breakpoint out because
that can get a big noisy.
But let's let the app continue
and see what happens here.
So I've been prompted
to sign in.
Now one thing to note is
because this is a test app,
I'm using the App
Store test environment.
That is I have my app signed
with my development
certificate as we see here.
Scroll down to the code sign.
Sometimes it's easy to just
search here for code signing.
Of course you need to
set it all and combine.
See here my code
signing identity is set
to Mac developer.
This makes sure that my app is
development-signed when it runs.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This makes sure that my app is
development-signed when it runs.
That means that when we
start using StoreKit,
StoreKit can inspect
our code signature --
the code signature of
this app and it can know
that because it's
development-signed it needs
to talk to the test environment,
not the production App store.
And that allows us to test our
In-App Purchase implementation
before we have anything
on the store.
So I'm going to go ahead
here and enter my password.
Cool, yep.
I definitely want
to buy this banana.
And I've already purchased it.
I've done this demo
a few times now.
And see here when we
get the final thank you
through form StoreKit
and from the App store,
we're already back
in in our app,
we've got our breakpoint fired
in the SKPayment
transaction state purchased.
This is where we can then go
ahead and inspect what it was
that was bought by looking at
the receipt, unlocking features
and content, and then whatever
else we need to do based
on this purchase
having occurred.
But right now, at this point
because of the breakpoint,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But right now, at this point
because of the breakpoint,
notice that I have not
yet called FinishTransaction
I've made breakpoint right
at the point when
I would normally go
and check the receipt
to know what they bought
and unlock those features
and content accordingly.
But what if I hit
the Stop button
up here in my app in X code?
This is just like what would
happen if my app crashed midway
through the In-App
Purchase, or what would happen
if user killed your app,
or quite your app
during the purchase.
Or maybe something
else happened,
lost network, whatever
it may be.
The important thing is here --
let's put this breakpoint back
on, when my app next launches
and this applies to iOS and
OS X, watch what happens,
on launch I was immediately
taken again
into Payment Queue
Updated Transactions.
Why? Because that transaction
hadn't finished yet.
Your app crashed
midway through handling
that even though the user had
paid for what they've bought.
So this is why it's so important
to always observe the queue
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So this is why it's so important
to always observe the queue
and handle any and every
transaction that occurs
through here in a
way that doesn't rely
on you tracking any
state yourself.
So this time I'm going
to let this go through.
And it goes through
and make sure
that we do actually
finish that transaction.
One thing I did want to
note, we mentioned before
about the updating of UI.
Let's go through
this process again.
I'm going to set
a breakpoint here
on transaction state purchasing.
Now when I run this
lets buy a banana again.
Just take note of the
fact that this time
around my breakpoint here was
called on the main thread.
But you shouldn't necessarily
make any assumptions
about which thread you
will get the call backs on.
So if you're updating
UI elements in here
such as setting the button
state, graying things out,
anything in UI kit or app kit,
make sure you don't make an
assumption about which thread
that message comes in on and
dispatch it to main if you have
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that message comes in on and
dispatch it to main if you have
to to work safely with
App Kit and UI kit.
Okay, let's go back
to the slides.
Let's move on to the
post sales experience.
So we showed a beautiful
UI to the user,
we enticed them to buy.
The buy process was
smooth and seamless.
Now you've got to make good
on what they've paid for.
In particular you need to
process the transaction
through the payment queue.
Then you need to make that asset
or content or feature available
and this may mean
downloading content.
And then of course
finish the transaction.
Now in the post-sale experience
there's three key things you're
going to want to do.
The first is validate the
purchase, verify the receipt
from that transaction and
you can verify that on device
or on a server to server
level if you're handing
out content from servers.
And that is your way to confirm
that it was a real monetary
transaction that is authentic
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that it was a real monetary
transaction that is authentic
and trusted and occurred with
the production App store.
Again, 10:15 Friday come along
and let me tell you
all about that.
The next thing you want to do
is give them what they paid for.
And especially if you have
to download content
for that feature.
Obviously as apps
become more and more rich
in the features they
offer, it often makes sense
to have a smaller binary
for the actual app itself
and then download extra content
as those In-App Purchases
are made.
You can host that with us, or
you could have it self-hosted.
And then lastly,
depending on the type
of transaction there may
be a need to persist state
about the transaction
having occurred or be able
to handle the situation
where the user needs
to restore the previous
transactions
that they have made.
So a quick word on
receipt validation.
When you do the on-device
validation of the receipt,
that is confirming that there
was a real monetary transaction
with the store, you can then
go and unlock those features
and content within
the app itself.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But increasingly, like I said we
see developers hosting content
either with us or on their
own server infrastructure
that is downloaded when
the purchase takes place.
If you have your own servers
that are issuing that content
out to your users based
on In-App Purchases,
of course you want to make sure
that those servers only hand
that content out to
real paying customers.
And in fact you can to
server to server validation
of receipts using Apple's
online validation service.
And that will allow you to
restrict that access and be sure
that you only hand out
those valuable assets
to people who have paid.
But what you definitely don't
want to do is try and use
that online validation service
directly from the device.
Now there was a time
a little while ago
when that was kind
of okay to do.
But when we introduced iOS 7
we introduce the grand unified
receipt format.
That unified the receipt format
that we use on iOS and OS X.
And it empowered you as the
developer to be able to validate
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And it empowered you as the
developer to be able to validate
that receipt on the
device itself.
That way there was no need
for your app to be calling
out to Apple's validation
service
to get back the information
about that receipt
to know if it was valid.
So you should no longer be using
that validation service
directly from your device.
It's okay to do it
from your service
to the validation service.
But as far as your app
running on a device,
it should either validate
the receipt locally,
or pass that receipt up
to your service first,
then your service can talk to
the Apple validation service
to know if the receipt is valid.
And those iOS 6 APIs they
are in fact deprecated now,
so you really need to
move away from those.
Downloading content,
especially in terms
of ensuring a very
hassle-free, smooth,
and great In-App Purchase
experience every time,
downloading content is a really
important area to focus on.
At this point in time
the user has paid
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
At this point in time
the user has paid
for what you're offering right?
They've done their side of
the bargain they've paid you.
Now it's up to you to deliver
what they've paid for as quickly
and reliably as possible
every single time.
One of the great ways
you can do that is
by using Apple's hosted
In-App Purchase feature.
That allows you to host In-App
Purchase content with us,
it's hosted on our servers,
which are very scalable,
very reliable.
We can download that content
for you in the background,
even when your apps not running.
So once that purchase is
made, the user can switch
around to doing lots of
other things and then
when they get back to your app,
the content's there
and waiting for them.
Great experience for them.
And you can have up to 2 GB
per In-App purchasable product.
That's not 2 gig total per app.
That's 2 gig per
item you are selling
through In-App Purchases.
Now if you're using the hosted
content it's so simple to use.
We're back now in our
payment queue updated
transaction method.
Looping over our transactions
we're receiving updates for.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If you see a transaction
with a downloads property
and that download property
contains one or more download
and you want to start
downloading
that content, watch
this, simple.
You simply call SKPaymentQueue
start download
and hand those downloads
over to us.
StoreKit in the background
process then takes care
of downloading that content and
gets it to the user as quickly
and seamlessly as possible.
When a download progresses,
you'll also receive updates
via the SKPaymentQueue Observer
Method, payment queue
updated downloads.
This will tell you things like
progress and time remaining,
as well as state and error.
And when it's finished,
you will get a content URL
where you can locate where that
content has been downloaded to.
Now of course there may be many
reason why you want to host
that In-App Purchase content
yourself and that's okay,
but make sure you use the
background download APIs
to download that for
all the same reasons.
If you're using the standard
old NS URL connection
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If you're using the standard
old NS URL connection
to download this
content, there's going
to be some serious limitations
that you run into particularly
around the fact that you can't
guarantee the download will
start straight away depending
on the network conditions,
and the user would
have to sit there
in your app while
the download happens
to guarantee it completes.
That's not good.
But what you can do is
you can us NS URL session
to do the background
download for you.
There's lots of great
documentation available
for this online, but
here's a quick run
through of how this works.
You create your NS URL
session configuration
by giving it a name, I've called
here my background download --
my background session.
And then when we've got
our session configured,
we create the session itself
and we give it a delegate
to get updates about how the
download's going and a queue
on which to receive
those updates.
That's a dispatch queue.
This will be familiar
with anyone that's work
with NS URL connection before.
You create an NS URL request, in
this case I've just created it
with the URL that
I want to download.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Then simple as this, I create
the download task using
that request and the
download session.
You see what we did there?
We setup a session to
go and download this
for us in the background.
We told it what we wanted to
go and get and then we went
and created the download task.
And now the OS is taking care of
that for us in the background.
You will get updates
via the delegate method
as to how the download
is progressing
so you can update your UI.
But because these can continue
to happen while your
app's not running,
when your app launches you need
to reconnect to any sessions
that might be in progress
or might have finished while
your app wasn't running.
And this is how you do that.
In your application delegate
you should implement application
handle events for
background URL session.
That will get called on
launch so you can re-establish
that session configuration,
get the session itself,
set your delegate again,
and then get those
same progress updates
about how the download is going.
But no matter how you
download the content,
whether you download
it yourself,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
whether you download
it yourself,
whether you download it
via host In-App Purchases,
you must always call
finishTransaction of course,
but you must do that once you've
completely downloaded the asset
and made it available
to the user.
One thing that can
go horribly wrong
from the user's perspective is
if you call finishTransaction
earlier,
before the download is done,
and then something happens
during the download,
they may have no
way to get back,
or get to that purchase
they've just made.
So you call the
finishTransaction once the
content's downloaded and
you've made it available.
That way only then does
it go out of the queue
and only then will you not
receive further updates
about it.
Now restoring transactions,
this is something you have
to offer -- excuse me.
This is something
you have to offer
if you are selling
non-consumable items
or order renewal subscriptions.
Now, non-consumable items,
they're things like game levels
of maps, or run off purchases
that are designed to be used
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of maps, or run off purchases
that are designed to be used
across multiple devices.
Order renewal subscriptions,
they're like your periodicals
where you are setting up
a subscription payment
for ongoing delivery
of new content.
If you're offering
either of those,
you have to make sure
you can offer the ability
to restore previous
transactions.
That is allow the user
to get back what they've
bought before through your app.
Especially if they buy a
new device or otherwise need
to get back what
they've already paid for.
But if you're offering
consumables
and non-renewing subscriptions,
so a consumable might
be something like gas
in a racecar track, gems,
or coins, or currency,
generally an item that's
purchased, then used up,
and ideally purchased
again, and again, and again.
If you're offering
those sorts of items
through In-App Purchase, then
it's up to you, the developer,
to persist that state.
You can't restore those.
Now this comes back to what
I was just saying before
about making sure you
call finishTransaction
at the right point and time.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Because you get one
shot at a consumable
and non-renewing subscription.
You have to make sure your
app handles the transaction
properly, makes it
available to the user,
and only then calls
finishTransaction,
because you can't restore those.
But for the content
types you can restore,
you call SKPaymentQueue
default queue
restoreCompletedTransactions.
Now what happens
here, is of course,
you need to observe the queue
because you will receive these
delegate callback methods
such as
restorCompletedTransaction
FailedWithError if
things don't go well,
or paymentQueueRestoreCompleted
TransactionFinished.
At that point you can go
and inspect the receipt
to know what purchases were
restored, unlock those features
and content, but, note that this
requires a network connection
and the user will be prompted to
sign into the store if we have
to verify their identity so that
we know exactly what they've
paid for to get you that list
of restored transactions.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now just because your app has
to offer restore transactions,
it doesn't mean you should
call it all the time.
Because it requires
a network connection
and because it will
require the user to sign in,
it should be something you do
only when the user asks you to.
I know a lot of developers
think it would make sense
to just call restore
transactions every time the app
launches, because
don't they always want
to get back what
they've paid for?
Yes, but when they
have to sign in to do
that it becomes too much of a
heavy-weight process on launch.
So let the app launch and
offer a graceful way for them
to restore transactions if
you have to do that based
on the content types
you're selling.
So let's wrap this
up with a summary.
This is my recipe for
trouble-free In-App Purchases.
So, when you are loading up
your In-App Purchase product
identifiers, the list of product
identifiers that you're going
to sell or offer to the
user, be very careful
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to sell or offer to the
user, be very careful
about how you choose to host
them and where you host them
if you're not going to bake
them into your app yourself.
Because if that first experience
is a spinner while you want
for that to load, or
even worse an error
because the server
can't be reached,
that totally derails
your sales experience.
Cache appropriately if
you can and avoid delay
in presenting these products
by fetching just ahead of time
if you can anticipate.
A great example of this is if
you've got a racing car game
for example, there's probably
just a lot of conditions
that you can anticipate
around when you're going
to offer In-App Purchases.
Let's say I'm going to
offer an In-App Purchase
that allows the user
to go faster
around the track
next time around,
or somehow upgrades their car.
When are we going to offer that?
Well probably when they come
in dead last in the race,
when they finish last.
So those sorts of things we
could anticipate in code right?
If we know they are about
to cross the finish line
and we know that they're
in a bad position,
just ahead of time go and
grab that product information
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
just ahead of time go and
grab that product information
and make sure you've
got it on hand
for when they cross the
finish line in last place
and then you can offer them a
great way to beat their friends
and finish in first
place next time around.
Likewise, the same applies
when you go and fetch
that product information,
the localized product
information from the App Store.
Fetch only the products
you need.
Sure a lot of you have got 10s,
and 10s, maybe even hundreds
of In-App Purchase
identifiers in your app,
but you're probably only going
to offer 3, 4, 5, maybe 6 items
to the user at any
one point in time.
And because this involves a
round trip to the App Store
and network, you only want to
fetch just the products you need
so that this happens
as quickly as possible.
So fetch it just ahead of time,
just like we said before try
and anticipate when you're
going to display this,
fetch just ahead of time
to avoid any delay there.
It just makes for such a smooth
In-App Purchase experience
if the user can go directly
from the point in time
when they may be enticed to buy
something to being able to tap
that Buy button straightaway.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now when you show your beautiful
In-App Purchase UI and I've got
to mention earlier today there
was a really great session
called Designing a Great
In-App Purchase experience.
You should really check
out the video of that.
Because it goes far more in
depth about design techniques
about how to provide a great
In-App Purchase experience.
But from a code level make sure
that you ensure proper
localization.
We deal with a global
marketplace.
And just because we're
familiar with a particular way
of representing currency
or a particular display style
doesn't mean everyone else will
like that, let alone want to buy
your app if you get it wrong.
So take care to ensure
great localization.
It makes users feel comfortable
and it makes them feel happy
that the customs they're
familiar with are being enticed
and enjoyed by your app.
Do not convert the
currencies though.
Everything you get back in the
SKProduct object will be correct
for the store that the
user is signed into.
And when they make the
purchase, that is you take
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And when they make the
purchase, that is you take
that SKProduct object,
make and SKPayment throw it
in the payment queue, once
you've added it to the queue,
step back, and be hands-off.
There's no step three, remember?
Create the payment, add it
to the queue, that's it.
Let the payment queue then
drive the further updates.
And then process those updates.
Make sure you verify the receipt
to make sure it's a real
monetary transaction
that happened with the App Store
and that nothing
funny's going on.
Unlock those features
and content as soon
as you possibly can
for a great experience.
And avoid deprecated APIs and
unsafe receipt verification
that could leave you opened
to unauthorized transactions
occurring.
If you've got to
download content
to make a feature available,
get that asset as reliably
and quickly as possible.
Use our In-App Purchases
if you can.
If not, if you're going
to download it yourself.
Make sure you use those
background download APIs.
And lastly make sure you
finish the transaction.
If you need more
information, there is --
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
If you need more
information, there is --
we an evangelist mailing list
setup that you're welcome
to email to get help from our
wonderful evangelist team.
There is also the In-App
Purchase programming guide
available online and also the
StoreKit framework reference.
And there's the Apple
developer forums
that are great resources
wealth for discussion and help.
There are some related
session that I mentioned,
Preventing Unauthorized
Purchases
with Receipt that's me
again on Friday morning
up in Pacific Heights.
That's where we'll take you
through how to make sure
that your revenue is protected
and your business
model is enforced
in your app and your service.
If you care about your
revenue come along
and let's talk about it.
Also, designing a great
In-App Purchase experience.
Rachel did an amazing
job this morning in here,
telling developers how
to structure their
In-App Purchase UI
to deliver a great
experience every time.
The video should be
available shortly.
And lastly, Chris Espinosa's
session tomorrow, or Thursday.
He's really good if your app in
particular is targeted at kids.
Thank you very much.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]