StoreKit: Is it OK to keep unfinished transactions around on purpose? - ios

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)?

Related

Using unfinished transactions to share consumables cross device in iOS?

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.

Check whether in-app payment item has been purchased

I now spent about 24 hours implementing Apple's StoreKit IAP system. After 3 hours I got "purchasing" itself working but the last 21 hours I wasted on implementing all the receipt communication. Now I am struggling to get openSSL working (some bitcode compiler error) and I reached the point of giving up.
I don't care about people cracking my apps or things like this, so is there a simple way of just asking the App Store servers whether an in-app purchase has been made? Without any encryptions, keys, certificates or likewise?
The flow would be like this:
set a NSUserDefault to "true" after purchasing
on every app launch, if there is an internet connection, check again
unless the device is connected to the internet, the NSUserDefault stays on "true"
if a user tempered with the NSUserDefault, it will be overwritten as soon as the app connects to the internet again
If the user finds his way around it, he deserves to keep the item, no user on the world can pay me enough to spend one more hour on that
So if you know a simple way of achieving this, please let me know. I noticed that after buying a (non-consumable) product with the test account, the popup tells you that you've already bought this item before. Maybe this can be used for this purpose?
I am sorry for the rather informal way of posting right now, but this is the first ever time where I think Google did a better job with Android than Apple did with iOS. There are cracks for literally every app anyway, Google's system is far from being bulletproof but in the end of the day, what difference does it make if a cracker takes 10 minutes or 2 hours to manipulate the app?
It took me like 1 hour to get in app payments working in my Android apps but what Apple requires here is ridiculous. Their "instructions" aren't helping at all, it's like they want to make it a secret how to implement all that and you gotta take a guess.
If you don't want to check receipts, you only need to implement the SKPaymentTransactionObserver protocol and add whatever object you implemented it in as an observer using SKPaymentQueue's addTransactionObserver. Ideally, you want this done early in your App Delegate to ensure you receive any transactions sent to your app on launch (this is most important for subscriptions).
In the paymentQueue:updatedTransactions: method, check the transactionState of each transaction. If it's SKPaymentTransactionStatePurchased or SKPaymentTransactionStateRestored, then the purchase is (supposed to be) valid. You can then store the status in NSUserDefaults or a database.
Don't forget to implement restoring purchases.
This works quite well, but anyone who has jailbroken their device and installed whatever software it is that bypasses IAPs will get IAPs for free: it does not need to be specific to your app, they just return "purchased" for any IAP.

Stop deleting and downloading apps again to gain free access

I want to offer my app free for a period and then charge to continue using it. I understand that the way to do this is to force the user to buy an In-App product after the free period. However, if I simply record the date that the user starts using the app in the standardUserDefaults and use this to calculate when the user must buy the upgrade the user could simply delete the app when the time arrives (I assume the sandbox is also deleted) and download the app again for another free period.
First question. Is my reasoning so far correct?
Second question. Is there any way of accessing the date that an app is first downloaded?
Assuming the answers to these questions are Yes and No I have come up with the following solution.
Upon downloading the app the user is first forced to "buy" free an In-App product which then will have the date it was added to the transaction queue. This In-App product would then be downloaded for any subsequent download and I could use the date of this product as my reference date.
As I can find no reference to this problem or solutions I really wanted confirmation that this was a sound way to proceed or if there was another more standard way of dealing with the problem.
Thank you
Silas
You are not allowed to limit you app for free for a limited period:
11.9 Apps containing content or services that expire after a limited time will be rejected, except for specific approved content (e.g.
films, television programs, music, books)
If you want to risk it, you could save the date (encrypted) in keychain to make it more persistent. Just so you know, user are able to access their keychain data if they iCloud keychain sharing and are on a Mac. So if they delete the correct key or rest there device your app will fall back to the free/trial mode.
This does not seem entirely consistent with:
Communicate the value of your products to your users. Users want to
know exactly what they’re going to buy. Combine information from the
App Store, such as product prices and descriptions, with additional
data from your server or the app bundle, such as images or demos of
your products. Let users interact with a product in a limited way
before buying it. For example, a game that gives the user the option
to buy new race cars can allow users to run a test lap with the new
car. Likewise, a drawing app that lets the user buy additional brushes
can give users the chance to draw with the new brush on a small
scratch pad and see the difference between brushes. This kind of
design provides users an opportunity to experience the product and be
convinced they want to purchase it.
https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/ShowUI.html
One way of letting the user interact with a product feature in a limited way is to let them use it a few times before requiring that they pay to use the feature.

How can I validate purchases with Amazon IAP API

