Why Non-Consumable product has originalTransactionId?

I try to call Get Transaction Info from App Store Server API, and the transactionId is for a Non-consumable type product, but it is odd that there are so many different transactionId and they have a same originalTransactionId

{
    "bundleId": "${bundleId}",
    "environment": "Production",
    "inAppOwnershipType": "PURCHASED",
    "originalPurchaseDate": 1691220528000,
    "originalTransactionId": "${originalTransactionId}",
    "productId": "${productId}",
    "purchaseDate": 1691220528000,
    "quantity": 1,
    "signedDate": 1692590989925,
    "storefront": "USA",
    "storefrontId": "143441",
    "transactionId": "${originalTransactionId}",
    "transactionReason": "PURCHASE",
    "type": "Non-Consumable"
}

the defination of Non-Consumable is can only purchase once for same apple account. But why there would have originalTransactionId?

Accepted Answer

If the user has multiple devices, or restoreCompletedTransactions is called, new transaction ids will be created. However the underlying purchase, and therefore the original transaction id for a non-consumable, is still the same. In Original StoreKit transaction ids are per-device and can be restored, meaning both non-consumables and auto-renewing subscription transactions will have this multiple-transaction id per underlying purchase property.

When a user clicks on "restoreCompletedTransactions," a new transaction ID is generated. Does the server receive any notification regarding this?

Assuming that when the user initially made a purchase, the client passed the appAccountToken field, the server can associate the purchase with the app store transaction using the appAccountToken after a successful purchase. However, when the user clicks to restore purchases on another device, does the app store send a server notification like it does for ONE_TIME_CHARGE after the purchased item is restored? If no notification is sent, is the server only able to validate whether the "purchase has been completed" by relying on the new transaction ID received from the client?

A local restoring of purchases does not trigger new or resend old server notifications. There a few things to cover here:

  • A one time purchase like a consumable will always have the same OTID (original transaction ID), so that is your persistent identifier to represent a unique purchase. Additionally with StoreKit2 a new transaction ID is not created, same one is returned.
  • You can retrieve a history of received server notifications a few different ways, recommend learning more about the App Store Server API notification history endpoint.
  • You can retrieve a customers purchase history server side, you can retrieve a complete notification history for a customer using our App Store Server API Transaction history endpoint.
  • Recommend looking into implementing the 'Proactive restore' best practice in your app so customers have access to their purchase without the extra step of doing a manual 'restore purchases' and re-authenticating. Upon app launch your app has what it needs. Leverage the app receipt on device if using original storeKit or leverage the power of StoreKit2 where you can retrieve complete transaction history when needed.

This trips up a lot of people coming from the verifyReceipt world. Short version: transactionId is not stable for a non-consumable. It changes every time the purchase is "issued" to a device, which includes restore, re-download on a new device, Family Sharing additions, etc. originalTransactionId is the stable identifier.

For one non-consumable purchase, reinstalled across multiple devices, you'll see:

  • originalTransactionId: same value across all entries (the first purchase).
  • transactionId: a new value for each issuance event.

That's why querying Get Transaction Info with different transactionId values over time can return what looks like the same product ownership. Each one represents a separate re-issuance of the same underlying entitlement.

Rules of thumb for backend storage:

  • Use originalTransactionId as the stable "user owns this product" key in your database.
  • Use transactionId only when you need a unique identifier for a specific issuance event, e.g. deduplication on your webhook.
  • For Family Sharing, check inAppOwnershipType on each transaction: PURCHASED means the user bought it, FAMILY_SHARED means they received it via Family Sharing. They can share the same originalTransactionId but have different transactionIds.

WWDC23 "Meet StoreKit 2" walks through this relationship visually if you want more context.

Why Non-Consumable product has originalTransactionId?
 
 
Q