Skip to content
This repository has been archived by the owner on Mar 22, 2024. It is now read-only.

Observer mode: add usesStoreKit2IfAvailable parameter #299

Closed
wants to merge 2 commits into from

Conversation

NachoSoto
Copy link
Contributor

This is required since RevenueCat/purchases-ios#3032.

@NachoSoto NachoSoto requested a review from aboedo August 22, 2023 18:09
@@ -10,6 +10,8 @@ final class Container {
.with(observerMode: true)
// Optionally set an app user ID for the user
.with(appUserID: UserManager.appUserID)
// If your app uses StoreKit 2, you must enable it in the SDK
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vegaro also asked through Slack. Not sure if it's normal that this file is "DocsTester`. I can move it if not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already answered in Slack, but also explaining here for reference. Snippets can live in an independent file or inside the DocsTester project. The difference is that inside the DocsTester project, they will be tested

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The project is failing to build, is that normal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh cause this is deprecated :/
It does seem weird to point users to using a deprecated API, but it might be needed. Maybe we should un-deprecate it now? /cc @aboedo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to change this to use RevenueCat/purchases-ios#3066 (thanks @aboedo for the idea).

@RCGitBot
Copy link
Contributor

Previews

temp/entitlements.md

See contents

The RevenueCat dashboard allows you to specify what level of access each product should unlock for your users, which can greatly simplify your in-app code to check for subscription access. The dashboard also allows you to configure which in-app products are shown to your users remotely, so you can control how they're presented without the need to update your app. This is great for experimenting how different product configurations affect key subscription metrics.

[block:embed]
{
"html": "<iframe class="embedly-embed" src="//cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FQxHeZiW4KCA%3Ffeature%3Doembed&display_name=YouTube&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DQxHeZiW4KCA&image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FQxHeZiW4KCA%2Fhqdefault.jpg&key=7788cb384c9f4d5dbbdbeffd9fe4b92f&type=text%2Fhtml&schema=youtube" width="854" height="480" scrolling="no" title="YouTube embed" frameborder="0" allow="autoplay; fullscreen; encrypted-media; picture-in-picture;" allowfullscreen="true"></iframe>",
"url": "https://www.youtube.com/watch?v=QxHeZiW4KCA",
"title": "RevenueCat Products, Offerings, and Entitlements Explained",
"favicon": "https://www.google.com/favicon.ico",
"image": "https://i.ytimg.com/vi/QxHeZiW4KCA/hqdefault.jpg",
"provider": "youtube.com",
"href": "https://www.youtube.com/watch?v=QxHeZiW4KCA",
"typeOfEmbed": "youtube"
}
[/block]

There are three levels of product configuration available within RevenueCat:

  1. Products
  2. Entitlements
  3. Offerings

[block:image]
{
"images": [
{
"image": [
"https://files.readme.io/fb06e40-Screen_Shot_2020-07-01_at_6.12.53_PM.png",
"Screen Shot 2020-07-01 at 6.12.53 PM.png",
1470
],
"align": "center",
"caption": "An example of how products in Apple, Google, and Stripe translate to Products, Entitlements, and Offerings in RevenueCat."
}
]
}
[/block]

The diagram above illustrates how products from Apple, Google, and Stripe would tie into RevenueCat. The configuration is explained in more detail below.

Products

Products are the individual SKUs that users actually purchase. The stores (Apple, Google, Stripe, and Amazon) process these SKUs and charge the user.

Store Configuration

No matter how you choose to use RevenueCat, you'll need to first have products set up in the stores. This is done outside of RevenueCat, and where you set things like price, duration, and free trials. If you've never set up products before or need a refresher (or tips and tricks) check out these guides:

  • iOS / App Store Connect
  • Android / Google Play Console
  • Android / Amazon Appstore
  • Stripe

RevenueCat Configuration

After your products are set up in the stores, you'll also need to set up a 1-to-1 mapping of the products in RevenueCat as well.

Navigate to the Products tab in the settings for your project in the RevenueCat dashboard. To add a new product, click the + New button and enter the product identifier exactly as it appears in the store, as well as the store that the product belongs to.

These product identifiers are the link between RevenueCat, and Apple, Google, Stripe, or Amazon.

After clicking "New", you also have the opportunity to directly import products for the following stores: Apple App Store, subscriptions from Google Play Store. Support for importing from other stores will be added in the future.

If you choose to set up subscription products manually for Google Play apps, you will need to add both the subscription ID and the base plan ID, which you can find in Google Play Console as per the following screenshot:

📘

If a user purchases a product that has not been set up in RevenueCat, the purchase will still go through and be tracked in RevenueCat.

To make it easier to identify your products in RevenueCat, you can optionally set a display name for them by:

  1. Navigating to a product's configuration page
  2. Clicking Edit
  3. Entering your desired display name
  4. Clicking Save

📘

Product display names must be unique within an app. It's a good practice to include the product duration in the display name to avoid name overlaps, for example: 'My Pro Subscription Monthly', 'My Pro Subscription Yearly' etc".

🚧

Product display names are currently supported in Charts, and will be supported throughout the Dashboard in the near future.

Entitlements

An entitlement represents a level of access, features, or content that a user is "entitled" to.

Entitlements are used to ensure a user has appropriate access to content based on their purchases, without having to manage all of the product identifiers in your app code.

Most apps only have one entitlement, unlocking all premium features. However, if you had two tiers of content such as Gold and Platinum, you would have 2 entitlements.

A user's entitlements are shared across all apps contained within the same project.

Creating an Entitlement

To create a new entitlement, click Entitlements in the left menu of the Project dashboard and click + New. You'll need to enter a unique identifier for your entitlement that you can reference in your app, like "pro".

Most apps only have one entitlement, but create as many as you need. For example a navigation app may have a subscription to "pro" access, and one-time purchases to unlock specific map regions. In this case there would probably be one "pro" entitlement, and additional entitlements for each map region that could be purchased.

Attaching Products to Entitlements

Once entitlements are created, you should attach products to entitlements. This lets RevenueCat know which entitlements to unlock for users after they purchase a specific product.

When viewing an Entitlement, click the Attach button to attach a product. If you've already added your products, you'll be able to select one from the list to attach.

When a product that is attached to an entitlement is purchased, that entitlement becomes active for the duration of the product. Subscription products will unlock entitlements for the subscription duration, and non-consumable and consumable purchases that are attached to an entitlement will unlock that content forever.

If you have non-subscription products, you may or may not want to add them to entitlements depending on your use case. If the product is non-consumable (e.g. lifetime access to "pro" features), you likely want to attach it to an entitlement. However, if it is consumable (e.g. purchase more lives in a game) you likely do not want to add them to an entitlement.

Attaching an entitlement to a product will grant that entitlement to any customers that have previously purchased that product. Likewise, detaching an entitlement from a product will remove it for any customers that have previously purchased that product.

When designing your Entitlement structure, keep in mind that a single product can unlock multiple entitlements, and multiple products may unlock the same entitlement.

[block:image]
{
"images": [
{
"image": [
"https://files.readme.io/5bb6bc2-Screen_Shot_2020-07-01_at_6.14.38_PM.png",
"Screen Shot 2020-07-01 at 6.14.38 PM.png",
1346
],
"align": "center",
"caption": "Example Entitlement structure with associated Apple, Google, Stripe, or Amazon product identifiers."
}
]
}
[/block]

📘

When relying on entitlements to enable access to certain content, it's important that you remember to add new products to their associated entitlements if needed. Failing to add your products to an entitlement, could lead to your users making purchases that don't unlock access to the promised content.

Offerings

Offerings are the selection of products that are "offered" to a user on your paywall. They're an optional, but recommended feature of RevenueCat that can make it easier to change and experiment with your paywalls.

Offerings allow you to choose which combination of products are shown to a user on your paywall or upsell screen. For example your default Offering may contain a monthly and annual subscription, but you might want to experiment with Offerings with a different combination of subscription durations, trial lengths, prices, etc.

Within each Offering, there must be one or more Packages. Packages are simply a group of equivalent products across iOS, Android, and web. If your app is available on multiple platforms, then a Package would contain all of the equivalent product Ids from each platform.

Creating an Offering

To create an Offering, navigate to the Offerings tab to your project settings in the RevenueCat dashboard, and click + New to get started.

You'll be prompted to enter an Identifier and Description for your offering. Note that the offering identifier cannot be changed later. Once you've entered this information, click Add.

Current Offering
The RevenueCat dashboard allows you to choose which Offering should be current. The current Offering is a convenience method for the Purchases SDK to reference a primary Offering.

If you build your paywall to reference the current Offering, instead of hardcoding an identifier value, you can change this Offering from the dashboard to dynamically display different Offerings at any time. We strongly recommend utilizing the current Offering feature. See Displaying Products for more info.

Adding Packages

Each Offering you create should contain at least one Package that holds cross-platform products.

To create a package, click into your new Offering, then click + New in the Packages section. In the popup, choose an Identifier from the dropdown that corresponds with the duration of the package. If a duration isn't suitable for your package (e.g. consumable purchases), then you can choose a custom identifier. Include a Description, then click Add.

Click into the newly created package and begin attaching product by clicking Attach.

On the next screen, you'll see dropdowns to select the appropriate product for each store. Choose the appropriate products, then click Attach.

For Google Play store products, if you select a non backwards-compatible product and the app compatibility setting is set to "SDK v6+ and backwards compatible", you will have the ability to configure a backwards compatible fallback product. This product will be available for purchase in previous versions of the SDK which don't yet support non backwards compatible products.

📘

Any product can be added to an Offering, even if it's not part of any Entitlement. This can come in handy if your app's paywall contains a combination of subscription products that unlock Entitlements, and consumable products that do not.

Continue this process until all of the packages are created for your Offering, and all Offerings are created.

The packages within an offering can be updated at any time, and their display order can be modified by dragging their position in the table. This display order will be reflected in the SDK when you fetch Offerings.

Removing Packages

Packages can be removed from an Offering at any time. This can be useful if you want to update your paywall on-the-fly without an app update.

Removing a package from an Offering does not remove the products from RevenueCat, remove transactions, or remove the products from any Entitlements. You can re-add a package and products at any time.

Next Steps

You've successfully created Entitlements, Offerings, and Packages, and have attached products to be used in your app.

Continue on to Displaying Products to start showing your new Offering in your app.

temp/observer-mode.md

See contents

Observer Mode enables you to migrate your existing subscribers to RevenueCat while retaining your existing code for fetching products, making purchases, and checking subscription status. This allows you to access to the advanced charting, webhooks, and integrations that RevenueCat provides as quickly as possible and with minimal engineering effort.

Observer Mode may be set up as part of your migration strategy to RevenueCat, or as a permanent solution. Use the table below to compare what features become available with Observer Mode vs. a full RevenueCat SDK integration.

Feature Observer Mode Full Integration
Charts ✅ *
Webhooks & Integrations
Data Exports
Customer Lists ✅ *
Experiments
Fetching Products
Making Purchases
Checking Subscription Status ❌ **

*information based around the "last seen time" and "country" may not be complete in Observer Mode

**subscription status will be correct for your customers in Observer Mode, but we strongly recommend a full integration if planning to use RevenueCat as your subscription source-of-truth

Implementation

Observer Mode can be implemented server-side via REST APIs or client-side through the RevenueCat SDK. You should only implement Observer Mode one way or the other.

Option 1: Server-side

Pros Cons
✅ No client-side code changes ❌ The "last seen" attribute will be missing from customers since they are never "seen" by the client
✅ Captures purchases from any app version ❌ The "country" attribute will be missing from customers and transactions since it is computed from the customer's device IP address

If you're already sending receipts to your server, you can enable Observer Mode by forwarding the receipts along to RevenueCat then processing them as you normally do.

With this option, you should already have a process in place for validating receipts on your server. When you receive a receipt, you can forward it to RevenueCat via the POST /receipts endpoint then have your server continue doing whatever it is already.

Don't worry about validating the receipt before forwarding to RevenueCat - we always validate receipts with the stores before saving them.

Requirements

The requirements for server-side Observer Mode are the same as those for the POST /receipts endpoint. This includes:

  • app_user_id
  • fetch_token
  • price
  • currency
  • product_id

While price, currency, and product_id are not required parameters for the API, they're highly recommended for Observer Mode to get value out of the integration - without providing this info RevenueCat will not have any price information.

If you're not already sending price and currency to your server there are two options:

  1. Update your client-side code to start sending price and currency to your server. However, at this point a client-side Observer Mode setup may be simpler.
  2. Hardcode a mapping of product IDs to their corresponding USD currency on your server. While this system isn't perfect as it won't account for country specific pricing and currency conversion rates, it may be a suitable approximation.

🚧 Complete Apple base64 receipt file required

Observer Mode requires the raw base64 encoded Apple receipt as the fetch_token. Partial receipts or the receipt information from the Apple server-to-server notifications is insufficient.

🚧 Past subscription history will not be imported for Google and Stripe subscriptions

Due to limitations in both Google’s and Stripe’s subscription APIs, RevenueCat will not have access to the full past history of your subscription data. This means that metrics such as revenue may not be accurate for past periods. However, data will fill in from the point of migration into the future.

Option 2: Client-side

Pros Cons
✅ No server-side code changes ❌ Only captures purchases from latest app version

If you're not saving receipts already on your server, or have limited backend resources available, a client-side Observer Mode implementation is often the best path. Implementation is no more than a couple lines

Requirements

Android
Google Play Billing Library: 2.0+ (Android only)

iOS
No special requirements

Amazon
No special requirements

1. Configure the SDK

Purchases.logLevel = .debug
Purchases.configure(
    with: Configuration.Builder(withAPIKey: Constants.apiKey)
        // If your app uses StoreKit 2, you must enable it in the SDK
        .with(observerMode: true, storeKit2: false)
        // Optionally set an app user ID for the user
        .with(appUserID: UserManager.appUserID)
        .build()
)
RCPurchases.logLevel = RCLogLevelDebug;
RCConfigurationBuilder *configuration = [RCConfiguration builderWithAPIKey:@<public_sdk_key>];
// If your app uses StoreKit 2, you must enable it in the SDK
configuration = [configuration withObserverMode:YES, storeKit2:NO];
configuration = [configuration withAppUserID:@<app_user_id>];
[RCPurchases configureWithConfiguration:[configuration build]];
// If you're targeting only Google Play Store
class MainApplicationOnlyPlayStoreObserverMode : Application() {
    override fun onCreate() {
        super.onCreate()

        Purchases.logLevel = LogLevel.DEBUG
        val builder = PurchasesConfiguration.Builder(this, Constants.googleApiKey)
            .observerMode(true)
            // Optionally set an app user ID for the user
            .appUserID(UserManager.appUserID)
        Purchases.configure(builder.build())
    }
}
class MainApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        Purchases.logLevel = LogLevel.DEBUG

        // If you're building for the Amazon Appstore, you can use flavors to determine
        // which keys to use.
        //
        // Add the following to your build.gradle to create a flavor dimension called "store":
        //
        // flavorDimensions "store"
        // productFlavors {
        //     amazon {
        //         buildConfigField "String", "STORE", "\"amazon\""
        //     }
        //
        //     google {
        //         buildConfigField "String", "STORE", "\"google\""
        //     }
        // }
        if (BuildConfig.STORE == "amazon") {
            val builder = AmazonConfiguration.Builder(this, Constants.amazonApiKey)
                .observerMode(true)
                // Optionally set an app user ID for the user
                .appUserID(UserManager.appUserID)
            Purchases.configure(builder.build())
        } else if (BuildConfig.STORE == "google") {
            val builder = PurchasesConfiguration.Builder(this, Constants.googleApiKey)
                .observerMode(true)
                // Optionally set an app user ID for the user
                .appUserID(UserManager.appUserID)
            Purchases.configure(builder.build())
        }
    }
}
// If you're targeting only Google Play Store
public class MainApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Purchases.setLogLevel(LogLevel.DEBUG);
        Purchases.configure(new PurchasesConfiguration.Builder(this, <public_google_sdk_key>).observerMode(true).build());
    }
}

