iosswiftstorekitreceipt-validation

In what scenarios is SKPaymentTransaction.transactionIdentifier the same as the validated receipt's transaction_id?


The documentation for SKPaymentTransaction.transactionIdentifier notes:

This value has the same format as the transaction’s transaction_id in the receipt; however, the values may not be the same.

And the documentation for transaction_id notes:

This value has the same format as the transaction’s transactionIdentifier property; however, the values may not be the same.

You can use this value to:

• Manage subscribers in your account database. Store the transaction_id, original_transaction_id, and product_id for each transaction, as a best practice to store transaction records for each customer. App Store generates a new value for transaction_id every time the subscription automatically renews or is restored on a new device.

• Differentiate a purchase transaction from a restore or a renewal transaction. In a purchase transaction, the transaction_id always matches the original_transaction_id. For subscriptions, it indicates the first subscription purchase. For a restore or renewal, the transaction_id does not match the original_transaction_id. If a user restores or renews the same purchase multiple times, each restore or renewal has a different transaction_id.

Also will note the Receipt Validation Programming Guide (from the Documentation Archive) states under Transaction Identifier:

This value corresponds to the transaction’s transactionIdentifier property.


With that noted, my question is: when is the SKPaymentTransaction.transactionIdentifier the same value as the validated receipt's transaction_id, or when isn't it?

In our app we only work with consumable in-app purchases, no subscriptions. In this scenario, are these two values the same? I ask because I need to be able to record the purchase server-side along with information about the user who purchased it. See below for an explanation of the process and problem it presents.

Let’s say a user purchases a consumable product and the request to record that transaction fails because our server is down for example, so we do not call finishTransaction:. Now let’s say that person logs out and logs into a different user account and purchases another consumable product and again we fail to record it. There are now two consumable products in the receipt. When they launch the app next paymentQueue(_:updatedTransactions:) is called with two items in the transactions array to inform us there’s purchased transactions we need to finish. We need to submit the receipt to our server to record the transactions but we also need to send some additional info along with each transaction such as the user id who purchased it. This means I need to persist this info on disk with an associated transactionIdentifier so I can get that data later. I can send an array of user ids and the receipt to the server. But how would the backend know which transaction in the receipt matches which user id in the array? I don’t believe the in_app array is guaranteed to be sorted in any particular way and may not match the order of the transactions array provided to paymentQueue(_:updatedTransactions:). So when there’s multiple transactions to be recorded, how can we properly link them so the purchase is applied to the correct user account server-side? In this scenario, is the transactionIdentifier available in-app guaranteed to be the same as the transaction_id in the validated receipt?


Solution

  • In testing we've determined the transactionIdentifier is the same value as the transaction's transaction_id in the validated receipt for consumable purchases.

    However, we are reluctant to rely on that being the case due to the note in the documentation stating it may not be the same value. So we put a fallback in place to uniquely identify a transaction using a combination of the transaction.payment.productIdentifier and the transaction.transactionDate, which is documented to correspond to the original_purchase_date_ms in the receipt. This allows the backend to find the correct transaction in the receipt for the information the app submitted.