Other articles in the series:

[1] Apple In-app Purchase (IAP) from Starter to Master (1) – In-app purchase product types and configurations

[2] IAPS from Beginner to Master (2) – Bank card and tax information configuration

[3] App Purchase (IAP) from Beginner to Master (3) – Product Recharge Process (non-subscription)

[4] IAPS from Starter to Master (4) – Subscribe, renew, Unsubscribe, restore subscription

[5] IAPS from beginner to master (5) – drop handling, hook prevention and some pitfalls

preface

In our previous article on Iaps from Getting Started to Mastering (3) – Goods Recharge Process (Non-subscription), we briefly covered the entire consumable items recharge process (there are other types of non-subscription items, but consumable items are the most common). The general process is as follows:

Add to monitor (addTransactionObserver) - > add new trading (addPayment) - > payment - > successfully returns transition (SKPaymentTransactionStatePurchased) - > check payment instrument FinishTransaction -> finishTransaction

However, combined with the actual business and stable online use, there are still a lot of places to deal with, as well as some abnormal situations to pay attention to. Below I’ll illustrate some real-world business scenarios that combine IAP with my own business experience, as well as some anomalies that have been validated by a lot of data online.

1. When the App starts and before launching a new recharge, it is necessary to check whether there are any outstanding transactions

In the previous # IAP from Starter to Master (5) – drop handling, hook prevention, and some pit 4.(4) points, I briefly explained the need to enable payment queue listening at App launch and before initiating new payments.

After these two scenarios are processed, any paid but uncompleted transactions (without finishTransaction) will be returned to the developer in the SKPaymentQueue. The developer will first process the check ticket for this transaction. If an empty array of transactions is returned, continue to create a new transaction.

It is important to note that addTransactionObserver adds the same listener multiple times, which is actually just one addition. So in these two scene, want to receive trading after each call queue returns results, should be in accordance with the startup called addTransactionObserver, launched a new payment before calling restoreCompletedTransactions queue to check transactions.

Some teams feel that they can dispense with checking the transaction queue, believing that the probability of losing an order is low. However, based on our online experience, it is easy to lose orders because apple’s interface request fails or users actively kill the process in the payment process (for example, the loading card dies). So make sure you check both locations.

So why is the “first call addTransactionObserver, then call estoreCompletedTransactions”? Here are some examples of how each of these calls might result:

  • Scenario 1: No pending transactions –> Start call addTransactionObserver –> No messages will be called back –> Call addTransactionObserver before payment –> No information will be called back –> Process is stuck

  • Scene 2: no unfinished transactions – > startup do operations – > payment before call restoreCompletedTransactions — — > callback no news > process

  • Scenario 3: No pending transactions –> No action at startup –> addTransactionObserver is called before payment –> No messages are called back –> Process is stuck

  • Scenario 4: No pending transactions –> Start addTransactionObserver –> Do not call back any messages –> Initiate a new transaction without judging whether there are pending transactions before payment –> Normal

    This scenario seems fine, but when combined with the order number matching in the actual APP business, it can cause a mismatch between the order number and the TransactionId. Such as:

    • “Order A-6 yuan commodity -TransactionA” purchase success –> Network interruption before check bill, TransactionA temporarily shelved –> create A new order again — “Order B-6 yuan commodity” –> Transaction queue At this time initiate payment of order B, According to Purchased, it will call back TransactionA in priority to verify tickets (if the order number of Purchased order B matches TransctionA, the business layer may match incorrectly). (assuming successful) Next purchase will initiate “Order C-6 yuan commodity” again –> resume the normal top-up process

    • “Order A-6 yuan commodity -TransactionA” purchase success –> Network interruption before check bill, TransactionA temporarily shelved –> create a new order again “Order B-12 yuan commodity” –> Normal recharge and pay 12 yuan and verify the ticket successfully –> create a new order again “Order C-6 yuan commodity” –> go to the previous process, Namely, the callback queue priority callback state of TransactionA

  • Scene five: No pending transactions –> Start addTransactionObserver –> No messages will be called –> Call addTransactionObserver before payment –> callback empty transaction array, Determine that there is no recoverable transaction –> initiate a new transaction –> proceed normally

  • Scenario 6: Call addTransactionObserver before payment — call back the empty transaction array. Determine that there is no recoverable transaction –> initiate a new transaction –> proceed normally