// If you're building for the Amazon Appstore, 
// click the Kotlin tab to see how to set up flavors in your build.gradle:
///...

public class MainApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Purchases.setLogLevel(LogLevel.DEBUG);
      
        PurchasesConfiguration.Builder builder = null;
      
        if (BuildConfig.STORE.equals("amazon")) {
            builder = new AmazonConfiguration.Builder(this, <public_amazon_sdk_key>);
        } else if (BuildConfig.STORE.equals("google")) {
            builder = new PurchasesConfiguration.Builder(this, <public_google_sdk_key>);
        }
      
        Purchases.configure(builder.observerMode(true).build());
    }
}
await Purchases.configure(
    PurchasesConfiguration(<public_sdk_key>)
      ..observerMode = true
);
// Observer mode can be configured through the Unity Editor. 
// If you'd like to do it programmatically instead, 
// make sure to check "Use runtime setup" in the Unity Editor, and then:

Purchases.PurchasesConfiguration.Builder builder = Purchases.PurchasesConfiguration.Builder.Init(<api_key>);
Purchases.PurchasesConfiguration purchasesConfiguration =
    builder.SetUserDefaultsSuiteName("user_default")
    .SetObserverMode(true)
    .SetAppUserId(appUserId)
    .Build();
purchases.Configure(purchasesConfiguration);

