React-native-iap: How to manage subscribed users?

Created on 23 Nov 2019  ·  8Comments  ·  Source: dooboolab/react-native-iap

Hi!
I am having a bit of trouble planning how the flow of the information should be.
How can I know if my user has paid for a subscription and would this method work offline?
Should I always check the result of said method to update the apps state to a premium version or how should I go about it? Sorry if the question is a bit broad. Any help is welcome.

❓ question 🙏 help wanted

Most helpful comment

@wmonecke
*tip
you should check cancellation_date field in iOS latestReceiptInfo receipt

// The time Apple customer support canceled a transaction
// in a date-time format similar to the ISO 8601. This field is only present for refunded transactions.
const nonCancelledReceiptsInfo: = latestReceiptInfo.filter(
   receipt => !receipt.cancellation_date
);

All 8 comments

There is related post in react-native-iap repo. The idea is, you should check the receipt. Since checking receipt requires the internet, I don't think it is possible offline. Please update me if I am outdated.

@hyochan I read the whole thread and see they are having my same issues.
I thought Apple/Google would handle the subscription status for us (online or offline) but I will just save the receipt in my own server and then recheck its validity every time the app starts up.

@hyochan

I have another issue that might be stupid but is driving me nuts. I read the related post and I am implementing the posted method to get to know if the user is a paying user:

````
import * as RNIap from 'react-native-iap';
import {ITUNES_CONNECT_SHARED_SECRET} from 'react-native-dotenv';

const SUBSCRIPTIONS = {
// This is an example, we actually have this forked by iOS / Android environments
ALL: ['monthlySubscriptionId', 'yearlySubscriptionId'],
}

async function isSubscriptionActive() {
if (Platform.OS === 'ios') {
const availablePurchases = await RNIap.getAvailablePurchases();
const sortedAvailablePurchases = availablePurchases.sort(
(a, b) => b.transactionDate - a.transactionDate
);
const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt;

const isTestEnvironment = __DEV__;
const decodedReceipt = await RNIap.validateReceiptIos(
  {
    'receipt-data': latestAvailableReceipt,
    password: ITUNES_CONNECT_SHARED_SECRET,
  },
  isTestEnvironment
);
const {latest_receipt_info: latestReceiptInfo} = decodedReceipt;
const isSubValid = !!latestReceiptInfo.find(receipt => {
  const expirationInMilliseconds = Number(receipt.expires_date_ms);
  const nowInMilliseconds = Date.now();
  return expirationInMilliseconds > nowInMilliseconds;
});
return isSubValid;

}

if (Platform.OS === 'android') {
// When an active subscription expires, it does not show up in
// available purchases anymore, therefore we can use the length
// of the availablePurchases array to determine whether or not
// they have an active subscription.
const availablePurchases = await RNIap.getAvailablePurchases();

for (let i = 0; i < availablePurchases.length; i++) {
  if (SUBSCRIPTIONS.ALL.includes(availablePurchases[i].productId)) {
    return true;
  }
}
return false;

}
}
````

However, the following method is not working for me. I am working on iOS right now and when I call
const availablePurchases = await RNIap.getAvailablePurchases(); I get prompted to Sign In into iTunes (even though I am already signed in with my Apple Id). When putting in the credentials nothing else happens and just get this error after a while:

Cannot connect to iTunes Store at createErrorFromErrorData (NativeModules.js:152) at NativeModules.js:104 at MessageQueue.__invokeCallback (MessageQueue.js:442) at MessageQueue.js:127 at MessageQueue.__guard (MessageQueue.js:343) at MessageQueue.invokeCallbackAndReturnFlushedQueue (MessageQueue.js:126) at debuggerWorker.js:80

I don't know what to do. I made a Sandbox user and tried signing in with those credentials as well and it didn't work. Do you have any idea or hint why this is not working?

Thank you for your time and patience!!

The prompted to Sign In into iTunes only appears in the debug scheme. I am not sure what's going on internally in your case. Trying out for a different user, or doing the whole step once again would help I think.

Also, try out for live purchase either. Sometimes it is hard to track the mistake because there are several things to go over carefully such as is wifi on? Have you signed out for a newly updated tax agreement? and so on.

@hyochan

Screenshot 2019-11-25 at 15 42 42

I did agree to the new tax agreement, however I just added the bank account yesterday and it says the status is active (but pending)? Is this also crucial to be able to test iap (i.e. the dot has to be green)?

I reset the iPhone Simulator with no user signed-in in the Settings app. I loaded the component that allows in app purchases and got my subscriptions with const subscriptions = await RNIap.getSubscriptions(itemSubs)

You should be able to test on a simulator right?

When I press to actually make the purchase of a sub it instantly throws the following error (When I was expecting the prompt to Sign-in into iTunes):

Error requesting Subscription Error: Cannot connect to iTunes Store at createErrorFromErrorData (NativeModules.js:152) at NativeModules.js:104 at MessageQueue.__invokeCallback (MessageQueue.js:442) at MessageQueue.js:127 at MessageQueue.__guard (MessageQueue.js:343) at MessageQueue.invokeCallbackAndReturnFlushedQueue (MessageQueue.js:126)

This is also a new sandbox user since I read that trying to log in with an account into your iPhone will reject said account.

Have you logged into the Sandbox on iTunes account? https://developer.apple.com/apple-pay/sandbox-testing/

Have you logged into the Sandbox on iTunes account? https://developer.apple.com/apple-pay/sandbox-testing/

I had to test it on an iPhone and not the simulator. Apparently, it has worked before but on iOS 12.4 you cant test IAP in the Simulator.

@wmonecke
*tip
you should check cancellation_date field in iOS latestReceiptInfo receipt

// The time Apple customer support canceled a transaction
// in a date-time format similar to the ISO 8601. This field is only present for refunded transactions.
const nonCancelledReceiptsInfo: = latestReceiptInfo.filter(
   receipt => !receipt.cancellation_date
);
Was this page helpful?
0 / 5 - 0 ratings