Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Ti.RevenueCat

Native iOS and Android module that wraps the RevenueCat SDK and RevenueCatUI paywalls for Titanium apps.

Ti.RevenueCat brings RevenueCat's subscription and in-app purchase infrastructure to Titanium: offerings, purchases, restores, customer info, subscription management, and both modal and embeddable RevenueCatUI paywalls — all from JavaScript, backed by native Swift (iOS) and Kotlin (Android) modules that share the same JS API.

Titanium Platform License Maintained


Roadmap

  • Core Purchases API (offerings, purchase, restore, customer info, login/logout)
  • Real-time customer info updates
  • RevenueCatUI Paywall — modal presentation
  • RevenueCatUI Paywall — embeddable view
  • Subscription management (showManageSubscriptions)
  • Android support

Features

  1. Core Purchases API - Configure the SDK, fetch offerings, purchase packages, restore purchases, read customer info, and manage user identity (login/logout)
  2. Real-Time Customer Info Updates - A single event keeps your app in sync with entitlement changes, including renewals and expirations that happen in the background
  3. Paywall — Modal Presentation - Present RevenueCat's dashboard-configured paywall (or the automatic default template) as a full-screen modal with one call
  4. Paywall — Embeddable View - Drop the same paywall into any Titanium view hierarchy as a regular view, for custom layouts and flows
  5. Subscription Management - Send users straight to the native "Manage Subscription" sheet

Table of Contents


Installation

1. Download the Module

Download the latest version from the releases page. The zip contains both the iOS and Android builds.

2. Install the module in your Titanium project

# Copy the compiled modules to:
{YOUR_PROJECT}/modules/iphone/
{YOUR_PROJECT}/modules/android/

3. Configure tiapp.xml

<modules>
    <module platform="iphone">ti.revenuecat</module>
    <module platform="android">ti.revenuecat</module>
</modules>

<ios>
    <min-ios-ver>15.0</min-ios-ver>
</ios>

Why iOS 15.0? RevenueCatUI (the paywall UI) is built on SwiftUI and requires iOS 15.0+. The core Purchases API alone only needs iOS 13.0+, but since this module bundles both, 15.0 is the effective minimum.

4. Android only — two required manual steps

RevenueCat's Android paywall UI is built on Jetpack Compose. As of this writing, Titanium's module build system doesn't have first-class support for the Compose Compiler Gradle plugin, so two manual steps are required until that's resolved upstream. Skip this section if your app is iOS-only.

4a. Add a Gradle init script (once per development machine)

Create ~/.gradle/init.d/compose-plugin.gradle:

initscript {
    repositories {
        gradlePluginPortal()
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.3.0'
    }
}

allprojects {
    buildscript {
        repositories {
            gradlePluginPortal()
            google()
            mavenCentral()
        }
        dependencies {
            classpath 'org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.3.0'
        }
    }
}

Without this, building any app that includes this module fails with Plugin with id 'org.jetbrains.kotlin.plugin.compose' not found. This has to live outside the module's own files — it's a Gradle-level fix that applies to every build on your machine, not something a module zip can carry. If your project uses a different Kotlin version, match it in the classpath line above.

4b. Also worth checking

Google Play may ask the user to confirm payment outside your app (e.g. a banking app). If your app's main Activity launchMode isn't standard or singleTop, the purchase can get cancelled when your app returns to the foreground. Check this in tiapp.xml before testing real purchases.

5. Configure your RevenueCat project

This module doesn't replace RevenueCat's own dashboard setup. Before calling configure(), you'll need, in the RevenueCat dashboard:

  • An In-App Purchase Key uploaded for iOS (Apps → your iOS app → In-app Purchase Key Configuration) — required for the SDK to report transactions
  • At least one Entitlement, with your store products (iOS and/or Android) attached to it
  • At least one Offering with Packages pointing at those products, marked as Default

See RevenueCat's Configuring Products guide if you're setting this up for the first time.


Quick Start

import RevenueCatModule from 'ti.revenuecat';

RevenueCatModule.configure({ apiKey: 'appl_YOUR_PUBLIC_API_KEY' });

RevenueCatModule.getOfferings(result => {
    if (!result.success || !result.current) return;

    const offering = result.offerings[result.current];
    const pkg = offering.packages[0];

    RevenueCatModule.purchase({ packageIdentifier: pkg.identifier }, purchaseResult => {
        if (purchaseResult.success) {
            console.log('Purchased! Active entitlements:', JSON.stringify(purchaseResult.customerInfo.activeEntitlements));
        }
    });
});

