Hi, PBK!
Looks like you took some time to really look things over for me, thanks a bunch!
> You state you don't finishTransaction for a failed transaction - but you do in the code above. Which is it?
Sorry, to answer your question, the code here is more accurate than my prose was (when I wrote my post yesterday, I mistakenly implied that I didn't finishTransaction for a failed transaction -- I, in fact, do), when I wrote the code, I was actually following the documentation:
"Your app needs to finish every transaction, regardless of whether the transaction succeeded or failed."
Good catch, I have been in agreement with you here all along! I'll try to update my post to more accurate reflect what my code is doing. 🙂
> By delaying the call to finishTransaction until the app receives a call back from your server you open yourself up to unfinished transactions.
Right, that's true, and I implemented this intentionally aware of this possible outcome because the documentation explicitly states that "after you finish a transaction, don’t take any actions on that transaction or do any work to deliver the product. If any work remains, your app isn’t ready to finish the transaction yet."
The documentation then proceeds to list several very failure prone steps as examples:
"Complete all of the following actions before you finish the transaction:"
- Persist the purchase.
- Download associated content.
- Update your app’s UI to let the user access the product.
and mentions that the "unfinished transactions remain in the queue until they’re finished, and the transaction queue observer is called every time your app is launched so your app can finish the transactions" (good!!! but why didn't this user's transaction get "restored" when she restarted her app even though the observer was called?).
Pretty clear guidance supporting my current approach, wouldn't you say?
It also places a lot of responsibility on the service-side for Apple to ensure that EVERY transaction that the user completes in the billing system somehow ends up persisted on the queue for future finishing/restoration -- if I were working on the IAP service for Apple, I would not feel comfortable making that kind of a service-level guarantee. We all know how fragile software can be! 😁
> You need to be sure you have an active transaction observer to receive that transaction the next time the app enters foreground.
Okay, yes, I do not necessarily have an active transaction observer whenever the app enters foreground -- persisting the purchase entails having the user "logged in" and authenticated on my app (so that I know which user account on my backend to credit the purchase to). This doesn't happen until a bit of time after the app enters foreground (but still nearly always happens).
In this case, I do have logs showing that this specific user subsequently restarted the app and her purchases were restored several times afterwards (but the one that ostensibly "already purchased" still doesn't attempt to get restored?).
So I'm not sure... perhaps I should also forcibly finish all of them in my release build as well (I started doing this for debug mode under advice from some StackOverflow post a long time ago, but I was under the impression that restoring would trigger the SKPaymentTransactionObserver protocol method updatedTransactions again, at least that's what I've seen happen time and time again while testing -- AND I do not want incomplete transactions to finish preemptively so that they will still be restored again sometime in the future -- maybe the user's Internet is down, etc. -- this would create a support and bad one-star app review nightmare for us)?
If you have any additional advice for me, that would be very helpful!
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue*)queue
{
RLOGI(@"IAP: paymentQueueRestoreCompletedTransactionsFinished");
#if DEBUG
for (SKPaymentTransaction *transaction in queue.transactions) {
RLOGI(@"IAP: will finishTransaction");
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
#endif
}
In case you don't believe me (unfortunately, we don't see any updatedTransactions logging messages triggered from the other code snippet above interleaved, as we might have expected):
Oct 04 08:31:18 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 08:32:38 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 08:36:44 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 08:53:38 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 08:55:40 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 09:09:56 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 09:12:36 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 09:33:43 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 09:36:49 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 09:37:29 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 09:38:29 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:16:00 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:22:48 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:30:34 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:32:42 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:34:16 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:45:33 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 10:54:57 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 16:45:52 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 19:13:15 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 20:55:16 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 21:47:47 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 21:54:42 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 22:05:45 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 04 22:40:06 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 02:06:36 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 02:36:25 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 02:59:58 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 04:02:40 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 04:03:15 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 04:58:29 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 05:22:01 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 06:00:24 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 07:57:17 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 08:02:04 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 08:03:18 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished
Oct 05 08:54:56 <REDACTED USER EMAIL> LingolandNative: INFO IAP: paymentQueueRestoreCompletedTransactionsFinished