react-native-purchases icon indicating copy to clipboard operation
react-native-purchases copied to clipboard

Purchases.getOfferings() returning different object on Android vs iOS

Open wmonecke opened this issue 2 years ago • 4 comments

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?

  1. Environment
    1. Platform: iOS/ Android
    2. SDK version: "react-native-purchases": "^6.3.0",
    3. OS version: latest
    4. React Native version: 0.72.2
    5. 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"
        },

wmonecke avatar Sep 02 '23 16:09 wmonecke

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

RCGitBot avatar Sep 02 '23 16:09 RCGitBot

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.

vegaro avatar Sep 04 '23 12:09 vegaro

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 avatar Sep 04 '23 12:09 wmonecke

@wmonecke Hey! 👋 I just noticed that your example above is grabbing two different types of periods 🤔

  • selectedPackage.product.defaultOption.billingPeriod.value is the billing period for the sub (not the intro)
  • selectedPackage.product.introPrice.periodNumberOfUnits is 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.billingPeriod values are put into selectedPackage.product. subscriptionPeriod
  • The selectedPackage.product.defaultOption.introPhase values are put into selectedPackage.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

joshdholtz avatar Sep 15 '23 20:09 joshdholtz