Features

Feature 1: Core Purchases API

Configure the SDK, fetch products, and process purchases — everything needed for a bespoke purchase flow.

Configure

RevenueCatModule.configure({
    apiKey: 'appl_YOUR_PUBLIC_API_KEY',
    appUserID: 'optional-your-own-user-id' // omit to let RevenueCat generate an anonymous ID
});

Fetch Offerings

RevenueCatModule.getOfferings(result => {
    if (!result.success) {
        console.error(result.error);
        return;
    }

    console.log('Current offering:', result.current);
    console.log('All offerings:', JSON.stringify(result.offerings));
});

Purchase a Package

RevenueCatModule.purchase({ packageIdentifier: '$rc_monthly' }, result => {
    if (result.userCancelled) {
        console.log('User cancelled the purchase');
        return;
    }
    if (!result.success) {
        console.error('Purchase failed:', result.error);
        return;
    }
    console.log('Purchased! Customer info:', JSON.stringify(result.customerInfo));
});

packageIdentifier must come from a package returned by getOfferings() — the module caches packages from the last getOfferings() call so it can look them up by identifier.

Restore Purchases

RevenueCatModule.restorePurchases(result => {
    if (result.success) {
        console.log('Restored:', JSON.stringify(result.customerInfo));
    }
});

Get Customer Info

RevenueCatModule.getCustomerInfo(result => {
    if (result.success) {
        const isPremium = !!result.customerInfo.activeEntitlements['premium'];
        console.log('Is premium:', isPremium);
    }
});

User Identity

// Log in with your own user ID (e.g. after your app's own login flow)
RevenueCatModule.logIn('your-internal-user-id', result => {
    console.log('Logged in, newly created:', result.created);
});

// Log out — reverts to a new anonymous user
RevenueCatModule.logOut(result => {
    console.log('Logged out:', JSON.stringify(result.customerInfo));
});

Feature 2: Real-Time Customer Info Updates

One listener keeps entitlement state in sync automatically — including renewals, expirations, refunds, and billing issues that happen while the app is open, without polling.

RevenueCatModule.addEventListener('customerInfoUpdated', e => {
    const isPremium = !!e.customerInfo.activeEntitlements['premium'];
    updateAppUI(isPremium);
});

Use Cases

  1. Live subscription state - Reflect a lapsed renewal or refund immediately, without requiring the user to reopen the app
  2. Cross-device consistency - Pick up entitlement changes made on another device tied to the same user
  3. Post-migration sync - Confirm existing subscribers keep access automatically after adopting this module in an app that previously used raw StoreKit

Feature 3: Paywall — Modal Presentation

Present RevenueCat's dashboard-configured paywall as a full-screen modal. If no custom paywall was designed in the dashboard for the offering, RevenueCatUI automatically falls back to a sensible default template — no extra configuration required to get started.

RevenueCatModule.presentPaywall({
    // offeringIdentifier: 'default' // optional — omit to use the current offering
}, result => {
    switch (result.event) {
        case 'purchaseStarted':
            console.log('Purchase started for', result.packageIdentifier);
            break;
        case 'purchased':
            console.log('Purchased!', JSON.stringify(result.customerInfo));
            break;
        case 'restored':
            console.log('Restored!', JSON.stringify(result.customerInfo));
            break;
        case 'dismissed':
            console.log('Paywall dismissed');
            break;
    }
});

Feature 4: Paywall — Embeddable View

The same paywall, embedded as a regular Titanium view — for cases where a full-screen modal doesn't fit your navigation flow (onboarding steps, a tab, an inline upsell card, etc).

const win = Ti.UI.createWindow({ backgroundColor: '#fff', extendSafeArea: false });

const paywallView = RevenueCatModule.createPaywallView({
    // offeringIdentifier: 'default', // optional — omit to use the current offering
    width: Ti.UI.FILL,
    height: Ti.UI.FILL
});

[
    'purchaseStarted', 'purchaseCompleted', 'purchaseCancelled', 'purchaseFailure',
    'restoreStarted', 'restoreCompleted', 'restoreFailure', 'requestedDismissal'
].forEach(eventName => {
    paywallView.addEventListener(eventName, e => {
        console.log(eventName, JSON.stringify(e));
        if (eventName === 'requestedDismissal') win.close();
    });
});

