# In-app purchases with RevenueCat

Source: https://saastostore.com/docs/in-app-purchases

If your app sells digital content, Google Play generally requires its own billing for sales made in the app. SaasToStore connects your site to Google Play subscriptions through RevenueCat. Your website code starts the purchase and receives the result: no native code.

> **Warning:** This works on Android today. It isn't available in iOS apps yet, so hide your purchase button there (see [iOS](#ios)).

## What you need

- An **Android Build (Native)** with native navigation (Shell V2)
- A RevenueCat project, with its public Android API key (it starts with `goog_`)
- An Entitlement ID in RevenueCat, such as `premium`

## Set it up

1. **Turn on In-App Subscriptions** In your project's plugin library, add the plugin and fill in the API key and the Entitlement ID.
2. **Add the code to your site** Use the example below, or the [prompt for AI builders](https://saastostore.com/docs/javascript-bridge).
3. **Launch a new build** The plugin is part of the native app, so it needs a build.
4. **Test with a test account** Purchases only work in an app installed from a Google Play test track, signed in with a tester's account.

## The four calls

```js
billing.identify(userId);     // after sign-in, before any purchase
billing.getStatus();          // is the subscription active?
billing.purchase(productId);  // opens the Google Play purchase sheet
billing.restore();            // restore purchases
```

Results don't come back from the call. They arrive as an `s2s:billing` event on `window`, with a `type` in `event.detail`:

```
identify_result  { success, userId, error? }
purchase_result  { success, productId, isEntitled?, error? }
restore_result   { success, isEntitled, error? }
status           { isEntitled, entitlementId, expiresAt? }
```

## Full example

```js
const billing = await whenSaasToStore("billing");

window.addEventListener("s2s:billing", (e) => {
  const d = e.detail;
  if (d.type === "identify_result" && d.success) billing.getStatus();
  if (d.type === "status") setPremium(d.isEntitled);
  if (d.type === "purchase_result" && d.success) setPremium(d.isEntitled);
});

if (billing?.isAvailable) billing.identify(currentUser.id); // after sign-in

// your "Upgrade" button, inside the Android app:
billing.purchase("premium_monthly");
```

`whenSaasToStore` waits for the bridge to load. Its code is in the [JavaScript bridge](https://saastostore.com/docs/javascript-bridge) reference.

> Keep the purchase state on your server too. RevenueCat can notify your server by webhook, so someone who subscribed in the app stays premium on your website.

## iOS

In-app purchases aren't available in iOS apps yet. Apple doesn't allow a digital purchase through your own web checkout in most cases, so don't leave that path visible. Use the marker to hide your purchase button on iPhone:

```js
const ua = navigator.userAgent;
const inApp = ua.includes("SaasToStoreApp");
const isIos = /iPhone|iPad|iPod/.test(ua);
const canSell = !inApp || !isIos;
```

## What about physical goods?

Physical products and real-world services don't use store billing. A shop can keep its normal checkout: Stripe, Shop Pay or another provider. If checkout runs on another domain, it opens in the system browser, unless you add it to Extra domains.

## Frequently asked questions

### Where do I create my subscriptions?

In the Google Play Console, under Monetize, then link them in RevenueCat. Your app needs to be uploaded to a test track before Google lets you create them.

### Is the RevenueCat API key safe in the app?

Yes. It's the public key, made to be embedded in apps. Never paste a secret key here.

### Does RevenueCat cost extra?

RevenueCat has its own pricing, with a free tier at the time of writing. Check their site for current terms. SaasToStore adds no commission.

### Can I keep Stripe on Android?

For digital goods, Google Play's billing rules generally apply, with exceptions that depend on the country. Check the current policy for your market before you submit.
