Types
Data types returned by the Rovenue SDK.
Type gallery: RovenueConfig · User · Entitlement · ProductType · StoreProduct · Package · Offering · Offerings · PurchaseResult · ChangeEvent · LogEntry
consumable products in ProductType add virtual-currency balances — configured in the Rovenue dashboard — to the subscriber's account on purchase.
RovenueConfig
The configuration object passed to configure() on React Native. Swift and Kotlin use labelled parameters directly.
type RovenueConfig = {
apiKey: string;
baseUrl?: string;
logLevel?: 'off' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
appVersion?: string;
};Fields
| Field | Type | Required | Description |
|---|---|---|---|
apiKey | string | Yes | Your project's public API key. Prefix rov_pub_. Obtain it from the Rovenue dashboard → API Keys. |
baseUrl | string | No | API endpoint URL. Defaults to the hosted endpoint https://api.rovenue.io; override only when self-hosting (https://; http:// allowed for localhost only). |
logLevel | 'off' | 'error' | 'warn' | 'info' | 'debug' | 'trace' | No | Verbosity of SDK logs. Defaults to 'warn' — turn up to 'info'/'debug' locally, never ship 'debug'/'trace' in production. |
appVersion | string | No | App version string forwarded to the server. Defaults to the value reported by the OS. |
Returned by / used by: configure()
Usage
import { Rovenue } from '@rovenue/react-native-sdk';
Rovenue.configure({
apiKey: 'rov_pub_…',
logLevel: 'info',
appVersion: '3.1.0',
// baseUrl: 'https://api.acme.com', // optional — only when self-hosting
});User
Represents the current device's identity.
type User = {
rovenueId: string;
appUserId: string | null;
};Fields
| Field | Type | Description |
|---|---|---|
rovenueId | string | Permanent per-install identifier generated on first launch (prefix rov_). Always present; never changes for the lifetime of the install. |
appUserId | string | null | Your backend's user ID, attached after a successful identify() call. null until the user is identified. |
Returned by: currentUser()
Usage
const user = await Rovenue.currentUser();
if (user.appUserId) {
console.log(`Known user: ${user.appUserId}`);
} else {
console.log(`Anonymous install: ${user.rovenueId}`);
}let user = await Rovenue.shared.currentUser()
if let appUserId = user.appUserId {
print("Known user: \(appUserId)")
} else {
print("Anonymous install: \(user.rovenueId)")
}val user = Rovenue.shared.currentUser()
if (user.appUserId != null) {
Log.d("App", "Known user: ${user.appUserId}")
} else {
Log.d("App", "Anonymous install: ${user.rovenueId}")
}Entitlement
Represents a single access entitlement. An entitlement is active when the user has a valid subscription or has made a one-time unlock purchase for the corresponding product.
type Entitlement = {
id: string;
isActive: boolean;
productIdentifier: string;
store: string;
expiresIso?: string;
};Fields
| Field | Type | Description |
|---|---|---|
id | string | The entitlement key as configured in the Rovenue dashboard (e.g. "pro", "credits_pack"). |
isActive | boolean | true if the entitlement is currently active (not expired, not refunded). Always check this field before granting access. |
productIdentifier | string | The store product ID that granted this entitlement (e.g. "com.example.pro_monthly"). |
store | string | The store that originated the purchase. One of "app_store", "play_store", or "stripe". |
expiresIso | string | undefined | ISO 8601 expiry timestamp (e.g. "2026-12-31T23:59:59Z"). undefined for non-expiring (lifetime) entitlements. |
Returned by: entitlement(id), entitlementsAll(), PurchaseResult.entitlements
Usage
const ent = await Rovenue.entitlement('pro');
if (ent?.isActive) {
// Grant Pro access
const expiresAt = ent.expiresIso
? new Date(ent.expiresIso)
: null; // null = lifetime
}let ent = await Rovenue.shared.entitlement(id: "pro")
if ent?.isActive == true {
// Grant Pro access
let expiresAt = ent?.expiresIso.flatMap { ISO8601DateFormatter().date(from: $0) }
}val ent = Rovenue.shared.entitlement(id = "pro")
if (ent?.isActive == true) {
// Grant Pro access
val expiresAt = ent.expiresIso?.let { Instant.parse(it) } // null = lifetime
}ProductType
A string enum describing what a store product delivers.
type ProductType = 'subscription' | 'consumable' | 'non_consumable';Values
| Value | Description |
|---|---|
subscription | Auto-renewing subscription. Grants entitlements for the subscription period. |
consumable | One-time purchase that adds credits to the user's balance. |
non_consumable | One-time permanent unlock. Grants a lifetime entitlement. |
Used by: StoreProduct.type
StoreProduct
Represents a store product with localised pricing and subscription metadata from the App Store or Google Play. You receive this as Package.product — you do not construct it directly.
Basic schema
type StoreProduct = {
// Core fields
id: string;
type: ProductType;
productCategory: 'subscription' | 'nonSubscription';
displayName: string;
description: string | null;
priceString: string | null;
price: number | null;
currencyCode: string | null;
// Subscription metadata
subscriptionPeriod: Period | null;
subscriptionGroupIdentifier: string | null; // iOS only
isFamilyShareable: boolean; // iOS only
introPrice: IntroPrice | null; // iOS only
discounts: Discount[]; // iOS promotional offers (empty on Android)
isEligibleForIntroOffer: boolean | null;
// Android subscription options (null on iOS)
subscriptionOptions: SubscriptionOption[] | null;
defaultOption: SubscriptionOption | null;
// Per-unit pricing (approximated)
pricePerWeek: number | null;
pricePerMonth: number | null;
pricePerYear: number | null;
pricePerWeekString: string | null;
pricePerMonthString: string | null;
pricePerYearString: string | null;
// Platform-native escape hatch (Swift & Kotlin only; null on React Native)
rawStoreProduct: StoreKit.Product | ProductDetails | null;
};Core Fields
| Field | Type | Description |
|---|---|---|
id | string | The store product identifier (e.g. "com.example.pro_monthly"). |
type | ProductType | What the product delivers: subscription, consumable, or non-consumable. |
productCategory | 'subscription' | 'nonSubscription' | Product category from the store. Maps to type: subscriptions have category 'subscription'; consumables and non-consumables have 'nonSubscription'. |
displayName | string | Localised product name from the store (e.g. "Pro Monthly"). |
description | string | null | Localised product description. null if not provided by the store. |
priceString | string | null | Localised, formatted price string (e.g. "$4.99", "€4,99"). Display this directly in your paywall. null if unavailable. |
price | number | null | Numeric price in the user's currency (e.g. 4.99). null if unavailable. |
currencyCode | string | null | ISO 4217 currency code (e.g. "USD", "EUR"). null if unavailable. |
Subscription Fields
| Field | Type | Platform | Description |
|---|---|---|---|
subscriptionPeriod | Period | null | All | The billing interval (e.g. 1 month, 1 year). null for non-subscription products. |
subscriptionGroupIdentifier | string | null | iOS | The App Store subscription group. Groups related subscriptions so users can switch between them. null on Android and non-subscription products. |
isFamilyShareable | boolean | iOS | true if eligible for family sharing on iOS. Always false on Android. |
introPrice | IntroPrice | null | iOS | The introductory offer (free trial or discounted period). iOS only; null on Android. |
discounts | Discount[] | iOS | iOS promotional offers (introductory + winback offers). Empty array on Android. |
isEligibleForIntroOffer | boolean | null | All | iOS: the real StoreKit value (true if intro offer eligible). Android: derived; true iff a free or intro phase is present. Play enforces actual eligibility at purchase time. null if unavailable. |
subscriptionOptions | SubscriptionOption[] | null | Android | Android subscription options (base plans + offers). null on iOS. |
defaultOption | SubscriptionOption | null | Android | The recommended option for this product (e.g. the base plan without an offer). null on iOS. |
Per-Unit Pricing (Approximated)
| Field | Type | Description |
|---|---|---|
pricePerWeek | number | null | Estimated weekly price, calculated from subscriptionPeriod and price (7 days per week). For a 2-week plan priced at $5, this is ~$3.50/week. An approximation only; exact billing varies by period. |
pricePerMonth | number | null | Estimated monthly price (30 days per month). |
pricePerYear | number | null | Estimated annual price (365 days per year). |
pricePerWeekString | string | null | Localised price-per-week string. |
pricePerMonthString | string | null | Localised price-per-month string. |
pricePerYearString | string | null | Localised price-per-year string. |
Platform-Native Escape Hatch
| Field | Type | Platform | Description |
|---|---|---|---|
rawStoreProduct | StoreKit.Product (iOS) / ProductDetails (Android) / null (RN) | Swift & Kotlin | Direct reference to the platform's native product object. Use only when the Rovenue wrapper is insufficient. Not available in React Native. |
Used by: Package.product
Usage
const pkg = offerings.current?.packages[0];
if (pkg) {
const {
displayName, priceString, type, description, subscriptionPeriod,
pricePerMonthString, isEligibleForIntroOffer
} = pkg.product;
console.log(`${displayName} — ${priceString}`);
console.log(`Description: ${description}`);
if (subscriptionPeriod) {
console.log(`Billing: every ${subscriptionPeriod.value} ${subscriptionPeriod.unit}`);
console.log(`~${pricePerMonthString}/month`);
}
if (isEligibleForIntroOffer) {
console.log('Eligible for intro offer');
}
}let pkg = offerings.current?.packages.first
if let product = pkg?.product {
print("\(product.displayName) — \(product.priceString ?? "N/A")")
if let period = product.subscriptionPeriod {
print("Billing: every \(period.value) \(period.unit)")
print("~\(product.pricePerMonthString ?? "N/A")/month")
}
if let intro = product.introPrice {
print("Intro: \(intro.priceString ?? "free") for \(intro.cycles) cycles")
}
// Access promotional offers (iOS-specific)
for discount in product.discounts {
print("Promo: \(discount.priceString ?? "free")")
}
// Access native StoreKit.Product if needed
if let native = product.rawStoreProduct {
print("Native product: \(native.id)")
}
}val pkg = offerings.current?.packages.firstOrNull()
pkg?.product?.let { product ->
println("${product.displayName} — ${product.priceString ?: "N/A"}")
product.subscriptionPeriod?.let { period ->
println("Billing: every ${period.value} ${period.unit}")
println("~${product.pricePerMonthString ?: "N/A"}/month")
}
product.introPrice?.let { intro ->
println("Intro: ${intro.priceString ?: "free"} for ${intro.cycles} cycles")
}
// Access Android subscription options
product.subscriptionOptions?.forEach { option ->
println("Option: ${option.id}")
option.pricingPhases.forEach { phase ->
println(" Phase: ${phase.priceString} (${phase.recurrenceMode})")
}
}
// Access native ProductDetails if needed
product.rawStoreProduct?.let { native ->
println("Native product: ${native.productId}")
}
}Supporting types for StoreProduct
Period
Represents a billing or time interval.
type Period = {
value: number;
unit: 'day' | 'week' | 'month' | 'year';
iso8601: string;
};Fields
| Field | Type | Description |
|---|---|---|
value | number | The magnitude (e.g. 1, 3, 12). |
unit | 'day' | 'week' | 'month' | 'year' | The time unit. |
iso8601 | string | ISO 8601 duration string (e.g. "P1M" for 1 month, "P1Y" for 1 year). Useful for standardised duration calculations. |
Used by: StoreProduct.subscriptionPeriod, IntroPrice.period, Discount.period, PricingPhase.billingPeriod
Example
const period = product.subscriptionPeriod;
if (period?.unit === 'month' && period.value === 1) {
console.log('Billing monthly'); // period.iso8601 → "P1M"
}IntroPrice
The introductory offer on an iOS subscription (free trial or discounted period).
type IntroPrice = {
price: number | null;
priceString: string | null;
currencyCode: string | null;
period: Period;
cycles: number;
paymentMode: 'freeTrial' | 'payAsYouGo' | 'payUpFront';
};Fields
| Field | Type | Description |
|---|---|---|
price | number | null | The price per cycle (e.g. 0 for free trial, 0.99 for discounted). null if unavailable. |
priceString | string | null | Localised price string. null if unavailable. |
currencyCode | string | null | ISO 4217 code. null if unavailable. |
period | Period | The duration of each intro cycle (e.g. 1 week free). |
cycles | number | How many intro periods apply (e.g. 1 = 1-week free trial; 3 = 3-week trial). |
paymentMode | 'freeTrial' | 'payAsYouGo' | 'payUpFront' | When payment is charged: 'freeTrial' (after trial ends), 'payAsYouGo' (each cycle, starting now), 'payUpFront' (upfront). |
Platform: iOS only. null on Android (use subscriptionOptions.introPhase instead).
Used by: StoreProduct.introPrice
Example
const intro = product.introPrice;
if (intro) {
if (intro.paymentMode === 'freeTrial' && intro.price === 0) {
console.log(`${intro.cycles}-${intro.period.unit} free trial`);
} else {
console.log(`${intro.priceString}/${intro.period.value} ${intro.period.unit} for ${intro.cycles} cycles`);
}
}Discount
An iOS promotional offer (winback or ongoing discount on a subscription).
type Discount = {
identifier: string | null;
price: number | null;
priceString: string | null;
currencyCode: string | null;
period: Period;
numberOfPeriods: number;
paymentMode: 'freeTrial' | 'payAsYouGo' | 'payUpFront';
type: 'introductory' | 'promotional' | 'winBack';
};Fields
| Field | Type | Description |
|---|---|---|
identifier | string | null | The offer identifier from the App Store dashboard. null if unavailable. |
price | number | null | The discounted price per cycle. null if unavailable. |
priceString | string | null | Localised price string. |
currencyCode | string | null | ISO 4217 code. |
period | Period | The duration of each discount cycle. |
numberOfPeriods | number | How many cycles the discount applies (e.g. 3 = 3 months at discount price). |
paymentMode | 'freeTrial' | 'payAsYouGo' | 'payUpFront' | When payment is charged. |
type | 'introductory' | 'promotional' | 'winBack' | Offer type: introductory (new subs only, applied automatically), promotional (all users), or winBack (lapsed subs). Note: only .promotional and .winBack can be passed to purchase(); .introductory is applied automatically by the App Store. |
Platform: iOS only. Empty array on Android.
Used by: StoreProduct.discounts
See also: Redeeming promotional offers (iOS)
PricingPhase
An Android subscription pricing phase (free, intro, or full price; part of a SubscriptionOption).
type PricingPhase = {
price: number | null;
priceString: string | null;
currencyCode: string | null;
billingPeriod: Period;
billingCycleCount: number | null;
recurrenceMode: 'infiniteRecurring' | 'finiteRecurring' | 'nonRecurring';
paymentMode: 'freeTrial' | 'payAsYouGo' | 'payUpFront' | null;
};Fields
| Field | Type | Description |
|---|---|---|
price | number | null | Price per cycle in this phase. 0 for free phases. null if unavailable. |
priceString | string | null | Localised price string. |
currencyCode | string | null | ISO 4217 code. |
billingPeriod | Period | How often the user is billed in this phase. |
billingCycleCount | number | null | How many billing cycles this phase lasts (e.g. 1 for a 1-month free trial, 12 for year-long offer). null for infinite recurrence. |
recurrenceMode | 'infiniteRecurring' | 'finiteRecurring' | 'nonRecurring' | 'infiniteRecurring' (lasts forever), 'finiteRecurring' (ends after billingCycleCount cycles), 'nonRecurring' (one-time). |
paymentMode | 'freeTrial' | 'payAsYouGo' | 'payUpFront' | null | When charged. null if unavailable. |
Platform: Android only. Not used on iOS.
Used by: SubscriptionOption.pricingPhases, SubscriptionOption.freePhase, SubscriptionOption.introPhase, SubscriptionOption.fullPricePhase
SubscriptionOption
An Android subscription option (base plan + offer combination, or just base plan). Equivalent to an iOS product in a subscription group but with explicit phase structure.
type SubscriptionOption = {
id: string;
basePlanId: string | null;
offerId: string | null;
tags: string[];
isBasePlan: boolean;
isPrepaid: boolean;
pricingPhases: PricingPhase[];
freePhase: PricingPhase | null;
introPhase: PricingPhase | null;
fullPricePhase: PricingPhase | null;
};Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for this option (e.g. "base", "base:offer_30_day_trial"). |
basePlanId | string | null | The base plan this option belongs to (e.g. "base", "annual"). null if unavailable. |
offerId | string | null | The offer ID if this is a base plan + offer combo (e.g. "30_day_trial"). null if this is the base plan without an offer. |
tags | string[] | User-assigned tags from the Play Console (e.g. ["trial", "new_user"]). Empty if none. |
isBasePlan | boolean | true if this option is the base plan (no offer applied). |
isPrepaid | boolean | true if prepaid (e.g. annual upfront). |
pricingPhases | PricingPhase[] | All pricing phases in order (free → intro → full price, or just full price). Used to compute net price. |
freePhase | PricingPhase | null | The free phase if present (e.g. 2-week free trial). null if none. |
introPhase | PricingPhase | null | The introductory discounted phase if present. null if none. |
fullPricePhase | PricingPhase | null | The full-price phase that lasts indefinitely (the recurring rate). null if unavailable. |
Platform: Android only. null on iOS.
Used by: StoreProduct.subscriptionOptions, StoreProduct.defaultOption
Example
const option = product.defaultOption || product.subscriptionOptions?.[0];
if (option) {
console.log(`Option: ${option.id}`);
option.pricingPhases.forEach((phase, i) => {
console.log(`Phase ${i}: ${phase.priceString} (${phase.recurrenceMode})`);
});
if (option.freePhase) {
console.log(`Free trial: ${option.freePhase.billingCycleCount} ${option.freePhase.billingPeriod.unit}`);
}
if (option.fullPricePhase) {
console.log(`Then: ${option.fullPricePhase.priceString}/${option.fullPricePhase.billingPeriod.value} ${option.fullPricePhase.billingPeriod.unit}`);
}
}PackageType
A convenience enum classifying packages into common categories (e.g. monthly, annual, lifetime) for UI presentation.
type PackageType =
| 'unknown' | 'custom'
| 'lifetime' | 'annual' | 'sixMonth' | 'threeMonth' | 'twoMonth' | 'monthly' | 'weekly';Values
| Value | Description |
|---|---|
'lifetime' | One-time permanent unlock. |
'annual' | 1-year subscription. |
'sixMonth' | 6-month subscription. |
'threeMonth' | 3-month subscription. |
'twoMonth' | 2-month subscription. |
'monthly' | 1-month subscription. |
'weekly' | 1-week subscription. |
'custom' | Billed on a custom interval (e.g. 2 weeks, 90 days). |
'unknown' | Unable to determine. Fallback for unrecognized patterns. |
Used by: Package.packageType
Offering convenience accessors
The Offering type provides accessors to quickly find packages by type:
React Native does not expose convenience accessors on the Offering object itself. Use Package.packageType to filter packages manually, or look them up by identifier:
const offerings = await Rovenue.getOfferings();
const offering = offerings.current;
// Find by packageType
const monthly = offering?.packages.find(p => p.packageType === 'monthly');
// Find by identifier
const annual = offering?.packages.find(p => p.identifier === 'annual');Swift Offering includes convenience properties:
let offering = offerings.current
// Access by PackageType enum
let monthly = offering?.monthly // Package? matching .monthly
let annual = offering?.annual // Package? matching .annual
let weekly = offering?.weekly // Package? matching .weekly
let sixMonth = offering?.sixMonth
let threeMonth = offering?.threeMonth
let twoMonth = offering?.twoMonth
let lifetime = offering?.lifetime
// Or look up by identifier
let custom = offering?.package(identifier: "pro_custom")Kotlin Offering includes convenience properties:
val offering = offerings.current
// Access by PackageType enum
val monthly = offering?.monthly // Package? matching MONTHLY
val annual = offering?.annual // Package? matching ANNUAL
val weekly = offering?.weekly // Package? matching WEEKLY
val sixMonth = offering?.sixMonth
val threeMonth = offering?.threeMonth
val twoMonth = offering?.twoMonth
val lifetime = offering?.lifetime
// Or look up by identifier
val custom = offering?.packageBy("\$rov_monthly")Package
A wrapper around a StoreProduct as configured in the Rovenue dashboard. Packages are the unit you pass to purchase().
type Package = {
identifier: string;
packageType: PackageType;
product: StoreProduct;
};Fields
| Field | Type | Description |
|---|---|---|
identifier | string | The package identifier as configured in the Rovenue dashboard (e.g. "monthly", "annual", "credits_100"). |
packageType | PackageType | A convenience category for the package (e.g. 'monthly', 'annual', 'lifetime'). Use this to simplify paywall UI logic. |
product | StoreProduct | The underlying store product with localised pricing and subscription metadata. |
Returned by: Offering.packages (via getOfferings())
Usage
const offerings = await Rovenue.getOfferings();
const pkg = offerings.current?.packages.find(p => p.identifier === 'annual');
if (pkg) {
console.log(`Annual plan: ${pkg.product.priceString}`);
console.log(`Type: ${pkg.packageType}`);
const result = await Rovenue.purchase(pkg);
}let offerings = try await Rovenue.shared.getOfferings()
let pkg = offerings.current?.packages.first { $0.identifier == "annual" }
if let pkg = pkg {
print("Annual plan: \(pkg.product.priceString ?? "N/A")")
print("Type: \(pkg.packageType)")
let result = try await Rovenue.shared.purchase(pkg)
}val offerings = Rovenue.shared.getOfferings()
val pkg = offerings.current?.packages?.find { it.identifier == "annual" }
pkg?.let {
println("Annual plan: ${it.product.priceString}")
println("Type: ${it.packageType}")
val result = Rovenue.shared.purchase(it)
}Offering
A named group of packages configured in the Rovenue dashboard. The dashboard controls which offering is marked as current (default), allowing remote configuration changes without an app release.
type Offering = {
id: string;
packages: Package[];
};Fields
| Field | Type | Description |
|---|---|---|
id | string | The offering identifier as configured in the dashboard (e.g. "default", "sale_q4"). |
packages | Package[] | The packages belonging to this offering, in dashboard-configured order. |
Used by: Offerings.current, Offerings.all
Usage
const offering = offerings.all['sale_q4'];
offering?.packages.forEach(pkg => {
console.log(pkg.id, pkg.product.priceString);
});Offerings
The top-level response from getOfferings().
type Offerings = {
current: Offering | null;
all: Record<string, Offering>;
};Fields
| Field | Type | Description |
|---|---|---|
current | Offering | null | The offering marked as default in the dashboard. null if no default is configured. |
all | Record<string, Offering> | All configured offerings, keyed by their identifier. |
Returned by: getOfferings()
Usage
const offerings = await Rovenue.getOfferings();
// Show the default paywall:
const packages = offerings.current?.packages ?? [];
// Or show a specific named offering (e.g. for an A/B test):
const saleOffering = offerings.all['sale_q4'];let offerings = try await Rovenue.shared.getOfferings()
// Show the default paywall:
let packages = offerings.current?.packages ?? []
// Or show a specific offering:
let saleOffering = offerings.all["sale_q4"]val offerings = Rovenue.shared.getOfferings()
// Show the default paywall:
val packages = offerings.current?.packages ?: emptyList()
// Or show a specific offering:
val saleOffering = offerings.all["sale_q4"]PurchaseResult
Returned by purchase() after the store transaction and server-side validation both succeed. Contains the fully-refreshed state — no additional polling is needed.
type PurchaseResult = {
entitlements: Entitlement[];
virtualCurrencies: Record<string, number>;
productId: string;
storeTransactionId: string;
isDeferred: boolean;
};Fields
| Field | Type | Description |
|---|---|---|
entitlements | Entitlement[] | The user's full set of active entitlements after the purchase. Empty when isDeferred is true — nothing has been granted yet. Use this to update gated UI immediately. |
virtualCurrencies | Record<string, number> | The user's virtual-currency balances after the purchase (currency code → amount). Reflects any balances added by a currency-pack product. |
productId | string | The store product ID that was purchased (e.g. "com.example.pro_monthly"). |
storeTransactionId | string | The platform transaction ID: StoreKit 2 transaction ID on iOS, Play Billing order ID on Android. Empty string when isDeferred is true. |
isDeferred | boolean | true when the purchase is pending external approval (e.g. Ask-to-Buy / parental controls) rather than completed. This is not an error — purchase() resolves normally. See Handling a deferred purchase. |
Returned by: purchase()
Usage
const result = await Rovenue.purchase(pkg);
if (result.isDeferred) {
// Ask-to-Buy — not an error. Wait for a change-stream event before granting anything.
} else {
// Grant access based on updated entitlements:
const hasPro = result.entitlements.some(e => e.id === 'pro' && e.isActive);
console.log(`Pro: ${hasPro}, COINS: ${result.virtualCurrencies['COINS'] ?? 0}`);
console.log(`Transaction: ${result.storeTransactionId}`);
}ChangeEvent
Emitted by the change stream whenever internal SDK state is updated. React to these events to keep your UI in sync without polling.
// React Native — string union (UPPER_SNAKE_CASE — matches the Rust core's wire values)
type ChangeEvent =
| 'ENTITLEMENTS_CHANGED'
| 'IDENTITY_CHANGED'
| 'VIRTUAL_CURRENCIES_CHANGED'
| 'REMOTE_CONFIG_CHANGED';On Swift, ChangeEvent is a Swift enum with cases .entitlementsChanged, .identityChanged, .virtualCurrenciesChanged, and .remoteConfigChanged, delivered as AsyncStream<ChangeEvent> via Rovenue.shared.changes.
On Kotlin, ChangeEvent is a Kotlin enum with constants ENTITLEMENTS_CHANGED, IDENTITY_CHANGED, VIRTUAL_CURRENCIES_CHANGED, and REMOTE_CONFIG_CHANGED, delivered as SharedFlow<ChangeEvent> via Rovenue.shared.changes.
Values
| Value (RN) | Swift case | Kotlin constant | When it fires |
|---|---|---|---|
'ENTITLEMENTS_CHANGED' | .entitlementsChanged | ENTITLEMENTS_CHANGED | After the entitlement cache is refreshed — e.g. after a successful purchase, refreshEntitlements(), or a foreground transition that triggers a background sync. |
'IDENTITY_CHANGED' | .identityChanged | IDENTITY_CHANGED | After identify() succeeds or logOut() resets the user. |
'VIRTUAL_CURRENCIES_CHANGED' | .virtualCurrenciesChanged | VIRTUAL_CURRENCIES_CHANGED | After refreshVirtualCurrencies() or a server-side grant/spend that updates the cache. |
'REMOTE_CONFIG_CHANGED' | .remoteConfigChanged | REMOTE_CONFIG_CHANGED | After every successful refreshRemoteConfig(). The SDK does not diff the payload, so this also fires when the refresh returned identical values. |
Emitted by: addChangeListener() (RN), Rovenue.shared.changes (Swift/Kotlin)
Usage
const unsub = Rovenue.addChangeListener(async (event) => {
if (event === 'ENTITLEMENTS_CHANGED') {
const ent = await Rovenue.entitlement('pro');
setHasPro(ent?.isActive ?? false);
}
if (event === 'VIRTUAL_CURRENCIES_CHANGED') {
const balances = Rovenue.virtualCurrencies();
setCoins(balances['COINS'] ?? 0);
}
});
return () => unsub(); // cleanup on unmountTask {
for await event in Rovenue.shared.changes {
switch event {
case .entitlementsChanged:
let ent = await Rovenue.shared.entitlement(id: "pro")
await MainActor.run { hasPro = ent?.isActive ?? false }
case .virtualCurrenciesChanged:
let balances = await Rovenue.shared.virtualCurrencyBalances()
await MainActor.run { self.coins = balances["COINS"] ?? 0 }
case .identityChanged:
break
case .remoteConfigChanged:
break
}
}
}Rovenue.shared.changes
.onEach { event ->
when (event) {
ChangeEvent.ENTITLEMENTS_CHANGED -> {
val ent = Rovenue.shared.entitlement(id = "pro")
_hasPro.value = ent?.isActive == true
}
ChangeEvent.VIRTUAL_CURRENCIES_CHANGED -> {
val balances = Rovenue.shared.virtualCurrencyBalances()
_coins.value = balances["COINS"] ?: 0
}
ChangeEvent.IDENTITY_CHANGED -> { /* re-read user if needed */ }
ChangeEvent.REMOTE_CONFIG_CHANGED -> { /* re-read flags/experiments */ }
}
}
.launchIn(viewModelScope)LogEntry
A structured log entry delivered to the handler registered via setLogHandler().
type LogEntry = {
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
timestamp: string;
};Fields
| Field | Type | Description |
|---|---|---|
level | 'debug' | 'info' | 'warn' | 'error' | Severity level. debug entries are only emitted when logLevel is set to 'debug' or 'trace' in RovenueConfig. |
message | string | Human-readable log message from the SDK. |
timestamp | string | ISO 8601 timestamp of when the entry was generated (e.g. "2026-06-15T10:30:00.000Z"). |
Used by: setLogHandler()
Usage
const unsub = Rovenue.setLogHandler((entry) => {
if (entry.level === 'error' || entry.level === 'warn') {
// Forward to your error tracking service
Sentry.addBreadcrumb({
message: entry.message,
level: entry.level,
timestamp: new Date(entry.timestamp).getTime() / 1000,
});
}
});FunnelClaimResult
Returned by the funnel-claim methods (claimFunnelToken, claimInstall, resolveFunnelClaim, claimFromClipboard, claimFromUrl) when a prior funnel session is matched, or delivered via addFunnelClaimListener.
// React Native
type FunnelClaimResult = {
subscriberId: string; // Rovenue subscriber ID of the matched user
funnelAnswers: Record<string, unknown>; // key-value map of funnel step answers, already parsed
};Fields
| Field | Type | Description |
|---|---|---|
subscriberId | string | Rovenue subscriber ID of the matched funnel session. |
funnelAnswers | Record<string, unknown> | Key-value map of funnel step answers, already parsed. React Native only — see below. |
Swift and Kotlin return the funnel answers as a raw JSON string, not a parsed object.
Their FunnelClaimResult is { subscriberId: String, funnelAnswersJson: String } —
decode funnelAnswersJson yourself (e.g. JSONSerialization on Swift,
org.json.JSONObject/kotlinx.serialization on Kotlin) if you need the parsed shape.
Only the React Native binding pre-parses it into funnelAnswers.
Returned by: claimFunnelToken(), claimInstall(), resolveFunnelClaim(), claimFromClipboard(), claimFromUrl()