In an IOS app, I normally validate that purchases have succeeded and been processed by my application by comparing Apple's list of receipts with one that I add to locally when I've processed the purchase myself. This safeguards against things like app crashes during purchase, bugs, etc that can make things get out of sync. I'm trying to do something similar with Amazon.
I have come across statements like this repeatedly in Amazon docs about purchase validation/restoration:
It is possible for a purchase transaction to have more than one Purchase Token representation, so they should not be used as transaction IDs.
This was a huge help as I thought the exact opposite. Unfortunately, I haven't found the rest of the thought: "The transaction ID can be found here:...". I'm starting to think that their transactions don't offer a stable ID...! Please tell me what I'm missing or, if it really doesn't exist, how I can safeguard against the problems I mentioned above?
The initiatePurchaseUpdatesRequest call under Amazon IAP takes an offset parameter which is a permanent identifier for a restore point. Each time you start up you issue an initiatePurchaseUpdatesRequest starting from the last offset you saw (or Offset.BEGINNING the first time). When the response comes in, you save off the offset from the response for the next startup. In this way, any new but unrecorded sales (due to bugs or multiple device issues) will get caught on the next startup. One, minor, impact of this is that you can actually get notified of a purchase twice - once when it occurs and once on the next onPurchaseUpdatesResponse.
As usual you also have the ability for a user to manually request a "restore purchases" that issues the initiatePurchaseUpdatesRequest with the Offset.BEGINNING parameter in case all else fails.
Ps. The actual receipt validation should be performed by calling an Amazon web service from your own server using the receipt values from either the restore or the purchase responses.
With the current Amazon IAP api, keep track of whether you've fulfilled the IAP item by the "requestId" in the PurchaseResponse object. This "requestId" can be used as a sort of pseudo transaction id. You should save this requestId to a server or to the device's local storage.
See the quick start link below for more details:
https://developer.amazon.com/appsandservices/apis/earn/in-app-purchasing/docs/quick-start#7

In app purchase - method to allow full control of adding products from personal server = allowed?

I have a very strait forward question (and yes I've looked though apple documentation to see if this has an answer but no luck... I may have accidentally missed it though)
Here's my plan:
The problem I've been trying to find a workaround for is that if the admin would ever want to add a product, he would have to log into iTunes connect to add it and also add it in a custom control panel. We, obviously, don't want to make him suffer that so I've been looking for a solution but I need you guys to tell me if it's allowed by apple. Basically I will take over most of the product handling on our servers and will only go to apple with the transactions. This means that apple will not have an in-app purchase set up for ALL the products... only one for each length of subscription (1 month, 3 month... etc) and a few consumable in-app purchases for the various prices of the issues/singles
Side note: I will be selling monthly issues that contain multiple singles for each day of the month. The user will be able to download a full month or a single day at a time if they like.
DEFINITION OF CONSUMABLE PURCHASE - products must be purchased each time the user needs that item. For example, one-time services are commonly implemented as consumable products.
So I will store all the information in our server about the products and if someone chooses to buy a single month's issue that was set to 4.99 (on our server, not apples) then the app will run the in-app purchase with apple that is listed for the 4.99 tier. Whenever a person opens the app for the first time, their app will send some information to our server and they will have a row set aside for them where all the information about their purchases will be recorded so that they can restore them if they switch over to another device.
If you guys think i'm safe in doing this, please let me know so that I can proceed. Also, if this method helps anyone, feel free to use it!
Thanks,
Matt
I think your restore process might be flawed. You talk about the app sending some information up to your server, but what is that information? There is no reliable way to uniquely identify a user across different devices.
If you want to continue on this path you'll want to make sure that your recovery and failover process is very solid. Try out every imaginable scenario. From an app store submission point of view, you'll want to consider a token/coin-based approach. Of course, Apple's guidelines are fairly loose and subject to change so it's always possible you'll get rejected, but tokens are certainly more solid than simply using the same generic in-app purchase.
In a token system, you would set up in-app purchases for different numbers of tokens that the user can purchase as a sort-of virtual currency only valid within your application. Then users can spend these tokens on any items that you dynamically create.
Server-side this means you'll need some way to store how many tokens a user currently has and a way to uniquely identify a user across devices, which is a fairly uncertain proposition. Instead of storing the number of tokens each user has, you could implement some sort of hashing algorithm that generates a hash from an in-app purchase receipt and then sends it up to your server. If the app crashes or the network dies after purchase but before sending your hashes up, next time they open the app you can recalculate all of the hashes, send 'em up, and if the server doesn't recognize a hash it just adds it to the database. Then if a user wants to restore their purchases you simply recalculate the hashes on the device using the in-app purchase receipts you'll receive and then send them up to your server and ask the server to figure out for each of those hashes, how many tokens the user has left. You could think of it like a gift card system, where each hash is one gift card.
Again, app store rules change and if apple thinks you're trying to game the system and not provide a useful experience they have the right to reject you. It could be worthwhile to open a Developer Technical Support request and see if an apple engineer can provide you with a better solution or tell you if the reviewers are likely to accept your application.

Resources