So I thought I had in-app purchase working, but my app sometimes triggers a popup user authentication action sheet with an old test user autopopulated in the action sheet. I've been creating test users with a sequential number, just because that's easy, and I can keep track of how old the users are. My current test user is #4. New test users were generally created because the old users were invalidated somehow.
Anyway, I sometimes get the popup screen for testuser #1 still (as well as #3). I figure that's because the transaction being looked at was originally dealt with using that user. But shouldn't these popups stop after the transaction is finalized using finishTransaction:?
What should I be checking? I assume I must have left something in limbo somewhere even though the products with downloads have been downloaded and all the products that were bought were finished (albeit with a different user since the original one was invalidated).
I guess what must be happening is that unfinished transactions remain on the Apple server. I can't finish the transactions because the test users used were invalidated by accidental use in production. I will try deleting those users on the theory that deleting the users will also delete the pending transactions.
Related
Implementing subscriptions on an iOS app means we want to handle SSEs in case of subscription status change, as to manage the user's rights inside the service.
The Apple documentation doesn't show an explicit customer GUID.
They provide, in each SSE's payload, receipts, e.g. the latest one.
The question is: how can an API server uniquely identify customers?
Should the API server keep track of every receipt, in an inheritance way?
I.e. should the API persist every receipt, and a link to the previous receipt, up to the first
one that was emitted (which can be linked to a customer through the iOS app the user used to initiate its subscription), and re-trace this history on every SSE?
Per this thread, the applicationUsername service-side UUID (used by apple, for "irregular activity checking") isn't a solution, as it isn't provided in receipts or SSEs. The thread hints that using the transaction IDs is still the best way to identify a SSE, as it is ultimately tied to a user, and it is up to the service to keep track of transactions made by the user on the Apple platform for the given service.
Isn't there an easier, more direct, way, to establish the relationship between a SSE, its apple customer, and the service's user?
After digging for a while and asking around, I reached the following conclusion:
There is only one way to tie a transaction event to a user, and that is by the
restoration mechanism one has to implement on their application.
For this reason, you need to at least keep track of original transaction IDs,
so when the restoration mechanism is triggered on your app, you can check the
currently logged user on your app, and call your back-end with the logged user,
and a list of original transaction IDs.
Usually, when a user subscribes, it is mainly done from within your app, so you
can do this link in a trivial way.
But, because you can "re-subscribe" from the AppStore after a while, without
going through your app, Apple may send a SSE to your back-end which would contain
a new (hence unknown) original transaction ID, since this new subscription is
has a new transaction.
In short,
You need to keep at least a DB of original transaction IDs you receive as SSEs
You need to implement the restoration mechanism as to contact your server every
time the app starts, with the list of transaction IDs / original transaction IDs Apple gives you
You need to accept having transactions stored in your backend without a link
to a user (because the user may have re-subscribed without launching the app yet)
I've read a lot of SO answers about handling auto-renewable subscriptions with your own user login system, but there is an issue that I am still not sure how to handle: what to do when the transaction observer is triggered while the user is logged out.
Apple recommends to implement a transaction observer right in the App Delegate:
Adding your app's observer at launch ensures that it will persist
during all launches of your app, thus allowing your app to receive all
the payment queue notifications.
Say the user starts a purchase, but it doesn't complete immediately (for instance because it needs to be approved by their parents, or the app crashes, etc.). The user logs out and opens the app again, and since we are observing transactions in App Delegate, we might receive a transaction immediately when launching the app, before he logs in. We are then unaware of which user to associate the subscription to.
Two ideas:
Non-ideal: should I store the fact that there is a subscription pending and assume that the first user to log in will be the right one, and then associate it in some way to them once they log in? And if so, where would I keep the receipt? KeyChain, UserDefaults? This sounds pretty clunky.
Another way that sounds better: can I store some information about the transaction when it is initiated, and then use one of these fields to actually know exactly whose user's subscription purchase just finished?
(Not particularly relevant, but FYI I am using SwiftyStoreKit).
This is how I would approach this:
When the user tries to purchase a subscription, I first have them login or create an account.
Once the user is logged in, I send their appStoreReceipt to my server and store it there. I check and make sure the user is able to purchase. (At this point they should have the subscription they are trying to purchase)
Once I get a response from the server that the user should be able to purchase I go ahead and start the in-app-purchase
When the in-app-purchase process is done, I send the updated appStoreReceipt to my server and upgrade their account.
The problem you are worried about is what if the user gets done with step 3 but never gets to step 4.
Well, since I have their receipt stored on my server (from step 2), I can just ask Apple to give the latest version of their receipt, and if it shows up that they did purchase, I upgrade their account. You can choose when the right time is to do this check, it can be every time the app launched, or every time the user logs.
Hope this helps.
Working on ironing out some UX considerations around a push notification system using WebPush and ServiceWorkers. Additionally, this system should gracefully handle multiple users sharing the same browser.
More concretely, I have the following constraints:
1) A user should not be able to see notifications which are not intended for them.
2) The state of a user's push subscription should be opaque to and immutable by other users. i.e. the state of my push subscription should not be based on the behavior/actions of anybody but my own.
Satisfying the first constraint was quite straightforward. I decided to store the currently logged-in user id in IndexedDB and include the id of the intended user within the push payload. It is then straightforward to only show the push notification to the user if those 2 ids match.
However, satisfying constraint 2 has proved to be quite elusive. So far I've tried:
1) One-to-one mapping between users and push subscriptions. This was the first thing I tried, as it struck me as the most natural mapping. However, this ends up falling flat on its face as creating a new push subscription will invalidate the subscription for the previous user, thus requiring the original user to create another subscription on subsequent logins, thus failing the second constraint.
2) Shared push subscription. This has the benefit of not invalidating the push subscriptions, so subsequent logins for the original user will behave as expected. However, none of the subsequent users will actually need to grant permission to the browser, thus failing the second constraint.
3) Even if I was able to get something working with one of the above 2 options, there is still nothing preventing another user from simply going to the browser settings and disallowing notifications, thus nuking all push subscriptions. However, I imagine that this is just something I will have to live with that will have no elegant solution.
I'm certain there have been many brilliant minds working on the above problem, so I'm all ears with regards to how constraint 2 above has effectively been satisfied.
Thanks.
I had the same problem when I was developing Pushpad. We tried different solutions and some of them, such as the many to many relation between users and browsers, became a nightmare very soon. So I would recommend the following approach, which proved to be the best one for us.
Each subscription (endpoint) is a device (browser) and can be associated at most to one user at a time. Whenever possible try to keep data associated to users and not to devices. In this way a subscription (endpoint) can be transferred to a different user and you don't loose data when the endpoint expires or gets replaced. Then when you need to send a notification filter your audience based on the user data, find the recipients and send the notification to the associated devices.
The only data that you may want to associate to devices are device preferences and device preferences are global to all the users that use that browser. This is consistent with the fact that the browser permission (allow / block) is global to all users.
The above solution partially meets your requirements:
1) A user should not be able to see notifications which are not intended for them.
Yes:
when a user logs out you can remove the association between the user and the device
when a browser (user) logs in with a different account on your website you associate the device to the new user (and you remove the previous association)
2) The state of a user's push subscription should be opaque to and immutable by other users.
Yes, because you don't keep data associated to the device. You associate data to the users in your database.
There is no way to satisfy both constraints. You will need to choose either the one-to-one mapping (1) or the shared push subscription (2). You cannot use the browser to deliver your push notifications and at the same time expect certain behaviour (w.r.t. permissions, nuking) from that very same browser...
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)?
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