Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,33 @@ public void redeemWebPurchase(String urlString, final Promise promise) {
CommonKt.redeemWebPurchase(urlString, getOnResult(promise));
}

// region Ad Tracking

@ReactMethod
public void trackAdFailedToLoad(ReadableMap adData) {
CommonKt.trackAdFailedToLoad(adData.toHashMap());
}

@ReactMethod
public void trackAdLoaded(ReadableMap adData) {
CommonKt.trackAdLoaded(adData.toHashMap());
}

@ReactMethod
public void trackAdDisplayed(ReadableMap adData) {
CommonKt.trackAdDisplayed(adData.toHashMap());
}

@ReactMethod
public void trackAdOpened(ReadableMap adData) {
CommonKt.trackAdOpened(adData.toHashMap());
}

@ReactMethod
public void trackAdRevenue(ReadableMap adData) {
CommonKt.trackAdRevenue(adData.toHashMap());
}

// endregion

//================================================================================
Expand Down
22 changes: 22 additions & 0 deletions ios/RNPurchases.m
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,28 @@ static void logUnavailablePresentCodeRedemptionSheet() {
}


#pragma mark - Ad Tracking

RCT_EXPORT_METHOD(trackAdFailedToLoad:(NSDictionary *)adData) {
[RCCommonFunctionality trackAdFailedToLoad:adData.mappingNSNullToNil];
}

RCT_EXPORT_METHOD(trackAdLoaded:(NSDictionary *)adData) {
[RCCommonFunctionality trackAdLoaded:adData.mappingNSNullToNil];
}

RCT_EXPORT_METHOD(trackAdDisplayed:(NSDictionary *)adData) {
[RCCommonFunctionality trackAdDisplayed:adData.mappingNSNullToNil];
}

RCT_EXPORT_METHOD(trackAdOpened:(NSDictionary *)adData) {
[RCCommonFunctionality trackAdOpened:adData.mappingNSNullToNil];
}

RCT_EXPORT_METHOD(trackAdRevenue:(NSDictionary *)adData) {
[RCCommonFunctionality trackAdRevenue:adData.mappingNSNullToNil];
}

#pragma mark - Subscriber Attributes

RCT_EXPORT_METHOD(setProxyURLString:(nullable NSString *)proxyURLString
Expand Down
132 changes: 132 additions & 0 deletions src/ads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Ad tracking types for reporting ad events to RevenueCat.
*
* These types map to the native SDK's ad tracking API, enabling comprehensive
* LTV tracking across subscriptions and ad monetization.
*/

/**
* Predefined ad mediator names. You can also use custom string values.
*/
export const AD_MEDIATOR_NAME = {
AD_MOB: "AdMob",
APP_LOVIN: "AppLovin",
} as const;

/**
* Predefined ad format types.
*/
export const AD_FORMAT = {
OTHER: "other",
BANNER: "banner",
INTERSTITIAL: "interstitial",
REWARDED: "rewarded",
REWARDED_INTERSTITIAL: "rewarded_interstitial",
NATIVE: "native",
APP_OPEN: "app_open",
MREC: "mrec",
} as const;

/**
* Revenue precision levels for ad revenue reporting.
*/
export const AD_REVENUE_PRECISION = {
EXACT: "exact",
PUBLISHER_DEFINED: "publisher_defined",
ESTIMATED: "estimated",
UNKNOWN: "unknown",
} as const;

/**
* Data for tracking a failed ad load event.
*/
export interface AdFailedToLoadData {
/** The mediation SDK name (e.g. "AdMob", "AppLovin") */
mediatorName: string;
/** The ad format (e.g. "banner", "interstitial") */
adFormat: string;
/** The ad unit identifier */
adUnitId: string;
/** Optional placement identifier */
placement?: string | null;
/** Optional error code from the mediation SDK */
mediatorErrorCode?: number | null;
}

/**
* Data for tracking a successful ad load event.
*/
export interface AdLoadedData {
/** The ad network name */
networkName?: string | null;
/** The mediation SDK name */
mediatorName: string;
/** The ad format */
adFormat: string;
/** The ad unit identifier */
adUnitId: string;
/** Unique impression identifier */
impressionId: string;
/** Optional placement identifier */
placement?: string | null;
}

/**
* Data for tracking an ad display/impression event.
*/
export interface AdDisplayedData {
/** The ad network name */
networkName?: string | null;
/** The mediation SDK name */
mediatorName: string;
/** The ad format */
adFormat: string;
/** The ad unit identifier */
adUnitId: string;
/** Unique impression identifier */
impressionId: string;
/** Optional placement identifier */
placement?: string | null;
}

/**
* Data for tracking an ad opened/clicked event.
*/
export interface AdOpenedData {
/** The ad network name */
networkName?: string | null;
/** The mediation SDK name */
mediatorName: string;
/** The ad format */
adFormat: string;
/** The ad unit identifier */
adUnitId: string;
/** Unique impression identifier */
impressionId: string;
/** Optional placement identifier */
placement?: string | null;
}

/**
* Data for tracking ad revenue.
*/
export interface AdRevenueData {
/** The ad network name */
networkName?: string | null;
/** The mediation SDK name */
mediatorName: string;
/** The ad format */
adFormat: string;
/** The ad unit identifier */
adUnitId: string;
/** Unique impression identifier */
impressionId: string;
/** Optional placement identifier */
placement?: string | null;
/** Revenue in micro-units (e.g. 1500000 = $1.50) */
revenueMicros: number;
/** ISO 4217 currency code (e.g. "USD") */
currency: string;
/** Revenue accuracy level */
precision: string;
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './errors';
export * from './customerInfo';
export * from './purchases';
export * from './offerings';
export * from './ads';
58 changes: 58 additions & 0 deletions src/purchases.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { NativeEventEmitter, NativeModules } from "react-native";
import type {
AdFailedToLoadData,
AdLoadedData,
AdDisplayedData,
AdOpenedData,
AdRevenueData,
} from "./ads";
import {
PurchasesError,
PURCHASES_ERROR_CODE,
Expand Down Expand Up @@ -1551,6 +1558,57 @@ export default class Purchases {
RNPurchases.overridePreferredLocale(locale);
}

// region Ad Tracking

/**
* Tracks when an ad fails to load. Call this from your ad SDK's failure callback.
* @param {AdFailedToLoadData} data The failed-to-load event data
*/
public static async trackAdFailedToLoad(
data: AdFailedToLoadData
): Promise<void> {
await Purchases.throwIfNotConfigured();
RNPurchases.trackAdFailedToLoad(data);
}

/**
* Tracks when an ad successfully loads.
* @param {AdLoadedData} data The ad loaded event data
*/
public static async trackAdLoaded(data: AdLoadedData): Promise<void> {
await Purchases.throwIfNotConfigured();
RNPurchases.trackAdLoaded(data);
}

/**
* Tracks when an ad is displayed (impression).
* @param {AdDisplayedData} data The ad displayed event data
*/
public static async trackAdDisplayed(data: AdDisplayedData): Promise<void> {
await Purchases.throwIfNotConfigured();
RNPurchases.trackAdDisplayed(data);
}

/**
* Tracks when an ad is opened/clicked.
* @param {AdOpenedData} data The ad opened event data
*/
public static async trackAdOpened(data: AdOpenedData): Promise<void> {
await Purchases.throwIfNotConfigured();
RNPurchases.trackAdOpened(data);
}

/**
* Tracks ad revenue. Use this to report ad revenue alongside subscription data.
* @param {AdRevenueData} data The ad revenue event data
*/
public static async trackAdRevenue(data: AdRevenueData): Promise<void> {
await Purchases.throwIfNotConfigured();
RNPurchases.trackAdRevenue(data);
}

// endregion

/**
* Check if billing is supported for the current user (meaning IN-APP purchases are supported)
* and optionally, whether a list of specified feature types are supported.
Expand Down