By the end of this lesson, you will be able to:
ℹ️ Info Definition: Monetization transforms user engagement into sustainable revenue through advertisements, in-app purchases, subscriptions, and premium features. The key is balancing user experience with business needs to create long-term value for both users and developers.
Successful monetization can make or break an app business:
Model | Revenue Potential | User Friction | Best For | Examples |
---|---|---|---|---|
Freemium + IAP | Very High | Medium | Games, Productivity | Clash of Clans, Spotify |
Subscription | High | Low | Services, Content | Netflix, Adobe |
Ads Only | Medium | Very Low | Content, Social | TikTok, Instagram |
Paid Download | Low | High | Premium Tools | Procreate, Dark Sky |
Hybrid | Very High | Medium | Complex Apps | Duolingo, Reddit |
💡 Monetization Insight: The most successful apps combine multiple monetization strategies, with 73% of top-grossing apps using hybrid approaches!
// services/InAppPurchaseService.ts
import * as InAppPurchases from 'expo-in-app-purchases';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
export interface ProductInfo {
productId: string;
title: string;
description: string;
price: string;
priceAmountMicros: number;
priceCurrencyCode: string;
type: 'consumable' | 'non-consumable' | 'subscription';
}
export interface PurchaseResult {
success: boolean;
productId?: string;
transactionId?: string;
receipt?: string;
error?: string;
}
export interface SubscriptionInfo {
productId: string;
purchaseDate: Date;
expirationDate: Date;
isActive: boolean;
autoRenewing: boolean;
originalTransactionId: string;
}
class InAppPurchaseService {
private static instance: InAppPurchaseService;
private isInitialized = false;
private availableProducts: ProductInfo[] = [];
private activePurchases: Map<string, any> = new Map();
// Product IDs configuration
private productIds = {
// Consumables
gems_100: 'com.yourapp.gems_100',
gems_500: 'com.yourapp.gems_500',
gems_1000: 'com.yourapp.gems_1000',
// Non-consumables
premium_features: 'com.yourapp.premium_features',
ad_removal: 'com.yourapp.remove_ads',
// Subscriptions
monthly_premium: 'com.yourapp.premium_monthly',
yearly_premium: 'com.yourapp.premium_yearly',
weekly_trial: 'com.yourapp.premium_weekly_trial',
};
static getInstance(): InAppPurchaseService {
if (!InAppPurchaseService.instance) {
InAppPurchaseService.instance = new InAppPurchaseService();
}
return InAppPurchaseService.instance;
}
async initialize(): Promise<boolean> {
try {
// Check if IAP is available
const isAvailable = await InAppPurchases.isAvailableAsync();
if (!isAvailable) {
console.warn('In-app purchases not available on this device');
return false;
}
// Connect to the store
await InAppPurchases.connectAsync();
// Load available products
await this.loadProducts();
// Set up purchase listener
InAppPurchases.setPurchaseListener(this.handlePurchaseUpdate.bind(this));
// Restore previous purchases
await this.restorePurchases();
this.isInitialized = true;
console.log('IAP Service initialized successfully');
return true;
} catch (error) {
console.error('IAP initialization error:', error);
return false;
}
}
private async loadProducts(): Promise<void> {
try {
const productIds = Object.values(this.productIds);
const { responseCode, results } = await InAppPurchases.getProductsAsync(productIds);
if (responseCode === InAppPurchases.IAPResponseCode.OK) {
this.availableProducts = results?.map(product => ({
productId: product.productId,
title: product.title,
description: product.description,
price: product.price,
priceAmountMicros: product.priceAmountMicros,
priceCurrencyCode: product.priceCurrencyCode,
type: this.getProductType(product.productId),
})) || [];
console.log(`Loaded ${this.availableProducts.length} products`);
} else {
console.error('Failed to load products:', responseCode);
}
} catch (error) {
console.error('Error loading products:', error);
}
}
private getProductType(productId: string): 'consumable' | 'non-consumable' | 'subscription' {
if (productId.includes('gems')) return 'consumable';
if (productId.includes('premium') || productId.includes('trial')) return 'subscription';
return 'non-consumable';
}
getAvailableProducts(): ProductInfo[] {
return this.availableProducts;
}
getProductById(productId: string): ProductInfo | undefined {
return this.availableProducts.find(p => p.productId === productId);
}
async purchaseProduct(productId: string): Promise<PurchaseResult> {
if (!this.isInitialized) {
return { success: false, error: 'IAP service not initialized' };
}
try {
const product = this.getProductById(productId);
if (!product) {
return { success: false, error: 'Product not found' };
}
console.log(`Purchasing product: ${productId}`);
const { responseCode, results } = await InAppPurchases.purchaseItemAsync(productId);
if (responseCode === InAppPurchases.IAPResponseCode.OK && results && results.length > 0) {
const purchase = results[0];
// Verify purchase with your backend
const isValid = await this.verifyPurchase(purchase);
if (!isValid) {
return { success: false, error: 'Purchase verification failed' };
}
// Process the purchase
await this.processPurchase(purchase);
return {
success: true,
productId: purchase.productId,
transactionId: purchase.transactionId,
receipt: purchase.transactionReceipt,
};
} else if (responseCode === InAppPurchases.IAPResponseCode.USER_CANCELED) {
return { success: false, error: 'Purchase canceled by user' };
} else {
return { success: false, error: `Purchase failed: ${responseCode}` };
}
} catch (error) {
console.error('Purchase error:', error);
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
private async handlePurchaseUpdate(purchaseUpdate: any): Promise<void> {
console.log('Purchase update received:', purchaseUpdate);
if (purchaseUpdate.responseCode === InAppPurchases.IAPResponseCode.OK) {
for (const purchase of purchaseUpdate.results || []) {
await this.processPurchase(purchase);
}
}
}
private async verifyPurchase(purchase: any): Promise<boolean> {
try {
// Send receipt to your backend for verification
const response = await fetch('https://your-api.com/verify-purchase', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
receipt: purchase.transactionReceipt,
productId: purchase.productId,
transactionId: purchase.transactionId,
platform: Platform.OS,
}),
});
const result = await response.json();
return result.valid === true;
} catch (error) {
console.error('Purchase verification error:', error);
return false;
}
}
private async processPurchase(purchase: any): Promise<void> {
const { productId, transactionId } = purchase;
try {
// Store purchase locally
this.activePurchases.set(productId, purchase);
// Apply purchase benefits
switch (productId) {
case this.productIds.gems_100:
await this.addGems(100);
break;
case this.productIds.gems_500:
await this.addGems(500);
break;
case this.productIds.gems_1000:
await this.addGems(1000);
break;
case this.productIds.premium_features:
await this.unlockPremiumFeatures();
break;
case this.productIds.ad_removal:
await this.removeAds();
break;
case this.productIds.monthly_premium:
case this.productIds.yearly_premium:
case this.productIds.weekly_trial:
await this.activateSubscription(productId, purchase);
break;
}
// Finish the transaction
await InAppPurchases.finishTransactionAsync(purchase, true);
console.log(`Purchase processed successfully: ${productId}`);
} catch (error) {
console.error('Error processing purchase:', error);
await InAppPurchases.finishTransactionAsync(purchase, false);
}
}
private async addGems(amount: number): Promise<void> {
try {
const currentGems = await AsyncStorage.getItem('user_gems');
const gems = currentGems ? parseInt(currentGems) : 0;
await AsyncStorage.setItem('user_gems', (gems + amount).toString());
console.log(`Added ${amount} gems. Total: ${gems + amount}`);
} catch (error) {
console.error('Error adding gems:', error);
}
}
private async unlockPremiumFeatures(): Promise<void> {
try {
await AsyncStorage.setItem('premium_features', 'true');
console.log('Premium features unlocked');
} catch (error) {
console.error('Error unlocking premium features:', error);
}
}
private async removeAds(): Promise<void> {
try {
await AsyncStorage.setItem('ads_removed', 'true');
console.log('Ads removed');
} catch (error) {
console.error('Error removing ads:', error);
}
}
private async activateSubscription(productId: string, purchase: any): Promise<void> {
try {
const subscriptionInfo: SubscriptionInfo = {
productId,
purchaseDate: new Date(purchase.purchaseTime),
expirationDate: new Date(purchase.expirationTime || Date.now() + 30 * 24 * 60 * 60 * 1000),
isActive: true,
autoRenewing: purchase.autoRenewing || false,
originalTransactionId: purchase.originalTransactionId || purchase.transactionId,
};
await AsyncStorage.setItem('subscription_info', JSON.stringify(subscriptionInfo));
await AsyncStorage.setItem('premium_subscription', 'true');
console.log('Subscription activated:', productId);
} catch (error) {
console.error('Error activating subscription:', error);
}
}
async restorePurchases(): Promise<PurchaseResult[]> {
if (!this.isInitialized) {
return [{ success: false, error: 'IAP service not initialized' }];
}
try {
const { responseCode, results } = await InAppPurchases.getPurchaseHistoryAsync();
if (responseCode === InAppPurchases.IAPResponseCode.OK && results) {
const restoredPurchases: PurchaseResult[] = [];
for (const purchase of results) {
await this.processPurchase(purchase);
restoredPurchases.push({
success: true,
productId: purchase.productId,
transactionId: purchase.transactionId,
});
}
console.log(`Restored ${restoredPurchases.length} purchases`);
return restoredPurchases;
} else {
return [{ success: false, error: `Restore failed: ${responseCode}` }];
}
} catch (error) {
console.error('Restore purchases error:', error);
return [{ success: false, error: error instanceof Error ? error.message : 'Unknown error' }];
}
}
async getActiveSubscriptions(): Promise<SubscriptionInfo[]> {
try {
const storedInfo = await AsyncStorage.getItem('subscription_info');
if (storedInfo) {
const subscription = JSON.parse(storedInfo);
// Check if subscription is still active
if (new Date(subscription.expirationDate) > new Date()) {
return [subscription];
}
}
return [];
} catch (error) {
console.error('Error getting active subscriptions:', error);
return [];
}
}
async hasActivePremium(): Promise<boolean> {
try {
const subscriptions = await this.getActiveSubscriptions();
if (subscriptions.length > 0) return true;
const premiumFeatures = await AsyncStorage.getItem('premium_features');
return premiumFeatures === 'true';
} catch (error) {
console.error('Error checking premium status:', error);
return false;
}
}
async hasRemovedAds(): Promise<boolean> {
try {
const adsRemoved = await AsyncStorage.getItem('ads_removed');
return adsRemoved === 'true' || await this.hasActivePremium();
} catch (error) {
console.error('Error checking ad removal status:', error);
return false;
}
}
async disconnectAsync(): Promise<void> {
try {
await InAppPurchases.disconnectAsync();
this.isInitialized = false;
console.log('IAP service disconnected');
} catch (error) {
console.error('Error disconnecting IAP service:', error);
}
}
}
export default InAppPurchaseService;
// components/SubscriptionManager.tsx
import React, { useState, useEffect } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
StyleSheet,
Alert,
ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import InAppPurchaseService, { ProductInfo, SubscriptionInfo } from '../services/InAppPurchaseService';
interface SubscriptionManagerProps {
onSubscriptionChange?: (hasSubscription: boolean) => void;
}
export const SubscriptionManager: React.FC<SubscriptionManagerProps> = ({
onSubscriptionChange,
}) => {
const [products, setProducts] = useState<ProductInfo[]>([]);
const [activeSubscription, setActiveSubscription] = useState<SubscriptionInfo | null>(null);
const [loading, setLoading] = useState(true);
const [purchasing, setPurchasing] = useState<string | null>(null);
const iapService = InAppPurchaseService.getInstance();
useEffect(() => {
initializeSubscriptions();
}, []);
const initializeSubscriptions = async () => {
try {
await iapService.initialize();
// Load subscription products
const allProducts = iapService.getAvailableProducts();
const subscriptionProducts = allProducts.filter(p => p.type === 'subscription');
setProducts(subscriptionProducts);
// Check for active subscriptions
const activeSubscriptions = await iapService.getActiveSubscriptions();
if (activeSubscriptions.length > 0) {
setActiveSubscription(activeSubscriptions[0]);
onSubscriptionChange?.(true);
}
} catch (error) {
console.error('Error initializing subscriptions:', error);
Alert.alert('Error', 'Failed to load subscription information');
} finally {
setLoading(false);
}
};
const handlePurchase = async (productId: string) => {
setPurchasing(productId);
try {
const result = await iapService.purchaseProduct(productId);
if (result.success) {
Alert.alert(
'Success!',
'Your subscription has been activated.',
[{ text: 'OK', onPress: initializeSubscriptions }]
);
} else {
Alert.alert('Purchase Failed', result.error || 'Unknown error occurred');
}
} catch (error) {
console.error('Purchase error:', error);
Alert.alert('Error', 'Failed to process purchase');
} finally {
setPurchasing(null);
}
};
const handleRestore = async () => {
setLoading(true);
try {
const results = await iapService.restorePurchases();
const successfulRestores = results.filter(r => r.success);
if (successfulRestores.length > 0) {
Alert.alert(
'Restored!',
`Successfully restored ${successfulRestores.length} purchase(s).`,
[{ text: 'OK', onPress: initializeSubscriptions }]
);
} else {
Alert.alert('No Purchases', 'No previous purchases found to restore.');
}
} catch (error) {
console.error('Restore error:', error);
Alert.alert('Error', 'Failed to restore purchases');
} finally {
setLoading(false);
}
};
const getSubscriptionPeriod = (productId: string) => {
if (productId.includes('weekly')) return '1 Week';
if (productId.includes('monthly')) return '1 Month';
if (productId.includes('yearly')) return '1 Year';
return 'Unknown';
};
const getSubscriptionBenefits = () => [
'Remove all advertisements',
'Unlock premium features',
'Priority customer support',
'Exclusive content access',
'Advanced analytics',
'Export capabilities',
'Cloud sync across devices',
'Early access to new features',
];
const SubscriptionCard = ({ product, isActive = false }: { product: ProductInfo; isActive?: boolean }) => {
const period = getSubscriptionPeriod(product.productId);
const isPopular = product.productId.includes('yearly');
const isPurchasingThis = purchasing === product.productId;
return (
<View style={[styles.subscriptionCard, isActive && styles.activeCard]}>
{isPopular && (
<View style={styles.popularBadge}>
<Text style={styles.popularText}>Most Popular</Text>
</View>
)}
<View style={styles.cardHeader}>
<Text style={styles.subscriptionTitle}>{period} Premium</Text>
{isActive && (
<View style={styles.activeBadge}>
<Ionicons name="checkmark-circle" size={20} color="#34C759" />
<Text style={styles.activeText}>Active</Text>
</View>
)}
</View>
<View style={styles.priceContainer}>
<Text style={styles.price}>{product.price}</Text>
<Text style={styles.period}>/{period.toLowerCase()}</Text>
</View>
<Text style={styles.description}>{product.description}</Text>
{!isActive && (
<TouchableOpacity
style={[
styles.subscribeButton,
isPurchasingThis && styles.disabledButton,
isPopular && styles.popularButton,
]}
onPress={() => handlePurchase(product.productId)}
disabled={isPurchasingThis}
>
{isPurchasingThis ? (
<ActivityIndicator size="small" color="white" />
) : (
<>
<Text style={[styles.subscribeText, isPopular && styles.popularButtonText]}>
Subscribe Now
</Text>
<Ionicons name="arrow-forward" size={20} color="white" />
</>
)}
</TouchableOpacity>
)}
</View>
);
};
if (loading) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#007AFF" />
<Text style={styles.loadingText}>Loading subscriptions...</Text>
</View>
);
}
return (
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
<LinearGradient
colors={['#667eea', '#764ba2']}
style={styles.headerGradient}
>
<Text style={styles.headerTitle}>Premium Subscription</Text>
<Text style={styles.headerSubtitle}>
Unlock the full potential of the app
</Text>
</LinearGradient>
{/* Benefits Section */}
<View style={styles.benefitsContainer}>
<Text style={styles.sectionTitle}>What's Included</Text>
{getSubscriptionBenefits().map((benefit, index) => (
<View key={index} style={styles.benefitItem}>
<Ionicons name="checkmark-circle" size={20} color="#34C759" />
<Text style={styles.benefitText}>{benefit}</Text>
</View>
))}
</View>
{/* Subscription Options */}
<View style={styles.subscriptionsContainer}>
<Text style={styles.sectionTitle}>Choose Your Plan</Text>
{products.map(product => (
<SubscriptionCard
key={product.productId}
product={product}
isActive={activeSubscription?.productId === product.productId}
/>
))}
</View>
{/* Current Subscription Info */}
{activeSubscription && (
<View style={styles.currentSubscriptionContainer}>
<Text style={styles.sectionTitle}>Current Subscription</Text>
<View style={styles.subscriptionInfo}>
<Text style={styles.subscriptionInfoText}>
Plan: {getSubscriptionPeriod(activeSubscription.productId)} Premium
</Text>
<Text style={styles.subscriptionInfoText}>
Started: {activeSubscription.purchaseDate.toLocaleDateString()}
</Text>
<Text style={styles.subscriptionInfoText}>
Expires: {activeSubscription.expirationDate.toLocaleDateString()}
</Text>
<Text style={styles.subscriptionInfoText}>
Auto-renewing: {activeSubscription.autoRenewing ? 'Yes' : 'No'}
</Text>
</View>
</View>
)}
{/* Restore Purchases Button */}
<TouchableOpacity style={styles.restoreButton} onPress={handleRestore}>
<Ionicons name="refresh-outline" size={20} color="#007AFF" />
<Text style={styles.restoreText}>Restore Purchases</Text>
</TouchableOpacity>
{/* Terms and Privacy */}
<View style={styles.legalContainer}>
<Text style={styles.legalText}>
By subscribing, you agree to our Terms of Service and Privacy Policy.
Subscriptions auto-renew unless turned off 24 hours before the period ends.
</Text>
</View>
</ScrollView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#FFFFFF',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
marginTop: 16,
fontSize: 16,
color: '#8E8E93',
},
headerGradient: {
padding: 40,
alignItems: 'center',
},
headerTitle: {
fontSize: 28,
fontWeight: 'bold',
color: 'white',
marginBottom: 8,
},
headerSubtitle: {
fontSize: 16,
color: 'rgba(255, 255, 255, 0.9)',
textAlign: 'center',
},
benefitsContainer: {
padding: 20,
},
sectionTitle: {
fontSize: 22,
fontWeight: 'bold',
marginBottom: 16,
color: '#000000',
},
benefitItem: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
benefitText: {
fontSize: 16,
color: '#000000',
marginLeft: 12,
flex: 1,
},
subscriptionsContainer: {
padding: 20,
},
subscriptionCard: {
backgroundColor: '#F8F9FA',
borderRadius: 16,
padding: 20,
marginBottom: 16,
borderWidth: 2,
borderColor: 'transparent',
position: 'relative',
},
activeCard: {
borderColor: '#34C759',
backgroundColor: '#F0FFF4',
},
popularBadge: {
position: 'absolute',
top: -8,
left: 20,
backgroundColor: '#FF6B35',
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12,
},
popularText: {
color: 'white',
fontSize: 12,
fontWeight: 'bold',
},
cardHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
subscriptionTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#000000',
},
activeBadge: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
activeText: {
color: '#34C759',
fontWeight: '600',
},
priceContainer: {
flexDirection: 'row',
alignItems: 'baseline',
marginBottom: 8,
},
price: {
fontSize: 32,
fontWeight: 'bold',
color: '#007AFF',
},
period: {
fontSize: 16,
color: '#8E8E93',
marginLeft: 4,
},
description: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 20,
lineHeight: 20,
},
subscribeButton: {
backgroundColor: '#007AFF',
borderRadius: 12,
paddingVertical: 16,
paddingHorizontal: 24,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
gap: 8,
},
popularButton: {
backgroundColor: '#FF6B35',
},
disabledButton: {
opacity: 0.6,
},
subscribeText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
popularButtonText: {
color: 'white',
},
currentSubscriptionContainer: {
padding: 20,
},
subscriptionInfo: {
backgroundColor: '#F8F9FA',
borderRadius: 12,
padding: 16,
},
subscriptionInfoText: {
fontSize: 16,
color: '#000000',
marginBottom: 8,
},
restoreButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 16,
marginHorizontal: 20,
marginBottom: 20,
borderRadius: 12,
borderWidth: 1,
borderColor: '#007AFF',
gap: 8,
},
restoreText: {
color: '#007AFF',
fontSize: 16,
fontWeight: '600',
},
legalContainer: {
padding: 20,
paddingTop: 0,
},
legalText: {
fontSize: 12,
color: '#8E8E93',
lineHeight: 16,
textAlign: 'center',
},
});
export default SubscriptionManager;
// services/AdService.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AdConfig {
bannerAdUnitId: string;
interstitialAdUnitId: string;
rewardedAdUnitId: string;
nativeAdUnitId: string;
testMode: boolean;
}
interface AdMetrics {
impressions: number;
clicks: number;
revenue: number;
fillRate: number;
ecpm: number;
}
interface UserAdProfile {
userId: string;
adInteractions: number;
lastAdClick: Date | null;
preferredAdTypes: string[];
adOptOut: boolean;
rewardedAdWatches: number;
estimatedValue: number; // User value for ad targeting
}
class SmartAdService {
private static instance: SmartAdService;
private adConfig: AdConfig;
private userProfile: UserAdProfile | null = null;
private adMetrics: AdMetrics;
private adBlockDetected = false;
private hasRemovedAds = false;
constructor() {
this.adConfig = {
bannerAdUnitId: __DEV__ ? 'ca-app-pub-3940256099942544/6300978111' : 'your-banner-ad-unit',
interstitialAdUnitId: __DEV__ ? 'ca-app-pub-3940256099942544/1033173712' : 'your-interstitial-ad-unit',
rewardedAdUnitId: __DEV__ ? 'ca-app-pub-3940256099942544/5224354917' : 'your-rewarded-ad-unit',
nativeAdUnitId: __DEV__ ? 'ca-app-pub-3940256099942544/2247696110' : 'your-native-ad-unit',
testMode: __DEV__,
};
this.adMetrics = {
impressions: 0,
clicks: 0,
revenue: 0,
fillRate: 0,
ecpm: 0,
};
}
static getInstance(): SmartAdService {
if (!SmartAdService.instance) {
SmartAdService.instance = new SmartAdService();
}
return SmartAdService.instance;
}
async initialize(userId: string): Promise<void> {
try {
// Check if user has removed ads
this.hasRemovedAds = await AsyncStorage.getItem('ads_removed') === 'true';
if (this.hasRemovedAds) {
console.log('Ads disabled - user has removed ads');
return;
}
// Load user ad profile
await this.loadUserProfile(userId);
// Initialize ad networks (AdMob, etc.)
// await mobileAds().initialize();
console.log('Ad service initialized');
} catch (error) {
console.error('Ad service initialization error:', error);
}
}
private async loadUserProfile(userId: string): Promise<void> {
try {
const profileData = await AsyncStorage.getItem(`ad_profile_${userId}`);
if (profileData) {
this.userProfile = JSON.parse(profileData);
} else {
this.userProfile = {
userId,
adInteractions: 0,
lastAdClick: null,
preferredAdTypes: [],
adOptOut: false,
rewardedAdWatches: 0,
estimatedValue: 0.5, // Default user value
};
}
} catch (error) {
console.error('Error loading user ad profile:', error);
}
}
private async saveUserProfile(): Promise<void> {
if (!this.userProfile) return;
try {
await AsyncStorage.setItem(
`ad_profile_${this.userProfile.userId}`,
JSON.stringify(this.userProfile)
);
} catch (error) {
console.error('Error saving user ad profile:', error);
}
}
shouldShowAd(adType: 'banner' | 'interstitial' | 'rewarded' | 'native'): boolean {
if (this.hasRemovedAds) return false;
if (this.userProfile?.adOptOut) return false;
if (this.adBlockDetected) return false;
// Intelligent ad frequency capping
const now = new Date();
const profile = this.userProfile;
if (!profile) return true;
switch (adType) {
case 'interstitial':
// Don't show interstitials too frequently
if (profile.lastAdClick) {
const timeSinceLastAd = now.getTime() - profile.lastAdClick.getTime();
if (timeSinceLastAd < 300000) return false; // 5 minutes
}
return profile.adInteractions < 10; // Cap at 10 per session
case 'rewarded':
return true; // Always allow rewarded ads (user initiated)
case 'banner':
return profile.estimatedValue > 0.3; // Only show to valuable users
case 'native':
return profile.adInteractions < 5; // Limit native ad impressions
default:
return true;
}
}
async showInterstitialAd(context: string): Promise<boolean> {
if (!this.shouldShowAd('interstitial')) {
console.log('Interstitial ad blocked by smart filtering');
return false;
}
try {
// Load and show interstitial ad
// const interstitial = InterstitialAd.createForAdRequest(this.adConfig.interstitialAdUnitId);
// await interstitial.load();
// await interstitial.show();
// Track ad interaction
await this.trackAdInteraction('interstitial', context);
console.log('Interstitial ad shown successfully');
return true;
} catch (error) {
console.error('Interstitial ad error:', error);
return false;
}
}
async showRewardedAd(rewardType: string, rewardAmount: number): Promise<{
watched: boolean;
rewarded: boolean;
rewardEarned?: any;
}> {
if (!this.shouldShowAd('rewarded')) {
return { watched: false, rewarded: false };
}
try {
// Load and show rewarded ad
// const rewarded = RewardedAd.createForAdRequest(this.adConfig.rewardedAdUnitId);
// await rewarded.load();
// const result = await rewarded.show();
if (true) { // result.rewarded in real implementation
// Grant reward
await this.grantAdReward(rewardType, rewardAmount);
// Track rewarded ad completion
await this.trackAdInteraction('rewarded', 'reward_earned');
if (this.userProfile) {
this.userProfile.rewardedAdWatches++;
await this.saveUserProfile();
}
return {
watched: true,
rewarded: true,
rewardEarned: { type: rewardType, amount: rewardAmount },
};
}
return { watched: true, rewarded: false };
} catch (error) {
console.error('Rewarded ad error:', error);
return { watched: false, rewarded: false };
}
}
private async grantAdReward(rewardType: string, amount: number): Promise<void> {
try {
switch (rewardType) {
case 'gems':
const currentGems = await AsyncStorage.getItem('user_gems');
const gems = currentGems ? parseInt(currentGems) : 0;
await AsyncStorage.setItem('user_gems', (gems + amount).toString());
break;
case 'coins':
const currentCoins = await AsyncStorage.getItem('user_coins');
const coins = currentCoins ? parseInt(currentCoins) : 0;
await AsyncStorage.setItem('user_coins', (coins + amount).toString());
break;
case 'lives':
const currentLives = await AsyncStorage.getItem('user_lives');
const lives = currentLives ? parseInt(currentLives) : 0;
await AsyncStorage.setItem('user_lives', (lives + amount).toString());
break;
}
console.log(`Granted ${amount} ${rewardType} for watching ad`);
} catch (error) {
console.error('Error granting ad reward:', error);
}
}
private async trackAdInteraction(adType: string, context: string): Promise<void> {
try {
if (this.userProfile) {
this.userProfile.adInteractions++;
this.userProfile.lastAdClick = new Date();
// Update user value based on interactions
this.userProfile.estimatedValue = Math.min(1.0,
this.userProfile.estimatedValue + 0.1
);
await this.saveUserProfile();
}
// Track metrics
this.adMetrics.impressions++;
// Send to analytics
// await AnalyticsService.getInstance().trackEvent('ad_interaction', {
// ad_type: adType,
// context,
// user_value: this.userProfile?.estimatedValue,
// });
console.log(`Ad interaction tracked: ${adType} in ${context}`);
} catch (error) {
console.error('Error tracking ad interaction:', error);
}
}
async optimizeAdPlacement(): Promise<{
recommendedPlacements: Array<{
location: string;
adType: string;
estimatedRevenue: number;
userExperienceImpact: 'low' | 'medium' | 'high';
}>;
}> {
// AI-powered ad placement optimization
const placements = [
{
location: 'after_level_complete',
adType: 'interstitial',
estimatedRevenue: 0.25,
userExperienceImpact: 'medium' as const,
},
{
location: 'main_menu',
adType: 'banner',
estimatedRevenue: 0.05,
userExperienceImpact: 'low' as const,
},
{
location: 'store_page',
adType: 'native',
estimatedRevenue: 0.15,
userExperienceImpact: 'low' as const,
},
{
location: 'continue_button',
adType: 'rewarded',
estimatedRevenue: 0.35,
userExperienceImpact: 'low' as const,
},
];
return { recommendedPlacements: placements };
}
async getAdMetrics(): Promise<AdMetrics & { userProfile: UserAdProfile | null }> {
return {
...this.adMetrics,
userProfile: this.userProfile,
};
}
async setAdOptOut(optOut: boolean): Promise<void> {
if (this.userProfile) {
this.userProfile.adOptOut = optOut;
await this.saveUserProfile();
}
}
async enableAds(): Promise<void> {
await AsyncStorage.removeItem('ads_removed');
this.hasRemovedAds = false;
console.log('Ads re-enabled');
}
}
export default SmartAdService;
// services/RevenueIntelligence.ts
import OpenAI from 'openai';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface MonetizationMetrics {
totalRevenue: number;
adRevenue: number;
iapRevenue: number;
subscriptionRevenue: number;
arpu: number; // Average Revenue Per User
arppu: number; // Average Revenue Per Paying User
conversionRate: number;
churnRate: number;
ltv: number; // Lifetime Value
}
interface PricingOptimization {
productId: string;
currentPrice: number;
recommendedPrice: number;
expectedImpact: {
revenueChange: number;
conversionChange: number;
userSatisfactionImpact: string;
};
reasoning: string;
}
interface MonetizationStrategy {
primary: 'freemium' | 'premium' | 'ad-supported' | 'subscription';
recommendations: string[];
expectedRevenueLift: number;
implementationPriority: Array<{
action: string;
impact: 'high' | 'medium' | 'low';
effort: 'high' | 'medium' | 'low';
roi: number;
}>;
}
class RevenueIntelligence {
private openai: OpenAI;
private metrics: MonetizationMetrics;
constructor(apiKey: string) {
this.openai = new OpenAI({ apiKey });
this.metrics = {
totalRevenue: 0,
adRevenue: 0,
iapRevenue: 0,
subscriptionRevenue: 0,
arpu: 0,
arppu: 0,
conversionRate: 0,
churnRate: 0,
ltv: 0,
};
}
async analyzeMonetizationPerformance(): Promise<MonetizationStrategy> {
try {
const revenueData = await this.collectRevenueData();
const userSegments = await this.analyzeUserSegments();
const prompt = `
Analyze app monetization performance and provide strategic recommendations:
Current Performance:
- Total Revenue: ${revenueData.totalRevenue}
- Ad Revenue: ${revenueData.adRevenue} (${((revenueData.adRevenue/revenueData.totalRevenue)*100).toFixed(1)}%)
- IAP Revenue: ${revenueData.iapRevenue} (${((revenueData.iapRevenue/revenueData.totalRevenue)*100).toFixed(1)}%)
- Subscription Revenue: ${revenueData.subscriptionRevenue} (${((revenueData.subscriptionRevenue/revenueData.totalRevenue)*100).toFixed(1)}%)
- ARPU: ${revenueData.arpu}
- Conversion Rate: ${(revenueData.conversionRate * 100).toFixed(2)}%
- Churn Rate: ${(revenueData.churnRate * 100).toFixed(2)}%
User Segments:
- Free Users: ${userSegments.freeUsers}%
- Premium Users: ${userSegments.premiumUsers}%
- Subscribers: ${userSegments.subscribers}%
- High Value Users: ${userSegments.highValueUsers}%
Provide:
1. Primary monetization strategy recommendation
2. Specific actionable recommendations
3. Expected revenue impact
4. Implementation priority matrix
Return as JSON matching MonetizationStrategy interface.
`;
const response = await this.openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: prompt }],
temperature: 0.3,
});
const strategy = JSON.parse(response.choices[0].message.content || '{}');
return strategy;
} catch (error) {
console.error('Monetization analysis error:', error);
return this.getFallbackStrategy();
}
}
async optimizePricing(productIds: string[]): Promise<PricingOptimization[]> {
const optimizations: PricingOptimization[] = [];
for (const productId of productIds) {
try {
const currentPrice = await this.getCurrentPrice(productId);
const performanceData = await this.getProductPerformance(productId);
const prompt = `
Optimize pricing for product: ${productId}
Current Performance:
- Price: ${currentPrice}
- Conversion Rate: ${performanceData.conversionRate}%
- Revenue: ${performanceData.revenue}
- User Feedback Score: ${performanceData.feedbackScore}/5
- Market Position: ${performanceData.marketPosition}
Analyze:
1. Price elasticity
2. Competitive positioning
3. User value perception
4. Revenue optimization opportunities
Recommend optimal price with impact analysis.
Return as JSON with recommendedPrice, expectedImpact, and reasoning.
`;
const response = await this.openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: prompt }],
temperature: 0.3,
});
const optimization = JSON.parse(response.choices[0].message.content || '{}');
optimizations.push({
productId,
currentPrice,
recommendedPrice: optimization.recommendedPrice || currentPrice,
expectedImpact: optimization.expectedImpact || {
revenueChange: 0,
conversionChange: 0,
userSatisfactionImpact: 'neutral',
},
reasoning: optimization.reasoning || 'Unable to generate pricing recommendation',
});
} catch (error) {
console.error(`Pricing optimization error for ${productId}:`, error);
optimizations.push({
productId,
currentPrice: await this.getCurrentPrice(productId),
recommendedPrice: await this.getCurrentPrice(productId),
expectedImpact: {
revenueChange: 0,
conversionChange: 0,
userSatisfactionImpact: 'neutral',
},
reasoning: 'Unable to analyze - maintain current pricing',
});
}
}
return optimizations;
}
private async collectRevenueData(): Promise<MonetizationMetrics> {
try {
// In a real app, this would query your analytics/revenue database
const storedMetrics = await AsyncStorage.getItem('revenue_metrics');
if (storedMetrics) {
this.metrics = JSON.parse(storedMetrics);
}
// Calculate derived metrics
this.metrics.arpu = this.metrics.totalRevenue / await this.getTotalUsers();
this.metrics.arppu = this.metrics.totalRevenue / await this.getPayingUsers();
return this.metrics;
} catch (error) {
console.error('Error collecting revenue data:', error);
return this.metrics;
}
}
private async analyzeUserSegments(): Promise<{
freeUsers: number;
premiumUsers: number;
subscribers: number;
highValueUsers: number;
}> {
// Mock data - in real app, query your user database
return {
freeUsers: 75,
premiumUsers: 15,
subscribers: 8,
highValueUsers: 2,
};
}
private async getCurrentPrice(productId: string): Promise<number> {
// Mock pricing data - in real app, fetch from IAP service
const mockPrices: Record<string, number> = {
'monthly_premium': 9.99,
'yearly_premium': 99.99,
'gems_100': 0.99,
'gems_500': 4.99,
'remove_ads': 2.99,
};
return mockPrices[productId] || 0;
}
private async getProductPerformance(productId: string): Promise<{
conversionRate: number;
revenue: number;
feedbackScore: number;
marketPosition: string;
}> {
// Mock performance data
return {
conversionRate: 3.5,
revenue: 1250,
feedbackScore: 4.2,
marketPosition: 'competitive',
};
}
private async getTotalUsers(): Promise<number> {
const userData = await AsyncStorage.getItem('total_users');
return userData ? parseInt(userData) : 1000;
}
private async getPayingUsers(): Promise<number> {
const userData = await AsyncStorage.getItem('paying_users');
return userData ? parseInt(userData) : 100;
}
private getFallbackStrategy(): MonetizationStrategy {
return {
primary: 'freemium',
recommendations: [
'Focus on improving user onboarding to increase conversion',
'Add more value to premium tier',
'Optimize ad placement for better user experience',
'Implement usage-based pricing for heavy users',
],
expectedRevenueLift: 15,
implementationPriority: [
{
action: 'Improve onboarding',
impact: 'high',
effort: 'medium',
roi: 3.5,
},
{
action: 'Add premium features',
impact: 'medium',
effort: 'high',
roi: 2.1,
},
{
action: 'Optimize ad placement',
impact: 'medium',
effort: 'low',
roi: 4.2,
},
],
};
}
async trackRevenueEvent(eventType: 'purchase' | 'subscription' | 'ad_revenue', amount: number, metadata?: Record<string, any>): Promise<void> {
try {
// Update metrics
this.metrics.totalRevenue += amount;
switch (eventType) {
case 'purchase':
this.metrics.iapRevenue += amount;
break;
case 'subscription':
this.metrics.subscriptionRevenue += amount;
break;
case 'ad_revenue':
this.metrics.adRevenue += amount;
break;
}
// Store updated metrics
await AsyncStorage.setItem('revenue_metrics', JSON.stringify(this.metrics));
console.log(`Revenue event tracked: ${eventType} - ${amount}`);
} catch (error) {
console.error('Error tracking revenue event:', error);
}
}
async generateRevenueReport(): Promise<{
summary: MonetizationMetrics;
trends: Array<{ date: string; revenue: number; source: string }>;
insights: string[];
recommendations: string[];
}> {
const trends = await this.getRevenueTrends();
const insights = await this.generateInsights();
return {
summary: this.metrics,
trends,
insights,
recommendations: [
'Focus on high-value user segments',
'Implement dynamic pricing based on user behavior',
'Optimize conversion funnel for better IAP performance',
'Consider introducing limited-time offers',
],
};
}
private async getRevenueTrends(): Promise<Array<{ date: string; revenue: number; source: string }>> {
// Mock trend data - in real app, query your analytics
return [
{ date: '2024-01-01', revenue: 1250, source: 'iap' },
{ date: '2024-01-02', revenue: 890, source: 'ads' },
{ date: '2024-01-03', revenue: 1500, source: 'subscription' },
];
}
private async generateInsights(): Promise<string[]> {
return [
'Subscription revenue is growing faster than IAP revenue',
'Ad revenue peaks on weekends when users are more active',
'Users who complete onboarding have 3x higher conversion rate',
'Premium features usage correlates with subscription renewals',
];
}
}
export default RevenueIntelligence;
In this lesson, you learned:
Code with AI: Try building these advanced monetization features.
Prompts to try:
Remember: Great monetization balances business success with user satisfaction - always provide value first!