Associating Non-renewing created transactions with completed transactions

206 Views Asked by At

I have an app with several entities per user. User can do IAP for each entity. When I'm adding the payment to the SKPaymentQueue, I know to what entity the purchase is done for.

How can I get this info from the updatedTransactions callback?

I've tried to match transaction identifier from purchasing transaction with transaction identifier in purchased transaction, but this value exist only in purchased transaction.

I've tried to use applicationUsername:

func buyProduct(_ product: SKProduct) {
    let payment = SKMutablePayment(product: product)
    payment.applicationUsername = "character ID"
    SKPaymentQueue.default().add(payment)
}

But the docs mention its unreliable and only used for fraud detection.

It works most of the time, but Apple do not guarantee to return this in the callback transaction.

2

There are 2 best solutions below

4
On

Product types

There're four product types you can offer:

  • Consumables
  • Non-consumables
  • Auto-renewable subscriptions
  • Non-renewing subscriptions

Let's skip subscriptions here and focus on (Non-)consumables. What's the difference?

  • Consumables
    • User can buy it multiple times
    • They're not synced and can't be restored
    • An example - in application currency, you can buy any amount, repeatedly, ...
  • Non-consumables
    • User can buy it once (and quantity 1 only)
    • They're synced and can be restored
    • An example - unlock of the application feature, bundle of features, ...

Identifiers

Every product has a unique identifier (productIdentifier). It's accessible from everywhere:

Custom data

Can you assign a custom data to a transaction? No. There's just the applicationUsername you mentioned. But it has flaws like:

The applicationUsername property is not guaranteed to persist between when you add the payment transaction to the queue and when the queue updates the transaction. Do not attempt to use this property for purposes other than providing fraud detection.

You can't use it for custom data. And even if you try to abuse it it's still of no use, because it's not guaranteed to persist.

Buy non-consumable

This is the easy part as it allows the user to buy it just once. You have the productIdentifier which is available all over the place and then you can unlock features, download content, ...

But it clearly isn't what you want. Anyway, you can achieve what you want in this way as well, but you have to create a unique product for every entity and entity & user combination. Which can lead to a millions of product identifiers. I can imagine that this will be a maintenance nightmare.

Buy consumable

This part is trickier, because you, as an app developer, are responsible for:

  • converting it to any in application goods (currency, game characters, ...)
  • making it available across all the user's devices
  • enabling users to restore past purchases

In other words - consumable behaves like any other payment gateway (Stripe, ...) - you know that user paid you for specific productIdentifier and that's it - everything else has to be handled in your application, on your server, ...

An example

Imagine you have a game where user can buy a new game character. There're two ways how to achieve this.

Non-consumable

  • Every game character has a unique productIdentifier
  • User can buy every character just once
  • You can restore these purchases

Pros:

  • Small amount of work on your side

Cons:

  • The list of productIdentifiers can grow a lot, especially if you have a lot of characters, they do differ for each user, etc.

Consumable

  • You have a custom in application currency user can use to exchange it for any game character
  • You can offer in application purchase products like
    • Credit 10
    • Credit 50
    • Credit 100
    • ...
  • You have to build your own store on top of the StoreKit
    • Show the user how much credit he does have
    • Allow him to buy any game character
    • Enough credit? Just mark it as bought
    • Not enough credit? Inform the user and offer him to buy more credit
      • In the meanwhile, store somewhere that he wants to buy a specific game character
      • When he has enough credit (in application purchase), look at your storage and exchange it for the game character

Pros:

  • Flexibility
  • Short list of productIdentifiers

Cons:

  • Syncing is on you
  • Restoring is on you
  • Custom store on top of StoreKit is on you
  • StoreKit acts as any other payment gateway for you
0
On

There is no way in apple's API to associate transactions with specific entities, so you have to do the association yourself.

I can only think of two ways:

  1. You can register a stateful callback handler to handle the specific entity, but you have to block multiple concurrent purchases from the same user in your application. Once the purchase is completed (success/fail) you remove the handlers.
  2. If you have a limited number of entities (e.g. at most 10 per user) which can be ordered you can register 10 products (product-for-entity-1, product-for-entity-2, etc. - one per entity) and purchase the correct one when a user purchases it for the numbered entity. This way you know what entity the subscription was applied to.

Other than that apple's API does not correlate initiated transactions with completed transactions (by transaction id, or allowing metadata, for example) - making it very hard to associate user actions to their initial cause.

This is very poor in terms of UX since the user has to interact twice, in a potentially long-running process, for no apparent reason.