Receber por email
← Voltar para o catálogo

revenuecat-patterns

RevenueCat SDK entegrasyon pattern'leri. iOS (Swift), Android (Kotlin), React Native ve Flutter icin setup, offerings, entitlement checking, webhook integration, StoreKit 2 migration ve sandbox testing.

507estrelas
Atualizado há 4 meses

Ver no GitHub ↗ Licença: MIT

Como adicionar

/plugin marketplace add vibeeval/vibecosystem

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/revenuecat-patterns/svg)](https://www.skillteca.com.br/skills/revenuecat-patterns)

Skills relacionadas

Ver todas de Desenvolvimento →

Alerta por categoria

Receba novas skills de Desenvolvimento toda segunda

1 email curto, só com as skills novas de Desenvolvimento. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

RevenueCat Integration Patterns

SDK Setup

iOS (Swift + StoreKit 2)

// AppDelegate.swift veya @main App
import RevenueCat

Purchases.logLevel = .debug // sandbox'ta acik tut
Purchases.configure(
    with: .init(withAPIKey: "appl_XXXXXXXXXXXXX")
        .with(usesStoreKit2IfAvailable: true)
)

// Kullanici kimlik eslestirme (auth sonrasi)
Purchases.shared.logIn("user_id_from_your_backend") { customerInfo, created, error in
    // created: true ise yeni kullanici
}

// Logout
Purchases.shared.logOut { customerInfo, error in }

Android (Kotlin)

// Application.onCreate()
Purchases.logLevel = LogLevel.DEBUG
Purchases.configure(
    PurchasesConfiguration.Builder(this, "goog_XXXXXXXXXXXXX")
        .appUserID("user_id") // null ise anonim
        .build()
)

React Native

import Purchases from 'react-native-purchases';

// App.tsx useEffect icinde
await Purchases.configure({
  apiKey: Platform.OS === 'ios'
    ? 'appl_XXXXXXXXXXXXX'
    : 'goog_XXXXXXXXXXXXX',
  appUserID: userId ?? undefined, // null = anonim
});

Flutter

// main.dart
await Purchases.configure(
  PurchasesConfiguration('appl_XXXXXXXXXXXXX')
    ..appUserID = userId
);

Offerings ve Paywalls

Offerings Getirme

// iOS
Purchases.shared.getOfferings { offerings, error in
    guard let current = offerings?.current else { return }

    let monthly = current.monthly   // Package?
    let annual = current.annual     // Package?
    let weekly = current.package(identifier: "weekly") // Custom

    // Fiyat gosterme
    if let monthly = monthly {
        priceLabel.text = monthly.storeProduct.localizedPriceString
        // "$9.99" (locale'e gore formatlanmis)
    }
}
// Android
Purchases.sharedInstance.getOfferingsWith(
    onError = { error -> /* PurchasesError */ },
    onSuccess = { offerings ->
        val current = offerings.current ?: return@getOfferingsWith
        val monthly = current.monthly
        val annual = current.annual

        monthly?.product?.let { product ->
            priceText.text = product.price.formatted // "$9.99"
        }
    }
)
// React Native
const offerings = await Purchases.getOfferings();
const current = offerings.current;

if (current) {
  const monthly = current.monthly;
  const annual = current.annual;

  // Fiyat
  monthly?.product.priceString; // "$9.99"

  // Savings hesapla
  if (monthly && annual) {
    const monthlyPerYear = monthly.product.price * 12;
    const savings = Math.round((1 - annual.product.price / monthlyPerYear) * 100);
    // "Save 60%"
  }
}

RevenueCat Paywalls (No-code)

// iOS - RevenueCat Paywall UI
import RevenueCatUI

// SwiftUI
PaywallView()
    .onPurchaseCompleted { customerInfo in
        // Satin alma basarili
    }
    .onDismiss {
        // Kullanici kapatti
    }

// Footer mode (kendi UI'in + RC pricing)
PaywallFooterView()
// Android - Paywall UI
PaywallDialog(
    PaywallDialogOptions.Builder()
        .setDismissRequest { /* kapandi */ }
        .setListener(object : PaywallListener {
            override fun onPurchaseCompleted(customerInfo: CustomerInfo, storeTransaction: StoreTransaction) {
                // Basarili
            }
        })
        .build()
)

Entitlement Checking

// iOS - Erisim kontrolu
Purchases.shared.getCustomerInfo { customerInfo, error in
    let isPremium = customerInfo?.entitlements["premium"]?.isActive == true

    // Detayli bilgi
    if let entitlement = customerInfo?.entitlements["premium"] {
        entitlement.isActive              // true/false
        entitlement.willRenew             // otomatik yenilenecek mi
        entitlement.expirationDate        // bitis tarihi
        entitlement.periodType            // .normal, .trial, .intro
        entitlement.productIdentifier     // hangi urun
    }
}

// Listener (real-time degisiklik)
Purchases.shared.delegate = self

func purchases(_ purchases: Purchases,
               receivedUpdated customerInfo: CustomerInfo) {
    let isPremium = customerInfo.entitlements["premium"]?.isActive == true
    updateUI(isPremium: isPremium)
}
// React Native
const customerInfo = await Purchases.getCustomerInfo();
const isPremium = customerInfo.entitlements.active['premium'] !== undefined;

// Listener
Purchases.addCustomerInfoUpdateListener((info) => {
  const isPremium = info.entitlements.active['premium'] !== undefined;
  setIsPremium(isPremium);
});

Feature Gating Pattern

// Merkezi erisim kontrolu
class EntitlementManager {
  private customerInfo: CustomerInfo | null = null;

  async refresh(): Promise<void> {
    this.customerInfo = await Purchases.getCustomerInfo();
  }

  get isPremium(): boolean {
    return this.customerInfo?.entitlements.active['premium'] !== undefined;
  }

  get isOnTrial(): boolean {
    const ent = this.customerInfo?.entitlements.active['premium'];
    return ent?.periodType === 'TRIAL';
  }

  get trialEndDate(): Date | null {
    const ent = this.customerInfo?.entitlements.active['premium'];
    if (ent?.periodType !== 'TRIAL') return null;
    return ent.expirationDate ? new Date(ent.expirationDate) : null;
  }

  get willRenew(): boolean {
    return this.customerInfo?.entitlements.active['premium']?.willRenew ?? false;
  }
}

Purchase Flow

// iOS
Purchases.shared.purchase(package: monthlyPackage) { transaction, customerInfo, error, userCancelled in
    if userCancelled {
        // Kullanici iptal etti - agresif olmadan geri don
        return
    }
    if let error = error {
        // Hata: odeme basarisiz, network vb.
        handleError(error)
        return
    }
    if customerInfo?.entitlements["premium"]?.isActive == true {
        // BASARILI - premium erisim ac
        unlockPremium()
    }
}
// React Native
try {
  const { customerInfo } = await Purchases.purchasePackage(monthlyPackage);
  if (customerInfo.entitlements.active['premium']) {
    unlockPremium();
  }
} catch (e: any) {
  if (e.userCancelled) return;
  handleError(e);
}

Restore Purchases (ZORUNLU - Apple Requirement)

// iOS - MUTLAKA paywall'da "Restore Purchases" butonu olmali
Purchases.shared.restorePurchases { customerInfo, error in
    if customerInfo?.entitlements["premium"]?.isActive == true {
        showAlert("Aboneliginiz geri yuklendi!")
        unlockPremium()
    } else {
        showAlert("Aktif abonelik bulunamadi.")
    }
}

Webhook Integration (Server-Side)

Webhook Setup

RevenueCat Dashboard > Project > Integrations > Webhooks

  • URL: https://your-api.com/webhooks/revenuecat
  • Authorization Header: Bearer token ile dogrula

Webhook Handler

// Next.js API Route
import { NextRequest, NextResponse } from 'next/server';

interface RevenueCatEvent {
  api_version: string;
  event: {
    type: string;
    app_user_id: string;
    product_id: string;
    entitlement_ids: string[];
    period_type: 'TRIAL' | 'NORMAL' | 'INTRO';
    expiration_at_ms: number;
    environment: 'SANDBOX' | 'PRODUCTION';
    price_in_purchased_currency: number;
    currency: string;
    store: 'APP_STORE' | 'PLAY_STORE' | 'STRIPE';
  };
}

// Event tipleri ve aksiyonlar
const EVENT_HANDLERS: Record<string, (event: RevenueCatEvent['event']) => Promise<void>> = {
  // Yeni satin alma
  'INITIAL_PURCHASE': async (event) => {
    await db.user.update({
      where: { id: event.app_user_id },
      data: { isPremium: true, subscriptionStart: new Date() }
    });
    await analytics.track('subscription_started', {
      userId: event.app_user_id,
      product: event.product_id,
      price: event.price_in_purchased_currency,
      currency: event.currency,
      periodType: event.period_type,
    });
  },

  // Yenileme
  'RENEWAL': async (event) => {
    await db.user.update({
      where: { id: event.app_user_id },
      data:
Compartilhar X LinkedIn