React-native-iap: getAvailablePurchases returns different array every time

Created on 9 Aug 2019  ·  13Comments  ·  Source: dooboolab/react-native-iap

Version of react-native-iap

3.3.9 (but also experiences this in 3.0.0 and before)

Version of react-native

0.59.9

Platforms you faced the error (IOS or Android or both?)

iOS (not tested on Android)

Expected behavior

Same array every time I call getAvailablePurchases
Same array length every time

Actual behavior

Different array every time i call getAvailablePurchases
Different array length every time i call getAvailablePurchases

Tested environment (Emulator? Real Device?)

Real Device

Steps to reproduce the behavior

I think this is only visible with a lot of transactions, but can't confirm this. My Sandbox account has 50+ transactions.

handleOnPressRestore = async () => {
  return this.setState({ isLoadingRestorePurchases: true }, async () => {
    try {

      // Get the previous purchases of the current user
      const purchases = await RNIap.getAvailablePurchases();

      // Get the latest receipt from the purchases to validate
      const { transactionReceipt, productId, transactionDate } = this.getLatestPurchase(purchases);

      console.log(purchases.length, productId, transactionDate);

      // Validate the receipt on our server
      await this.props.validateSubscriptionReceipt(productId, transactionReceipt);

      // The validation result is handled in componentDidUpdate
    } catch (err) {
      throw err;
    } finally {
      this.setState({ isLoadingRestorePurchases: false });
    }
  });
}

getLatestPurchase = (purchases: RNIap.ProductPurchase[]): RNIap.ProductPurchase => {
  // First, sort the array, so the latest purchase is on top
  // https://github.com/dooboolab/react-native-iap/issues/532#issuecomment-503174711
  const sortedPurchases = purchases.sort((a, b) => b.transactionDate - a.transactionDate);

  const purchase = sortedPurchases[0];

  return purchase;
}

With the above code, when I press "Restore purchase", i get different logs every time:

Pressing "Restore purchase" 6 times, with a wait in between to let the results come in:

// purchases.length, productId, transactionDate
9 "com.app.sub" 1565182529000
21 "com.app.sub" 1565181329000
22 "com.app.sub" 1565183001000
42 "com.app.sub" 1565183001000
53 "com.app.sub" 1565182529000
55 "com.app.sub" 1565183001000
15 "com.app.sub" 1565182529000

I would expect the array to always be the same, right? What's happening here?

The result of getAvailablePurchases() is different every time i run it

📱 iOS 🙏 help wanted 🚶🏻 stale

Most helpful comment

I also ran a loop on all the transactions, and was not able to remove them with finishTransactionIOS. My transactions are auto-renewing subscriptions.

All 13 comments

I am also seeing this. The really dangerous thing about it is there is a chance that the most recent valid receipt might not be in the results.

From looking at this modules code it honestly looks like an issue on Apple's side since this module is effectively just passing through whatever restoreCompletedTransactions() gives back

I wonder if getPurchaseHistory() is a good alternative to be used for subscriptions. I'm currently using that as the alternative, as that gives me reliable results. I don't have any other purchases in my app, only subscriptions.

As stated in the iap docs on this package, getAvailablePurchases() is talking about consumables. A subscription is not a "consumable" or "non-consumable" product. It's a "auto-renewable subscription" So getPurchaseHistory() should do?

Screenshot 2019-08-16 at 18 11 13

I found getPurchaseHistory() gave varying array lengths as well. Seemed like frequency of requests effected it. In the end it turned out this didn't really matter for us, once any of the transaction receipts was sent up for validation all receipts were returned by Apple in the latest_receipt_info array anyway which is all that mattered for us.

I'm having the same issue (don't know if it is sandbox mode error). Also, when I switch apple id within a real device the purchase made with another account gets returned.

Ex:
1 - I made a purchase with account [email protected]
2 - Switched apple id to [email protected]
3 - getAvailablePurchases returns the transactionId of the purchase made with [email protected] account

Another issue that I found is that if I restore the device (factory reset) both methods getPurchaseHistory() and getAvailablePurchases gives random results

Apple's response for restoring a purchase is:

"Users restore transactions to maintain access to content they've already purchased. For example, when they upgrade to a new phone, they don't lose all of the items they purchased on the old phone. Include some mechanism in your app to let the user restore their purchases, such as a Restore Purchases button."

Found a Apple Developer Forums topic about the varying results of restoreCompletedTransactions as said by @ssg-luke earlier.

https://forums.developer.apple.com/thread/115242

Maybe, this is related:

It seems like you are referring to one user having many transactions for which the app has not yet called finishTransaction.
https://forums.developer.apple.com/thread/115242#thread-post-355444

I've had other sandbox accounts on the same device. So could it be the transactions of different accounts, on the same device, are just messed up and not "finished" at all?

However, I did run a loop on all the transactions, and called finishTransactionIOS on all of them. But that did not make them go away or result in a reliable output of getAvailablePurchases()... Maybe because i'm already on an newer sandbox account and the transactions are from a older sandbox account?

Could also explain what @fcandiani experiences.

I found getPurchaseHistory() gave varying array lengths as well. Seemed like frequency of requests effected it. In the end it turned out this didn't really matter for us, once any of the transaction receipts was sent up for validation all receipts were returned by Apple in the latest_receipt_info array anyway which is all that mattered for us.

