TL;DR Can you postpone the finishTransaction call on a consumable in iOS to mimic androids consumeAsync?
In Android one can purchase an item it will end up in a list managed by the billing api. And can be accessed via queryPurchaseHistoryAsync.
An item can then be consumed later. The purchase and the consumption are two separate things so.
An account is optional in our app and we use the described functionality in android to keep track of the items a user has purchased / but not yet consumed also when one lost his/her device.
The docs are not clear on the SKPaymentQueue regarding multi-device synchronization.
The contents of the queue are persistent between launches of your app.
But in practice, it seems to be exchanged between devices.
While different sources suggest you have to take care of managing the state of consumables for your self.
I was wondering if one can use the SKPaymentQueue as a single point of truth by keeping the consumable SKPaymentTransaction in the purchased state. I would therefore not call the finishTransaction before the item has actually consumed.
In theory, the transaction should stay in the SKPaymentQueue and should be transferred to all devices logged in with the same AppStore account.
As soon as the transaction is finished on one of the devices it should get removed from the queue and again the information should be spread to all devices.
I couldn't find this approach somewhere and I would like to know if I miss something important here.
You should definitely be finishing the transaction on iOS:
StoreKit will call your observer’s paymentQueue(_:updatedTransactions:) every time that your app launches or resumes from background until they are removed. To that effect, your customers may be repeatedly asked to authenticate their purchases or be prevented from purchasing your products.
https://developer.apple.com/library/archive/technotes/tn2387/_index.html#//apple_ref/doc/uid/DTS40014795-CH1-BEST_PRACTICES-FINISH_THE_TRANSACTION
And Android:
Consume purchase corresponding to the given purchase token. This will result in this item being removed from all subsequent responses to getPurchases() and allow repurchase of items of the same sku.
https://developer.android.com/google/play/billing/billing_reference#consumePurchase
The right way to achieve multi-device support is by having a server that validates and keeps track of the purchased products for the users. With a server you can achieve, not only device synchronization but also multi-platform support.
You can also use a service like RevenueCat that does all this for you without the headaches.
Related
I'm creating an iOS app with an auto-renewal subscription. I've been reading a lot of tutorials and documentation but I'm a bit confused about how to handle certain scenarios.
Here's how my app works:
User installs app
User creates account within signup flow
User is asked to select a plan and pay within signup flow
The payment receipt is uploaded to my server and I activate their account in my database.
My server polls the /verifyReceipt endpoint on regular basis to renew the user's account or deactivate it depending on what the latest info from apple. (or use Apple's new Status Update Notification, both serve the same purpose to get me the latest subscription info on my server)
After a month when the subscription renews I know a transaction will appear on the SKPaymentQueue on the user's device. Because of this a lot of tutorials/documentation recommend having your AppDelegate implement the SKPaymentTransactionObserver protocol so that you can handle a transaction at any time.
But, I didn't use AppDelegate. I used the view controller in signup where the user picks their plans to implement SKPaymentTransactionObserver.
My reasoning is that since I'm getting info on the backend do I need to care about the transactions that will show up in the queue in the client each month when the subscription renews? Can't I just ignore these transactions, or will I need to call queue.finishTransaction on them?
I also read some things about restoring transactions when the user deletes the app and re-installs or gets a new phone. Again, do I need to worry about this? Because I should still know about the subscription on the backend and all the user has to do when they get a new phone is log in to their account for my service and it'll check the backend to see if their subscription is active.
I guess my larger question is: When you have a backend to handle IAP auto-renewal subscriptions, can you ignore some of the stuff happening on the client with the payment queue because that feature was built for apps that don't have a backend.
It's best practice to implement the observer immediately in the AppDelegate in case something goes wrong between the user being charged by Apple and you upgrading their account - if they close the app or it crashes you may lose that transaction.
Also, I think I've had cases where I forgot to call finishTransaction and that annoying iTunes login prompt kept popping up, not sure if that was a Sandbox only event though.
Like #Paulw11 said. Don't rely on the Status Notifications. At time of writing, they don't provide enough info to update a user's status, namely any sort of user identifier. Refreshing receipts from the backend is the way to go. If a new receipt is posted to the SKPaymentQueue (say on a renewal), you can handle it like any other receipt refresh for the user on your server.
Here's a good blog post that provides more detail on what should be happening exactly on the server: iOS Subscriptions are Hard
For your restore logic, you don't need to use the StoreKit restore method if you've implemented your own restore functionality through an account based system. If that's the route you want to go, you should definitely listen to SKPaymentQueue in the AppDelegate to avoid as many edge cases as possible where you could lose track of someone's subscription status. The good 'ol "Restore Purchases" button is a great way to fix some slightly flawed in-app purchase code :)
I guess my larger question is: When you have a backend to handle IAP
auto-renewal subscriptions, can you ignore some of the stuff happening
on the client with the payment queue because that feature was built
for apps that don't have a backend.
Don't ignore the payment queue. You can ignore the "Restore Transactions" if you have your own account based restore system.
I'm working on an iOS app and am currently trying to come up with strategies of how to manage in-app purchases:
Android has the nice capability that it caches product ownership information locally on the device and can therefore check ownership without having to prompt the user for a password. iOS, on the other hand, cannot restore purchases at all without asking the user to log in and Apple therefore specifically discourages ownership-checks at app-launch in their Restoring Purchased Products documentation (last sentence of first paragraph).
Now, technically, you can get iOS to provide the same "ownership caching" for non-consumable items as Android does, by just never calling finishTransaction:, except in the case of failed transactions. That way, at every app launch, as soon as you call addTransactionObserver:, you get a list of owned products without a user login request.
Clearly, I can also provide this cache myself using something like NSUserDefaults, but that would be a bit more work and just adds more opportunities to make mistakes in implementing the ownership tracking. And since I need to handle randomly reported transactions from the transaction observer anyways for good measure, I would basically have to implement the above idea and then duplicate its functionality again using NSUserDefaults or such.
Thus my questions: Is it OK to (mis-)use unfinished transactions in this way? Will they ever expire automatically? Might Apple reject my app if they notice that this is what I'm doing? Are there any negative implications of this idea (reliability, performance, wasted memory/storage)?
I intend my app to gain access to my server after the in-app purchase is complete. This access requires real work, and data & traffic ($$).
A typical client might own anywhere up to a couple of hundred devices (and I assume on one account, for their simplicity).
Thus I only get the one payment across many, many devices interacting with the server.
I'm not trying to be stingy - but can I prevent multiple devices sharing the same purchase (in my case server access)?
Or do I implement another method for selling access?
You'll have to implement another method. Apple's guide explicitly states that:
Users can restore products that they previously purchased—for example, to bring content they’ve already paid for onto their new phone.
If the same iTunes account is associated with more than one device then Apple requires any purchases to be available on all those devices.
#Tommy is correct - regarding non-consumable IAPs. However, you could implement a non-recurring subscription in-app purchase instead and give each device a unique identifier. When the user attempts to access the server, you could see if that identifier is in your database (if the user has purchased the IAP), and determine whether or not they need to purchase a subscription. Even though you may not want a subscription-based service, you could probably make a membership for a very long period of time like 10 years (it's not required to be selected in iTunes Connect) and handle it that way. Only issue is that you need to figure out a way to give each device a unique ID that won't change.
Further to the #Tommy answer, in the February 3, 2014 version of Apple's Getting Started with In-App Purchase on iOS and OS X it says:
There are a handful of important guidelines to keep in mind as you design your application:
- You must make your In-App Purchase items available to all of the devices registered to a user
and
Users view the items they purchase as theirs to keep and permanent. Therefore be sure
purchased items are available in all instances of your app running on all the devices the user
owns, even after your app is deleted from a device, reinstalled, or downloaded to a new device.
and
For Non-Renewing Subscriptions, [blah, blah, blah]..
Additionally, your application must include a mechanism to deliver the purchased Non-
Renewing Subscription In App Purchase to all iOS devices owned by a single user.
Looks like there is no way to restrict items to a single device :(
My client has an app in the App Store that uses the auto-renew subscription type. Upon resubmitting the app it was rejected because they claimed they don't have periodic content (even though they do... but this problem seems to be ubiquitous currently). I was wondering what the best approach is to deal with non-renewing subscriptions. Specifically issues dealing with:
Losing your device and restoring your subscription
Restoring your subscription to multiple devices (as mandated by Apple)
Preventing users from abusing the system.
What I came up with so far is the following:
When a user purchases a 1 month subscription this information is stored locally (say in NSUserDefaults). Also, a unique ID is generated and sent to my clients server. When the subscription ends users are asked whether they want to be directed to the purchase screen. If users wishes to save the subscription in case they need to restore they device, or in case they lose their device, they can opt to have the unique identifier sent to their email address (which they are prompted to enter, indicating that this information will not be used). The app has an place to enter this ID. It will retrieve the subscription information from the server and again store this in the NSUserDefaults. Each time a restore is done, a counter is increased. When it has reached say 5, the user can no longer restore. The same principle described above works in the case of sharing subscriptions over multiple devices. Does this seem like a reasonable solution (and one that Apple will accept)?
Thanks for your time!
Why don't you want to use Apple's restoreCompletedTransactions? It just provides you with information about ALL the previous purchases, on any device, at any time.
http://developer.apple.com/library/mac/#documentation/StoreKit/Reference/SKPaymentQueue_Class/Reference/Reference.html#//apple_ref/occ/instm/SKPaymentQueue/restoreCompletedTransactions
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.
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.
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.
I have one solution,
Configure the product as consumable.
this will solve the problem - (They require that every time we sell a piece of this content (i.e. user pays us)).
Next you need to implement a logic in product buy option. It is in a way that once the user purchase a product the buy option need to remove otherwise the user may happendly go purchase and lost is cash once again for same product in same device.
you can use NSUserdefaults for this purpose.
thanks,