Enable Observer Mode in Unity Editor (Unity Only)

For Google Play (can also be done programmatically, see Configuration section)

For Amazon Store (can also be done programmatically, see Configuration section)

🚧 Important note regarding InAppBillingService (Android Unity only)

If using RevenueCat alongside Unity IAP or other plugin that includes the Android InAppBillingService class, follow these instructions

❗️ Important (Android only)

On Android, RevenueCat will not consume or acknowledge any purchase in Observer Mode so be sure your app is handling that. Failure to do so will result in your purchases being refunded after 3 days.

2. Sync purchases with RevenueCat (Amazon Store only)

On Android with Amazon Store (including cross-platform SDKs running on Android), any time a purchase or restore occurs in your app you should call the syncObserverModeAmazonPurchase method to record it in RevenueCat. Failure to do so will result in no purchases being recorded.

public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
   m_StoreController = controller;
   storeExtensionProvider = extensions;
   var purchases = GetComponent<Purchases>();
   purchases.SetDebugLogsEnabled(true);
   foreach (Product product in controller.products.all)
   {
       if (product.hasReceipt) {
           var amazonExtensions = storeExtensionProvider.GetExtension<IAmazonExtensions>();
           var userId = amazonExtensions.amazonUserId;
           purchases.SyncObserverModeAmazonPurchase( 
               product.definition.id,
               product.transactionID,
               userId,
               product.metadata.isoCurrencyCode,
               Decimal.ToDouble(product.metadata.localizedPrice)
           );
       }
   }
}