win.add(paywallView);
win.open();

Note: unlike the modal presentation, exit offers configured in the paywall builder don't apply to the embedded view — that's a RevenueCatUI limitation, not specific to this module.


Feature 5: Subscription Management

Send the user straight to the native subscription management sheet (cancel, change plan, etc.), without leaving your app.

RevenueCatModule.showManageSubscriptions(result => {
    if (!result.success) {
        console.warn('Could not open subscription management:', result.error);
    }
});

If this fails or isn't available, fall back to opening customerInfo.managementURL (included in every customerInfo payload) with Ti.Platform.openURL().


API Reference

Module Methods

configure(options)

Initializes the SDK. Must be called before any other method.

Parameters:

  • apiKey (String, required) - Your RevenueCat public API key
  • appUserID (String, optional) - Your own user identifier. Omit to use an anonymous RevenueCat-generated ID

getOfferings(callback)

Fetches all configured offerings and their packages.

Callback payload:

  • success (Boolean)
  • error (String, on failure)
  • offerings (Object) - keyed by offering identifier
  • current (String) - identifier of the offering marked "Default" in the dashboard

purchase(options, callback)

Purchases a package returned by a previous getOfferings() call.

Parameters:

  • packageIdentifier (String, required)

Callback payload:

  • success (Boolean)
  • userCancelled (Boolean)
  • error (String, on failure)
  • customerInfo (Object, on success)

restorePurchases(callback)

Restores previous purchases for the current App Store account.

Callback payload: success, error, customerInfo


getCustomerInfo(callback)

Fetches the current customer's entitlement and purchase state.

Callback payload: success, error, customerInfo


logIn(appUserID, callback)

Identifies the current user with your own ID, merging anonymous purchase history where possible.

Callback payload: success, error, created (Boolean), customerInfo


logOut(callback)

Logs out the current user, reverting to a new anonymous ID.

Callback payload: success, error, customerInfo


showManageSubscriptions(callback)

Opens the native subscription management sheet.

Callback payload: success, error


presentPaywall(options, callback)

Presents the paywall for an offering as a full-screen modal.

Parameters:

  • offeringIdentifier (String, optional) - defaults to the current offering

Callback payload (callback is called once per event):

  • event (String) - one of purchaseStarted, purchased, restored, dismissed
  • packageIdentifier (String, on purchaseStarted)
  • customerInfo (Object, on purchased / restored)

createPaywallView(options)

Creates an embeddable paywall view.

Parameters:

  • offeringIdentifier (String, optional) - defaults to the current offering
  • Any standard Titanium view layout property (width, height, top, etc.)

Returns: a Titanium view proxy

Events: purchaseStarted, purchaseCompleted, purchaseCancelled, purchaseFailure, restoreStarted, restoreCompleted, restoreFailure, requestedDismissal


customerInfo object shape

Returned by every method above that includes a customerInfo field:

{
    originalAppUserId: "$RCAnonymousID:...",
    activeEntitlements: {
        premium: {
            identifier: "premium",
            isActive: true,
            willRenew: true,
            productIdentifier: "com.yourapp.subscription.monthly",
            expirationDate: 1786994772 // seconds since epoch
        }
    },
    allPurchasedProductIdentifiers: ["com.yourapp.subscription.monthly"],
    managementURL: "https://apps.apple.com/account/subscriptions"
}

allPurchasedProductIdentifiers lists every product ever purchased, including expired or cancelled ones — use activeEntitlements to gate access, not this list.

Module-Level Events

  • customerInfoUpdated - fires whenever entitlement state changes, including renewals and expirations that happen in the background

Requirements

Both platforms:

  • Titanium SDK 13.2.0.GA or later
  • A RevenueCat account with at least one Entitlement, Offering, and Package configured

iOS:

  • Titanium SDK 13.1.1.GA minimum (for SPM support)
  • iOS 15.0+ deployment target
  • An In-App Purchase Key uploaded to your RevenueCat app config

Android:

  • minSdkVersion 24+
  • Kotlin 2.3.0+ (must match the version used in the Gradle init script — see Installation)
  • RevenueCat Android SDK 10.16.2+ (com.revenuecat.purchases:purchases / purchases-ui)
  • Both manual setup steps from Installation (Gradle init script + tiapp.xml Activity declaration)

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT

About

Native iOS module that wraps the RevenueCat SDK and RevenueCatUI paywalls for Titanium apps.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages