Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differentiating between initial buy and free "re-buy" in StoreKit/In-App Purchase

From the StoreKit guide:

If the user attempts to purchase a nonconsumable product or a renewable subscription they have already purchased, your application receives a regular transaction for that item, not a restore transaction. However, the user is not charged again for that product. Your application should treat these transactions identically to those of the original transaction.

This presents a huge problem in an app I am working on. We have licensed a large body of content from a publisher for sale through in-app purchase. They require that every time we sell a piece of this content (i.e. user pays us), our server calls an API on their servers to report the transaction. This is for accounting purposes and ultimately used to determine how much we pay them at the end of the month, per our agreement with them.

I have read several suggestions on SO and elsewhere about calling restoreCompletedTransactions rather frequently and maintaining a local understanding, on the device, of what the user has already purchased so they cannot be allowed to purchase it again. This to me seems like something that should be able to be implemented on the server side. However, the receipts that we are getting back from the Apple servers are exactly the same for a buy and a re-buy, as promised by the StoreKit guide.

If payment callbacks from StoreKit cannot be trusted as a valid accounting mechanism in this kind of situation ("you got paid" vs. "you didn't get paid"), what other real-time insights into transaction traffic are available? I don't think the publisher we are working with is going to be happy if we tell them we have to wait 45 days after the end of the month to get the REAL paid dollar amount out of iTunes Connect.

like image 863
Ben Englert Avatar asked Dec 21 '22 15:12

Ben Englert


2 Answers

I have recently looked into the same problem. In my case, I wanted to implement accurate revenue tracking using Mobile App Tracking to track revenue generated from different customer acquisition campaigns.

Fortunately enough, there is a way to do it. It should be noted that SKPaymentTransactionStatePurchased vs. SKPaymentTransactionStateRestored solely depends on the initating action, e.g. whether you initiated a restore or a (re-)purchase, so that doesn't work.

What does work instead is checking for SKPaymentTransaction.originalTransaction which will be != nil for restores and re-purchases. The latter is unfortunately undefined behavior (docs). I'd consider a null check fair enough though.

Another option is to validate the transaction-receipt of transactions with SKPaymentTransactionStatePurchased and check that the original_transaction_id property in the returned, validated receipt matches the transaction_id.

like image 187
Johannes Rudolph Avatar answered Dec 24 '22 03:12

Johannes Rudolph


The bad news is: In the current iOS version (4.3.x) there's no way to distinguish between a buy and a re-buy of non-consumable products.

To ease the situation I would recommend two things:

First

After a successful purchase, store the product identifier of the purchased product in the NSUserDefaults on the device. You can then hide the already purchased products from the user and thus handle a re-buy situation.

The NSUserDefaults are backed up by iTunes when the user synchronizes his device. So your stored purchase information is not lost when the user gets a new device.

Second

Store the receipt data together with the device ID on your server. Analyze the receipt's product identifier and the device ID.

If you receive another receipt with the same product identifier and device ID combination, then assume a re-buy. At least this would allow you to cover most of the re-buy cases.

Assuming that an ordinary iPhone user switches his device every 1-2 years, you will at least cover most of the re-buy cases and maybe apple will fix this in the future.

like image 42
leviathan Avatar answered Dec 24 '22 03:12

leviathan