Skip to main content

RevenueCat Billing and Web SDK

⚠️Beta Feature

RevenueCat Billing and the RevenueCat Web SDK are currently in beta.

Introduction

RevenueCat Billing and the Web SDK allow you to easily offer web subscriptions. The feature is currently in beta and has a number of known limitations. Most notably:

  • Only USD is currently supported (multi-currency support will follow later)
  • Only subscriptions are currently supported, no one-time purchases
  • There is no support for sales tax or VAT calculation
  • There is no support for trials, discounts, or other offers
  • There is no support for our Paywall templates yet

Currently, RevenueCat Billing supports using Stripe as the payment gateway. This differs from our Stripe Billing / Stripe Checkout integration in that RevenueCat Billing takes care of the purchase UI, the subscription management portal, and the recurring billing logic. Stripe will still charge a payment processing fee if you use RevenueCat Billing, but you will not have to pay Stripe Billing / Checkout fees.

Installation

Start by installing the @revenuecat/purchases-js package using the package manager of your choice:

npm install --save @revenuecat/purchases-js

General setup

Connect with Stripe

To get started, you first need to connect your Stripe account with RevenueCat, if you haven't already done so follow the instructions here.

App configuration

To start using RevenueCat Billing, you will need to add a RevenueCat Billing App to your project. To do that, go the project settings, and in the "Apps" section, click "New", then "RevenueCat Billing":

New RevenueCat Billing App

In the following step, you can set up the most important information for the new App:

  • App Name: The name of the app. This will also be shown in the purchase flow, in emails, and on invoices.
  • Seller Company Name: The name of your company. This will also be shown in the purchase flow, in emails, and on invoices.
  • Support Email: An email address that customers can send support requests to. This will also be shown in the purchase flow, in emails, and on invoices.

New RevenueCat Billing App

After creating the app, some additional fields will be shown:

  • App icon: You can upload an app icon here that will be shown in the purchase flow, in emails, and on invoices.
  • Public API Key: The API key to be used in your web application when initializing the SDK. This key is safe to be included in production code.
  • Sandbox API Key: The API key to be used for testing purposes. Do not share this API key or include it in your production web app, as it allows to make purchases and unlocking entitlements using test cards.

Product configuration

To get started with the Web SDK, you should follow the regular setup of entitlements, products, and offerings.

In contrast to Apple App Store, Google Play Store, and Stripe Billing products, you can configure your product and its duration directly in the RevenueCat dashboard:

New product configuration page

SDK configuration

Configure the RevenueCat Web SDK by calling the static initializePurchases() method of the Purchases class:

import { ErrorCode, Purchases, PurchasesError } from "@revenuecat/purchases-js";

const appUserId = authentication.getAppUserId(); // Replace with your own authentication system
const purchases = Purchases.configure(REVENUECAT_BILLING_PUBLIC_API_KEY, appUserId);
⚠️Only configure once

You should configure the SDK only once in your code, attempting to call initializePurchases() twice will throw an exception.

You can use the object returned from the initializePurchases() method to call any of the SDK methods, or use the static method getInstance() of the Purchases class to returned the initialized Purchases object.

The method will throw an exception if the SDK has not yet been configured.

Identifying customers

In contrast to our mobile SDKs, the Web SDK currently supports only identified (non-anonymous) customers. There are two reasons for this limitation:

  1. In contrast to mobile app stores, there is no "restore purchase" functionality linked to a store account of the customer. Therefore, an anonymous purchase might get lost if the customer reloads their browser or clears their cache/cookies.
  2. It is not possible to write a secure web application completely without a backend, since it is easily possible for a malicious actor to manipulate the state of the application through the browser's developer tools. Therefore, any web app using RevenueCat Billing to manage subscriptions also needs a backend component to check for access, which requires a shared customer ID.