Thanks for the info @ssg-luke , i'm gonna do the same.

I also ran a loop on all the transactions, and was not able to remove them with finishTransactionIOS. My transactions are auto-renewing subscriptions.

Facing almost the same. I was creating a issue, but, decided to put here all the information to help others to find the problem and solution.

Version of react-native-iap

3.5.9

Version of react-native

0.60.5

Platforms you faced the error (IOS or Android or both?)

iOS

  • The same code is working perfectly on Android, already in production

Expected behavior

User subscribed once, getAvailablePurchases should return just one purchase

Actual behavior

getAvailablePurchases is returning more than one purchase,

Tested environment (Emulator? Real Device?)

Real device with fresh sandbox account.

Steps to reproduce the behavior

  • Create a sandbox test user on https://appstoreconnect.apple.com/access/testers
  • Build and run project on xcode
  • Call getAvailablePurchases, returns no purchaces (like expected)
  • Subscribe to some product
  • my purchaseUpdatedListener is called, and I call RNIap.finishTransactionIOS(subscription.transactionId); that returns just undefined (like expected I guess)
  • Call getAvailablePurchases, returns one purchase (like expected)
  • Reload de app
  • Call getAvailablePurchases, returns two purchases, each one with different transactionId:
{
   originalTransactionDateIOS: 1571144840000
   originalTransactionIdentifierIOS: "1000000579413333"
   productId: "io.appmasters.lowcarb.development"
   transactionDate: 1571145019000
   transactionId: "1000000579415509"
   transactionReceipt: "MIIVnQYJKoZIhvcN..."
},
{
   originalTransactionDateIOS: 1571144840000
   originalTransactionIdentifierIOS: "1000000579413333"
   productId: "io.appmasters.lowcarb.development"
   transactionDate: 1571144839000
   transactionId: "1000000579416705"
   transactionReceipt: "MIIVnQYJKoZIhvcN...."
}
  • Reload de app again
  • Call getAvailablePurchases, returns three purchases, each one with different transactionId (not the same from previous result):
{
   originalTransactionDateIOS: 1571144840000
   originalTransactionIdentifierIOS: "1000000579413333"
   productId: "io.appmasters.lowcarb.development"
   transactionDate: 1571145739000
   transactionId: "1000000579423546"
   transactionReceipt: "MIIb1AYJKoZIhvcN..."
},{
   originalTransactionDateIOS: 1571144840000
   originalTransactionIdentifierIOS: "1000000579413333"
   productId: "io.appmasters.lowcarb.development"
   transactionDate: 1571145019000
   transactionId: "1000000579426352"
   transactionReceipt: "MIIb1AYJKoZI..."
},{
   originalTransactionDateIOS: 1571144840000
   originalTransactionIdentifierIOS: "1000000579413333"
   productId: "io.appmasters.lowcarb.development"
   transactionDate: 1571144839000
   transactionId: "1000000579426353"
   transactionReceipt: "MIIb1AYJKoZIhvcN..."
}
  • Reload de app again
  • Call getAvailablePurchases, returns fours purchases, each one with different transactionId (not the same from previous result)...

This doesn't seem to be the right behavior, from what I read on docs and from what happens on Android.

I appreciate any help.

getAvaialblePurchases() is returning 150+ results on my sandbox test account. I have decided not to use getAvailablePurchases() at all and just store last purchase on the device and also on the server. If app is deleted or user moved to new phone, we will still have last purchase on our server tied to our company user id & platform (iOS or Android). Calling getAvailablePurchases() taking close to a minute and sometimes calling this triggering purchase updated listener trying to restore multiple purchases. We have modified our flow like this:
1) When user logs into the device:
a) see if there is a valid last purchase on our server for the user and platform.
b) If there is no record on server, see if there a local saved purchase in DB
c) If there is none or there is expired one, show subscriptions view.
Initially we were calling getAvailablePurchases() and trying to see if last purchase was still valid. Not sure if this helps others but we are not running into issues with this anymore. We are on react 59 and that is another pain we have to deal with.

@ramakula and what if the user cancels his subscription?

The way you told, you will keep the subscription on your database until the end date, and will "see" that's not available purchase anymore.

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.

getAvaialblePurchases() is returning 150+ results on my sandbox test account. I have decided not to use getAvailablePurchases() at all and just store last purchase on the device and also on the server. If app is deleted or user moved to new phone, we will still have last purchase on our server tied to our company user id & platform (iOS or Android). Calling getAvailablePurchases() taking close to a minute and sometimes calling this triggering purchase updated listener trying to restore multiple purchases. We have modified our flow like this:

  1. When user logs into the device:
    a) see if there is a valid last purchase on our server for the user and platform.
    b) If there is no record on server, see if there a local saved purchase in DB
    c) If there is none or there is expired one, show subscriptions view.
    Initially we were calling getAvailablePurchases() and trying to see if last purchase was still valid. Not sure if this helps others but we are not running into issues with this anymore. We are on react 59 and that is another pain we have to deal with.

I believe we can get the latest purchases from the receipt. By verifying the receipt of a user, with exclude-old-transactions: true we can get the latest subscription info (only for auto-renewable type). I believe we can use this method to restore auto-renewable subscription purchases. I'm not sure if this is an ideal method though.

Was this page helpful?
0 / 5 - 0 ratings