public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
{
   var purchases = GetComponent<Purchases>();
   
   var amazonExtensions = storeExtensionProvider.GetExtension<IAmazonExtensions>();
   var userId = amazonExtensions.amazonUserId;
   purchases.SyncObserverModeAmazonPurchase(
       e.purchasedProduct.definition.id,
       e.purchasedProduct.transactionID,
       userId,
       e.purchasedProduct.metadata.isoCurrencyCode,
       Decimal.ToDouble(e.purchasedProduct.metadata.localizedPrice)
   );
   return PurchaseProcessingResult.Complete;
}
Purchases.sharedInstance.syncObserverModeAmazonPurchase(
  receipt.termSku,
  receipt.receiptId,
  userData.userId,
  isoCurrencyCode,
  storeProduct.price
)

Configuring Integrations

Certain integrations, such as most of the Attribution Integrations, require some device data to work properly. Observer Mode does not automatically collect any device data.

If you plan on using an integration that requires device data with Observer Mode, you'll need to integrate the RevenueCat SDK to collect the data or set the appropriate attributes via the REST API. Note that attributes can be passed as an object in the POST /receipts endpoint as well if you're doing a server-side Observer Mode implementation.

Next Steps

  • With Observer Mode up-and-running capturing new purchases, you may want to migrate existing purchases to RevenueCat as well

NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Aug 24, 2023
…g SK2 setting

See also RevenueCat/revenuecat-docs#299.

Since #3032 developers using observer mode need to configure the SDK with the correct StoreKit 2 setting.
This new API makes that explicit, while leaving `with(usesStoreKit2IfAvailable:)` still deprecated.
NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Aug 29, 2023
…g SK2 setting

See also RevenueCat/revenuecat-docs#299.

Since #3032 developers using observer mode need to configure the SDK with the correct StoreKit 2 setting.
This new API makes that explicit, while leaving `with(usesStoreKit2IfAvailable:)` still deprecated.
@aboedo
Copy link
Member

aboedo commented Sep 4, 2023

@NachoSoto what's the status of this one?

@NachoSoto
Copy link
Contributor Author

I need to finish RevenueCat/purchases-ios#3066.
For this I'll update the documentation to mention that observer mode requires StoreKit 1.

NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Sep 14, 2023
…g SK2 setting

See also RevenueCat/revenuecat-docs#299.

Since #3032 developers using observer mode need to configure the SDK with the correct StoreKit 2 setting.
This new API makes that explicit, while leaving `with(usesStoreKit2IfAvailable:)` still deprecated.
@NachoSoto
Copy link
Contributor Author

Closing this since there's no longer a change in the API.
See RevenueCat/purchases-ios#3066

@NachoSoto NachoSoto closed this Sep 14, 2023
@NachoSoto NachoSoto deleted the observer-mode-sk2 branch September 14, 2023 19:22
NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Sep 15, 2023
…g SK2 setting

See also RevenueCat/revenuecat-docs#299.

Since #3032 developers using observer mode need to configure the SDK with the correct StoreKit 2 setting.
This new API makes that explicit, while leaving `with(usesStoreKit2IfAvailable:)` still deprecated.
NachoSoto added a commit to RevenueCat/purchases-ios that referenced this pull request Sep 15, 2023
…StoreKit 2

See also RevenueCat/revenuecat-docs#299.

Since #3032 developers using observer mode need to configure the SDK with the correct StoreKit 2 setting.
This new API makes that explicit, while leaving `with(usesStoreKit2IfAvailable:)` still deprecated.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants