Purchases.getOfferings() returning different object on Android vs iOS
- [x] I have updated Purchases SDK to the latest version
- [x] I have read the Contribution Guidelines
- [x] I have searched the Community
- [x] I have read docs.revenuecat.com
- [x] I have searched for existing Github issues
Describe the bug I was able to get my offerings as follows:
Purchases.getOfferings()
.then((offerings) => {
console.log('offerings:', offerings);
if (offerings?.current.availablePackages.length !== 0) {
return this.setState({
offerings: offerings?.current?.availablePackages,
});
}
return this.setState({ offerings: false });
})
.catch((error) =>
console.log('error getting offerings:', error),
);
However, the elements inside the availablePackages array are different in Android vs. on iOS.
Is this intended? I didn't find anything related to this in the docs. What could I be doing wrong?
- Environment
- Platform: iOS/ Android
- SDK version: "react-native-purchases": "^6.3.0",
- OS version: latest
- React Native version: 0.72.2
- How widespread is the issue. Percentage of devices affected - Local only
availablePackages item on ios:
{
"packageType": "MONTHLY",
"identifier": "$rc_monthly",
"product": {
"priceString": "$3.49",
"productType": "NON_CONSUMABLE",
"discounts": [],
"description": "Enjoy the all-access \"Plus\" version",
"currencyCode": "USD",
"title": "....",
"price": 3.4899999999999998,
"subscriptionPeriod": "P1M",
"productCategory": "SUBSCRIPTION",
"identifier": "....plus.1month",
"introPrice": {
"cycles": 1,
"periodUnit": "DAY",
"priceString": "$0.00",
"period": "P3D",
"periodNumberOfUnits": 3,
"price": 0
}
},
"offeringIdentifier": "default"
},
vs android
{
"offeringIdentifier": "default",
"product": {
"presentedOfferingIdentifier": "default",
"subscriptionOptions": [
{
"introPhase": null,
"freePhase": null,
"isPrepaid": false,
"presentedOfferingIdentifier": "default",
"fullPricePhase": {
"offerPaymentMode": null,
"billingCycleCount": 0,
"price": {
"currencyCode": "EUR",
"amountMicros": 3990000,
"formatted": "€3.99"
},
"recurrenceMode": 1,
"billingPeriod": {
"iso8601": "P1M",
"value": 1,
"unit": "MONTH"
}
},
"isBasePlan": true,
"billingPeriod": {
"iso8601": "P1M",
"value": 1,
"unit": "MONTH"
},
"productId": "....plus.1month",
"tags": [],
"pricingPhases": [
{
"offerPaymentMode": null,
"billingCycleCount": 0,
"price": {
"currencyCode": "EUR",
"amountMicros": 3990000,
"formatted": "€3.99"
},
"recurrenceMode": 1,
"billingPeriod": {
"iso8601": "P1M",
"value": 1,
"unit": "MONTH"
}
}
],
"storeProductId": "....plus.1month:p1m",
"id": "p1m"
}
],
"introPrice": null,
"currencyCode": "EUR",
"priceString": "€3.99",
"subscriptionPeriod": "P1M",
"productCategory": "SUBSCRIPTION",
"price": 3.99,
"title": "",
"productType": "AUTO_RENEWABLE_SUBSCRIPTION",
"discounts": null,
"defaultOption": {
"introPhase": null,
"freePhase": null,
"isPrepaid": false,
"presentedOfferingIdentifier": "default",
"fullPricePhase": {
"offerPaymentMode": null,
"billingCycleCount": 0,
"price": {
"currencyCode": "EUR",
"amountMicros": 3990000,
"formatted": "€3.99"
},
"recurrenceMode": 1,
"billingPeriod": {
"iso8601": "P1M",
"value": 1,
"unit": "MONTH"
}
},
"isBasePlan": true,
"billingPeriod": {
"iso8601": "P1M",
"value": 1,
"unit": "MONTH"
},
"productId": "....plus.1month",
"tags": [],
"pricingPhases": [
{
"offerPaymentMode": null,
"billingCycleCount": 0,
"price": {
"currencyCode": "EUR",
"amountMicros": 3990000,
"formatted": "€3.99"
},
"recurrenceMode": 1,
"billingPeriod": {
"iso8601": "P1M",
"value": 1,
"unit": "MONTH"
}
}
],
"storeProductId": "....plus.1month:p1m",
"id": "p1m"
},
"description": "...!",
"identifier": "....plus.1month:p1m"
},
"packageType": "MONTHLY",
"identifier": "$rc_monthly"
},
👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!
Thanks for pointing that out. defaultOption and subscriptionOptions are Google specific and only returned in Android devices. We have actually just noticed that they should be optional in the Typescript definitions but they are not, which is an issue. We will evaluate if we make them optional or return them as null in iOS. In the meantime, can you add checks so the properties are only accessed in iOS?
Sorry if that is confusing, we'll try to make it less confusing in a future release.
Yeah, at the moment I am doing something like this:
const periodLengthInMonths =
Platform.OS === 'android'
? selectedPackage.product.defaultOption.billingPeriod.value
: selectedPackage.product.introPrice.periodNumberOfUnits;
@wmonecke Hey! 👋 I just noticed that your example above is grabbing two different types of periods 🤔
selectedPackage.product.defaultOption.billingPeriod.valueis the billing period for the sub (not the intro)selectedPackage.product.introPrice.periodNumberOfUnitsis the billing period for the intro price (not the sub)
Design of defaultOption
The placing of defaultOption and subscriptionOptions in this response was designed for developers if they needed to do something super specific on Google Play (ex: if you had multiple different offers and you wanted to choose something specific for the user).
Values of defaultOptions are backsupported into the existing offering response that iOS uses. Example:
- The
selectedPackage.product.defaultOption.billingPeriodvalues are put intoselectedPackage.product. subscriptionPeriod - The
selectedPackage.product.defaultOption.introPhasevalues are put intoselectedPackage.product.introPrice
Getting your periodLengthInMonths
With the following examples, you shouldn't need to do any special checking of platforms 🤞
Subscription period
So, I think to calculate your periodLengthInMonths variable from your above example, you would only need to look at selectedPackage.product. subscriptionPeriod for both platforms. This returns a value P1M or P3M but we should really provide a nicer period object in there to make this easier.
Intro Period
If you are looking for the intro period, you should look into selectedPackage.product.introPrice.periodNumberOfUnits