WCAG 2.2 AA compliance, ARIA patterns, keyboard navigation, screen reader optimization
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
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.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
// 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 }
// Application.onCreate()
Purchases.logLevel = LogLevel.DEBUG
Purchases.configure(
PurchasesConfiguration.Builder(this, "goog_XXXXXXXXXXXXX")
.appUserID("user_id") // null ise anonim
.build()
)
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
});
// main.dart
await Purchases.configure(
PurchasesConfiguration('appl_XXXXXXXXXXXXX')
..appUserID = userId
);
// 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%"
}
}
// 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()
)
// 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);
});
// 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;
}
}
// 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);
}
// 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.")
}
}
RevenueCat Dashboard > Project > Integrations > Webhooks
https://your-api.com/webhooks/revenuecat// 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: { subscriptionRenewedAt: new Date() }
});
},
// Iptal (hemen degil, sure sonunda biter)
'CANCELLATION': async (event) => {
await db.user.update({
where: { id: event.app_user_id },
data: { willCancel: true, cancelledAt: new Date() }
});
// Win-back kampanyasi baslat
await triggerWinBackCampaign(event.app_user_id, event.expiration_at_ms);
},
// Sure doldu
'EXPIRATION': async (event) => {
await db.user.update({
where: { id: event.app_user_id },
data: { isPremium: false, expiredAt: new Date() }
});
},
// Odeme sorunu
'BILLING_ISSUE': async (event) => {
await db.user.update({
where: { id: event.app_user_id },
data: { hasBillingIssue: true }
});
// Grace period bilgilendirmesi
await sendBillingIssueNotification(event.app_user_id);
},
// Trial donusumu
'SUBSCRIBER_ALIAS': async (event) => {
// Anonim kullanici -> kayitli kullanici eslestirme
},
};
export async function POST(req: NextRequest) {
const authHeader = req.headers.get('authorization');
if (authHeader !== `Bearer ${process.env.REVENUECAT_WEBHOOK_SECRET}`) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const body: RevenueCatEvent = await req.json();
// Sandbox event'lerini filtrele (production'da)
if (process.env.NODE_ENV === 'production' && body.event.environment === 'SANDBOX') {
return NextResponse.json({ ok: true });
}
const handler = EVENT_HANDLERS[body.event.type];
if (handler) {
await handler(body.event);
}
return NextResponse.json({ ok: true });
}
Purchases.logLevel = .debug ile test et[ ] RevenueCat dashboard'da sandbox event'leri gorunuyor mu?
[ ] Entitlement dogru aktive oluyor mu?
[ ] Restore purchases calisiyor mu?
[ ] Webhook'lar geliyor mu? (RequestBin ile test et)
[ ] Fiyatlar locale'e gore formatlanmis mi?
[ ] Trial suresi dogru gorunuyor mu?
[ ] Iptal sonrasi erisim sure sonunda kapaniyor mu?
usesStoreKit2IfAvailable: true ile etkinlestir아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
WCAG 2.2 AA compliance, ARIA patterns, keyboard navigation, screen reader optimization
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
axe-core integration, WCAG 2.2 AA checklist, keyboard navigation testing, screen reader testing, and ARIA pattern validation.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Steam-style achievement system with XP, levels, streaks, and skill trees. Gamifies the development workflow. 25 achievements across 5 categories.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Framework for measuring and tracking agent response quality over time. Detects regressions before they reach production. Use when evaluating agent changes, auditing quality, or establishing performance baselines.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Agent Context Isolation
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Agent ve skill dosyalarinin yapisal dogrulamasi. Frontmatter kontrol, naming convention, zorunlu bolum kontrolu, tutarlilik denetimi. Yeni agent/skill eklendiginde veya mevcut dosyalar duzenlediginde otomatik calistirilir.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.