One way of achieving this is by setting up an authentication / login mechanism, for example, using a third party authentication provider like Firebase / Supabase or a library like auth.js. The added benefit of this is that using a shared authentication mechanism between web app and mobile apps allows customers to access their subscriptions they made on the web in the mobile app as well, without any additional configuration (other than adding the products to the correct entitlements).

Alternatively, you could also provide the App User ID to your web application through a URL parameter, eg. via emails sent to customers you have acquired in your mobile app.

Getting customer information

You can access the customer information using the getCustomerInfo() method of the Purchases object:

try {
customerInfo = await Purchases.getSharedInstance().getCustomerInfo();
// access latest customerInfo
} catch (e) {
// Handle errors fetching customer info
}

You can then use the entitlements property of the CustomerInfo object to check whether the customer has access to a specific entitlement:

if ("gold_entitlement" in customerInfo.entitlements.active) {
// Grant user access to the entitlement "gold_entitlement"
grantEntitlementAccess();
}

If your app has multiple entitlements, you might also want to check if the customer has any active entitlements:

if (Object.keys(customerInfo.entitlements.active).length > 0) {
// User has access to some entitlement, grant entitlement access
grantEntitlementAccess();
}

Building your paywall

Getting and displaying products

We recommend using Offerings to configure which products get presented to the customer. You can access the offerings for a given app user ID with the (asynchronous) method purchases.getOfferings(). The current offering for the customer is contained in the current property of the return value.

try {
const offerings = await Purchases.getSharedInstance().getOfferings();
if (
offerings.current !== null &&
offerings.current.availablePackages.length !== 0
) {
// Display packages for sale
displayPackages(offerings.current.availablePackages);
}
} catch (e) {
// Handle errors
}

It's also possible to access other Offerings besides the Current Offering directly by its identifier.

try {
const offerings = await Purchases.getSharedInstance().getOfferings();
if (offerings.all["experiment_group"].availablePackages.length !== 0) {
// Display packages for sale
displayPackages(offerings.all["experiment_group"].availablePackages);
}
} catch (e) {
// Handle errors
}

Each Offering contains a list of Packages. Packages can be accessed in a few different ways:

  1. via the .availablePackages property on an Offering.
  2. via the duration convenience property on an Offering
  3. via the .packagesById property of an Offering, keyed by the package identifier
const allPackages = offerings.all["experiment_group"].availablePackages;
// --
const monthlyPackage = offerings.all["experiment_group"].monthly;
// --
const customPackage = offerings.all["experiment_group"].packagesById["<package_id>"];

Each Package contains information about the product in the rcBillingProduct property:

// Accessing / displaying the monthly product
try {
const offerings = await Purchases.getSharedInstance().getOfferings();
if (offerings.current && offerings.current.monthly) {
const product = offerings.current.monthly.rcBillingProduct;
// Display the price and currency of the RC Billing Product
displayProduct(product);
}
} catch (e) {
// Handle errors
}

Purchasing a package

To purchase a package, use the purchasePackage() method of the Purchases object. It returns a Promise<{customerInfo: CustomerInfo}>. To handle errors, you should catch any exceptions that occur.

try {
const { customerInfo } = await Purchases.getSharedInstance().purchasePackage(pkg);
if (Object.keys(customerInfo.entitlements.active).includes("pro")) {
// Unlock that great "pro" content
}
} catch (e) {
if (e instanceof PurchasesError && e.errorCode == ErrorCode.UserCancelledError) {
// User cancelled the purchase process, don't do anything
} else {
// Handle errors
}
}

Testing

You can use Stripe's Test Mode to test your RevenueCat Billing integration.

⚠️Ensure proper sandbox testing

In contrast to mobile app stores, there is no intermediary between you and the customer, and no App Review before you start charging customers money. Therefore, you should make sure that you have properly tested your implementation in sandbox mode before shipping your web subscriptions.

If you use the RevenueCat Billing sandbox API key, Stripe's test mode will be used automatically, and you can use one of Stripe's test cards to make purchases.