From 298173205c4884bfa53a217ff65c42ec6c0b7ce4 Mon Sep 17 00:00:00 2001 From: Peter Porfy Date: Tue, 4 Aug 2026 17:08:30 +0200 Subject: [PATCH 1/4] feat(ads): ad reward tracking support --- .../purchases/react/RNPurchasesModule.java | 7 ++++-- ios/RNPurchases.m | 6 +++-- src/browser/nativeModule.ts | 2 +- src/purchases.ts | 25 +++++++++++++++++-- 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java b/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java index d458d8271..0d24977d0 100644 --- a/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java +++ b/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java @@ -666,8 +666,11 @@ public void generateRewardVerificationToken(String impressionId, final Promise p } @ReactMethod - public void pollRewardVerification(String clientTransactionId, final Promise promise) { - CommonKt.pollRewardVerification(clientTransactionId, getOnResult(promise)); + public void pollRewardVerification(String clientTransactionId, + @Nullable ReadableMap trackingMetadata, + final Promise promise) { + Map mapTrackingMetadata = trackingMetadata != null ? trackingMetadata.toHashMap() : null; + CommonKt.pollRewardVerification(clientTransactionId, getOnResult(promise), mapTrackingMetadata); } // endregion diff --git a/ios/RNPurchases.m b/ios/RNPurchases.m index 66e0567dd..3e1e8c1dd 100644 --- a/ios/RNPurchases.m +++ b/ios/RNPurchases.m @@ -714,9 +714,11 @@ static void logUnavailablePresentCodeRedemptionSheet() { } RCT_EXPORT_METHOD(pollRewardVerification:(NSString *)clientTransactionId - resolve:(RCTPromiseResolveBlock)resolve - reject:(RCTPromiseRejectBlock)reject) { + trackingMetadata:(nullable NSDictionary *)trackingMetadata + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) { [RCCommonFunctionality pollRewardVerificationWithClientTransactionId:clientTransactionId + trackingMetadata:trackingMetadata completion:[self getResponseCompletionBlockWithResolve:resolve reject:reject]]; } diff --git a/src/browser/nativeModule.ts b/src/browser/nativeModule.ts index c865ae00b..4b6065609 100644 --- a/src/browser/nativeModule.ts +++ b/src/browser/nativeModule.ts @@ -366,7 +366,7 @@ export const browserNativeModuleRNPurchases = { generateRewardVerificationToken: async (_impressionId: string) => { methodNotSupportedOnWeb('generateRewardVerificationToken'); }, - pollRewardVerification: async (_clientTransactionId: string) => { + pollRewardVerification: async (_clientTransactionId: string, _trackingMetadata?: any) => { methodNotSupportedOnWeb('pollRewardVerification'); }, }; diff --git a/src/purchases.ts b/src/purchases.ts index 601492546..37f24262b 100644 --- a/src/purchases.ts +++ b/src/purchases.ts @@ -340,6 +340,20 @@ export interface RewardVerificationResult { failed: boolean; } +/** + * Ad metadata for a rewarded ad, passed to {@link Purchases.pollRewardVerification} + * to have the SDK automatically track reward-verification events for it. + * @beta + */ +export interface RewardedAdTrackingMetadata { + mediatorName: AdMediatorName; + adFormat: AdFormat; + adUnitId: string; + impressionId: string; + networkName?: string | null; + placement?: string | null; +} + let debugEventListeners: DebugEventListener[] = []; eventEmitter?.addListener( @@ -2167,14 +2181,21 @@ export default class Purchases { * * @param clientTransactionId - The `clientTransactionId` from * {@link Purchases.generateRewardVerificationToken}. + * @param trackingMetadata - Pass to have the SDK automatically track + * reward-verification events for the ad it belongs to; omit to poll + * without tracking. * @returns {Promise} promise with the verification result. * @beta */ public static async pollRewardVerification( - clientTransactionId: string + clientTransactionId: string, + trackingMetadata?: RewardedAdTrackingMetadata ): Promise { await throwIfNotConfigured(); - return RNPurchases.pollRewardVerification(clientTransactionId); + return RNPurchases.pollRewardVerification( + clientTransactionId, + trackingMetadata + ); } private static async throwIfAndroidPlatform() { From 9991b3668dfb12366c9762c017436525f6a17587 Mon Sep 17 00:00:00 2001 From: Peter Porfy Date: Tue, 4 Aug 2026 17:50:01 +0200 Subject: [PATCH 2/4] chore(ads): api tests --- apitesters/rewardVerification.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/apitesters/rewardVerification.ts b/apitesters/rewardVerification.ts index 7a865ef7e..a5a4dd5aa 100644 --- a/apitesters/rewardVerification.ts +++ b/apitesters/rewardVerification.ts @@ -1,11 +1,14 @@ import Purchases, { RewardVerificationToken, RewardVerificationResult, + RewardedAdTrackingMetadata, VerifiedReward, VerifiedVirtualCurrencyReward, VerifiedEntitlementReward, VerifiedNoReward, VerifiedUnsupportedReward, + AdMediatorName, + AdFormat, } from "../src"; async function checkGenerateRewardVerificationToken() { @@ -24,6 +27,22 @@ async function checkPollRewardVerification() { const _moreRewards: VerifiedReward[] = result.moreRewards; } +async function checkPollRewardVerificationWithTrackingMetadata() { + const trackingMetadata: RewardedAdTrackingMetadata = { + mediatorName: AdMediatorName.adMob, + adFormat: AdFormat.rewarded, + adUnitId: "unit-1", + impressionId: "imp-1", + networkName: "AdNetwork", + placement: "home", + }; + const result: RewardVerificationResult = + await Purchases.pollRewardVerification( + "client-transaction-id", + trackingMetadata + ); +} + function checkVerifiedRewardTypes(reward: VerifiedReward) { switch (reward.type) { case "virtual_currency": { From b4647de5ebf9ad5f30f9a77ac2fab66694af3edd Mon Sep 17 00:00:00 2001 From: Peter Porfy Date: Tue, 18 Aug 2026 11:30:09 +0200 Subject: [PATCH 3/4] refactor(ads): change any to specific type --- src/browser/nativeModule.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/nativeModule.ts b/src/browser/nativeModule.ts index 4b6065609..2649447f5 100644 --- a/src/browser/nativeModule.ts +++ b/src/browser/nativeModule.ts @@ -7,6 +7,7 @@ import { validateAndTransform, isCustomerInfo, isPurchasesOfferings, isPurchases import { isExpoGo, isRorkSandbox } from '../utils/environment'; import { ensurePurchasesConfigured, methodNotSupportedOnWeb } from './utils'; import { purchaseSimulatedPackage } from './simulatedstore/purchaseSimulatedPackageHelper'; +import type { RewardedAdTrackingMetadata } from '../purchases'; const packageVersion = '9.1.0'; @@ -366,7 +367,7 @@ export const browserNativeModuleRNPurchases = { generateRewardVerificationToken: async (_impressionId: string) => { methodNotSupportedOnWeb('generateRewardVerificationToken'); }, - pollRewardVerification: async (_clientTransactionId: string, _trackingMetadata?: any) => { + pollRewardVerification: async (_clientTransactionId: string, _trackingMetadata?: RewardedAdTrackingMetadata) => { methodNotSupportedOnWeb('pollRewardVerification'); }, }; From 43081577a969d2aac7e9e8c0f0bcb80f2ee335ff Mon Sep 17 00:00:00 2001 From: Peter Porfy Date: Tue, 18 Aug 2026 11:41:09 +0200 Subject: [PATCH 4/4] fix(ads): map NSNull to nil for pollRewardVerification trackingMetadata --- ios/RNPurchases.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RNPurchases.m b/ios/RNPurchases.m index 3e1e8c1dd..5d8276736 100644 --- a/ios/RNPurchases.m +++ b/ios/RNPurchases.m @@ -718,7 +718,7 @@ static void logUnavailablePresentCodeRedemptionSheet() { resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { [RCCommonFunctionality pollRewardVerificationWithClientTransactionId:clientTransactionId - trackingMetadata:trackingMetadata + trackingMetadata:trackingMetadata.mappingNSNullToNil completion:[self getResponseCompletionBlockWithResolve:resolve reject:reject]]; }