To sum up, according to the startup called addTransactionObserver, launched a new payment before calling restoreCompletedTransactions to check transaction queue, most can meet a variety of scenarios of business layer, and there won’t be a bug.

Tips: for restoreCompletedTransactions this method, according to apple’s official document said:

Use this method to restore finished transactions that is, Transactions for which you have already called finishTransaction: : (Use this method to restore a completed transaction for which you have called finishTransaction:.) .

This is generally used to restore subscription items (and it is used to restore subscription items). However, in my tests, transactions of consumable goods that do not finish can also be returned through the Transaction queue after this method is called. If anyone else here has experience, we can join you in the comments section.

2. Timing and location of order number cache

In addition to Apple’s own transaction sequence number, TransactionId, most developers have an order number belonging to their (or company’s) business (replaced with orderId below). If the payment is successful, when verifying the bill with Apple, it will generally compare orderId with Transaction to determine whether the transaction in the bill is the commodity corresponding to this orderId. If the match is successful, the actual purchased props will be delivered to the user.

But because the callback to payment completion is asynchronous, the orderId is generally cached to try to match as accurately as possible. Most people will either pass this order number through applicationUserName or cache an order number locally. In point 2 of # IAP from Beginning to Master (5) – drop handling, hook prevention, and some pokholes, I showed that applicationUserName can easily be lost and empty in some scenarios in the real world. This actually works well in a sandbox environment: you enter the sandbox password, immediately kill the app, wait for a few seconds, and the system level pops up a “payment completed” window, which indicates that you actually made the payment. Start the app again and check the transaction queue. The transaction queue will be returned to you because you did not call finishTransction, but the applicationUserName in the Transction object will be empty.

It is recommended to cache the orderId into the Keychain when creating the orderId.

Some people tend to cache after Purchased state callbacks because by then Transction objects already have TransactionIDS and can be matched one-to-one. But if it’s the scenario I mentioned above, you can’t cache it; The applicationUserName is empty, causing the orderId to be lost and causing the check ticket to fail.

3. FinishTransaciton is called: a phenomenon that persists in the transaction queue

The packet capture software shows that when the code calls finishTransaciton:, not only the transaction is completed locally, but a network request from Apple is sent. The request is completed only when it succeeds. In an online environment, Apple’s interface is prone to requests for exceptions, such as requests that fail when the network is bad. Even if finishTransaciton: is called, the transaction still exists in the transaction queue.

Official advice calls finishTransaciton there are two types of timing: trade status to SKPaymentTransactionStatePurchased or SKPaymentTransactionStateFailed.

In this case, after Purchased, finishTransaciton is called but not fished after the ticket verification is successful. Then at the next call restoreCompletedTransactions check payment queue, is likely to return two transition trading (the same transaction ID), it will check for the first of two bills. However, since the bill has been verified before the last call to Finish, the two checks are repeated, which does not affect the delivery of goods and will not lead to order cancellation. The front end just needs to do a prompt for this state.

Transactions in the Failed state have not been completed, so even if finish fails, the next time the transaction queue is checked, the transaction will return to the Failed state. This time, when the network is normal, the transaction will be finished.

4. Deductions success but the callback SKPaymentTransactionStateFailed state

Because of the reason of apple’s interface is not stable, the online environment has a certain probability to appear “deductions succeeded, but return SKPaymentTransactionStateFailed status”, rather than a return to the transition. This situation is generally accompanied by the failure of the above mentioned finishTransaciton. After the network is restored, before the next app startup or recharge, to check the unfinished transactions, then the transaction queue will return a transaction in the Purchased state, and the TransactionIdentifier is the transaction serial number that returned Failed. In this case, you can initiate ticket verification again.

Now consider the case of the orderId for the business layer. Some developers will not only remove the cached orderId after the transaction is completed, but will also terminate the orderId if the transaction returns with failure. There is no problem except in the above special cases. However, if orderId was deleted when Failed and the passthrough parameter of applicationUserName was removed after restart, then when returning to Purchased, orderId will be lost and ticket verification will fail. This leads to the situation of dropping orders.

Therefore, it is recommended that the cached orderId be cleared only on successful ticket validation and not elsewhere. If a new recharge is initiated, the old orderId is overwritten by the new one.