import React, { useEffect, useMemo, useState } from 'react'; import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, ActivityIndicator, Alert, Linking } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import Constants from 'expo-constants'; import Purchases, { PRODUCT_CATEGORY } from 'react-native-purchases'; import { useApp } from '../../context/AppContext'; import { useColors } from '../../constants/Colors'; import { ThemeBackdrop } from '../../components/ThemeBackdrop'; import { Language } from '../../types'; import { PurchaseProductId } from '../../services/backend/contracts'; const getBillingCopy = (language: Language) => { if (language === 'de') { return { title: 'Abo und Credits', planLabel: 'Aktueller Plan', planFree: 'Free', planPro: 'Pro', creditsAvailableLabel: 'Verfügbare Credits', manageSubscription: 'Abo verwalten', subscriptionTitle: 'Abos', subscriptionHint: 'Wähle ein Abo und schalte stärkere KI-Scans sowie mehr Credits frei.', freePlanName: 'Free', freePlanPrice: '0 EUR / Monat', proPlanName: 'Pro', proPlanPrice: '4.99 EUR / Monat', proBadgeText: 'EMPFOHLEN', proYearlyPlanName: 'Pro', proYearlyPlanPrice: '39.99 EUR / Jahr', proYearlyBadgeText: 'SPAREN', proBenefits: [ '250 Credits jeden Monat', 'Pro-Scans mit GPT-5.4', 'Unbegrenzte Historie & Galerie', 'KI-Pflanzendoktor inklusive', 'Priorisierter Support' ], topupTitle: 'Credits Aufladen', topupSmall: '25 Credits – 1,99 €', topupMedium: '120 Credits – 6,99 €', topupLarge: '300 Credits – 12,99 €', topupBestValue: 'BESTES ANGEBOT', cancelTitle: 'Schade, dass du gehst', cancelQuestion: 'Dürfen wir fragen, warum du kündigst?', reasonTooExpensive: 'Es ist mir zu teuer', reasonNotUsing: 'Ich nutze die App zu selten', reasonOther: 'Ein anderer Grund', offerTitle: 'Ein Geschenk für dich!', offerText: 'Bleib dabei und erhalte den nächsten Monat für nur 2,49 € (50% Rabatt).', offerAccept: 'Rabatt sichern', offerDecline: 'Nein, Kündigung fortsetzen', confirmCancelBtn: 'Jetzt kündigen', restorePurchases: 'Käufe wiederherstellen', autoRenewMonthly: 'Verlängert sich monatlich automatisch. Jederzeit über iOS-Einstellungen kündbar.', autoRenewYearly: 'Verlängert sich jährlich automatisch. Jederzeit über iOS-Einstellungen kündbar.', manageInSettings: 'In iOS-Einstellungen verwalten', }; } else if (language === 'es') { return { title: 'Suscripción y Créditos', planLabel: 'Plan Actual', planFree: 'Gratis', planPro: 'Pro', creditsAvailableLabel: 'Créditos Disponibles', manageSubscription: 'Administrar Suscripción', subscriptionTitle: 'Suscripciones', subscriptionHint: 'Elige un plan y desbloquea escaneos con IA más potentes y más créditos.', freePlanName: 'Gratis', freePlanPrice: '0 EUR / Mes', proPlanName: 'Pro', proPlanPrice: '4.99 EUR / Mes', proBadgeText: 'RECOMENDADO', proYearlyPlanName: 'Pro', proYearlyPlanPrice: '39.99 EUR / Año', proYearlyBadgeText: 'AHORRAR', proBenefits: [ '250 créditos cada mes', 'Escaneos Pro con GPT-5.4', 'Historial y galería ilimitados', 'Doctor de plantas de IA incluido', 'Soporte prioritario' ], topupTitle: 'Recargar Créditos', topupSmall: '25 Créditos – 1,99 €', topupMedium: '120 Créditos – 6,99 €', topupLarge: '300 Créditos – 12,99 €', topupBestValue: 'MEJOR OFERTA', cancelTitle: 'Lamentamos verte ir', cancelQuestion: '¿Podemos saber por qué cancelas?', reasonTooExpensive: 'Es muy caro', reasonNotUsing: 'No lo uso suficiente', reasonOther: 'Otra razón', offerTitle: '¡Un regalo para ti!', offerText: 'Quédate y obtén el próximo mes por solo 2,49 € (50% de descuento).', offerAccept: 'Aceptar descuento', offerDecline: 'No, continuar cancelando', confirmCancelBtn: 'Cancelar ahora', restorePurchases: 'Restaurar Compras', autoRenewMonthly: 'Se renueva mensualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.', autoRenewYearly: 'Se renueva anualmente de forma automática. Cancela cuando quieras en Ajustes de iOS.', manageInSettings: 'Administrar en Ajustes de iOS', }; } return { title: 'Billing & Credits', planLabel: 'Current Plan', planFree: 'Free', planPro: 'Pro', creditsAvailableLabel: 'Available Credits', manageSubscription: 'Manage Subscription', subscriptionTitle: 'Subscriptions', subscriptionHint: 'Choose a plan to unlock stronger AI scans and more credits.', freePlanName: 'Free', freePlanPrice: '0 EUR / Month', proPlanName: 'Pro', proPlanPrice: '4.99 EUR / Month', proBadgeText: 'RECOMMENDED', proYearlyPlanName: 'Pro', proYearlyPlanPrice: '39.99 EUR / Year', proYearlyBadgeText: 'SAVE', proBenefits: [ '250 credits every month', 'Pro scans with GPT-5.4', 'Unlimited history & gallery', 'AI Plant Doctor included', 'Priority support' ], topupTitle: 'Topup Credits', topupSmall: '25 Credits – €1.99', topupMedium: '120 Credits – €6.99', topupLarge: '300 Credits – €12.99', topupBestValue: 'BEST VALUE', cancelTitle: 'Sorry to see you go', cancelQuestion: 'May we ask why you are cancelling?', reasonTooExpensive: 'It is too expensive', reasonNotUsing: 'I don\'t use it enough', reasonOther: 'Other reason', offerTitle: 'A gift for you!', offerText: 'Stay with us and get your next month for just €2.49 (50% off).', offerAccept: 'Claim discount', offerDecline: 'No, continue cancelling', confirmCancelBtn: 'Cancel now', restorePurchases: 'Restore Purchases', autoRenewMonthly: 'Auto-renews monthly. Cancel anytime in iOS Settings.', autoRenewYearly: 'Auto-renews annually. Cancel anytime in iOS Settings.', manageInSettings: 'Manage in iOS Settings', }; }; export default function BillingScreen() { const router = useRouter(); const { isDarkMode, language, billingSummary, isLoadingBilling, simulatePurchase, simulateWebhookEvent, syncRevenueCatState, colorPalette, session } = useApp(); const colors = useColors(isDarkMode, colorPalette); const copy = getBillingCopy(language); const isExpoGo = Constants.appOwnership === 'expo'; const [subModalVisible, setSubModalVisible] = useState(false); const [isUpdating, setIsUpdating] = useState(false); const [storeReady, setStoreReady] = useState(isExpoGo); const [subscriptionPackages, setSubscriptionPackages] = useState>>({}); const [topupProducts, setTopupProducts] = useState>>({}); // Cancel Flow State const [cancelStep, setCancelStep] = useState<'none' | 'survey' | 'offer'>('none'); const planId = billingSummary?.entitlement?.plan || 'free'; const credits = isLoadingBilling && !billingSummary ? '...' : (billingSummary?.credits?.available ?? '--'); useEffect(() => { let cancelled = false; const loadStoreProducts = async () => { if (isExpoGo) { setStoreReady(true); return; } try { const [offerings, topups] = await Promise.all([ Purchases.getOfferings(), Purchases.getProducts(['topup_small', 'topup_medium', 'topup_large'], PRODUCT_CATEGORY.NON_SUBSCRIPTION), ]); if (cancelled) return; const currentOffering = offerings.current; setSubscriptionPackages({ monthly_pro: currentOffering?.monthly ?? undefined, yearly_pro: currentOffering?.annual ?? undefined, }); setTopupProducts({ topup_small: topups.find((product) => product.identifier === 'topup_small'), topup_medium: topups.find((product) => product.identifier === 'topup_medium'), topup_large: topups.find((product) => product.identifier === 'topup_large'), }); } catch (error) { console.warn('Failed to load RevenueCat products', error); } finally { if (!cancelled) { setStoreReady(true); } } }; loadStoreProducts(); return () => { cancelled = true; }; }, [isExpoGo]); const monthlyPackage = subscriptionPackages.monthly_pro; const yearlyPackage = subscriptionPackages.yearly_pro; const monthlyPrice = monthlyPackage?.product.priceString ?? copy.proPlanPrice; const yearlyPrice = yearlyPackage?.product.priceString ?? copy.proYearlyPlanPrice; const topupLabels = useMemo(() => ({ topup_small: topupProducts.topup_small ? `25 Credits - ${topupProducts.topup_small.priceString}` : copy.topupSmall, topup_medium: topupProducts.topup_medium ? `120 Credits - ${topupProducts.topup_medium.priceString}` : copy.topupMedium, topup_large: topupProducts.topup_large ? `300 Credits - ${topupProducts.topup_large.priceString}` : copy.topupLarge, }), [copy.topupLarge, copy.topupMedium, copy.topupSmall, topupProducts.topup_large, topupProducts.topup_medium, topupProducts.topup_small]); const openAppleSubscriptions = async () => { await Linking.openURL('itms-apps://apps.apple.com/account/subscriptions'); }; const handlePurchase = async (productId: PurchaseProductId) => { setIsUpdating(true); try { if (isExpoGo) { // ExpoGo has no native RevenueCat — use simulation for development only await simulatePurchase(productId); } else { if (productId === 'monthly_pro' || productId === 'yearly_pro') { if (planId === 'pro') { await openAppleSubscriptions(); setSubModalVisible(false); return; } const selectedPackage = productId === 'monthly_pro' ? monthlyPackage : yearlyPackage; if (!selectedPackage) { throw new Error('Abo-Paket konnte nicht geladen werden. Bitte RevenueCat Offering prüfen.'); } await Purchases.purchasePackage(selectedPackage); // Derive plan locally from RevenueCat — backend sync via webhook comes later (Step 3) const customerInfo = await Purchases.getCustomerInfo(); await syncRevenueCatState(customerInfo as any); } else { const selectedProduct = topupProducts[productId]; if (!selectedProduct) { throw new Error('Top-up Produkt konnte nicht geladen werden. Bitte Store-Produkt IDs prüfen.'); } await Purchases.purchaseStoreProduct(selectedProduct); const customerInfo = await Purchases.getCustomerInfo(); await syncRevenueCatState(customerInfo as any); } } setSubModalVisible(false); } catch (e) { const msg = e instanceof Error ? e.message : String(e); const userCancelled = typeof e === 'object' && e !== null && 'userCancelled' in e && Boolean((e as { userCancelled?: boolean }).userCancelled); if (userCancelled) { return; } console.error('Payment failed', e); Alert.alert('Unerwarteter Fehler', msg); } finally { setIsUpdating(false); } }; const handleRestore = async () => { setIsUpdating(true); try { if (!isExpoGo) { const customerInfo = await Purchases.restorePurchases(); await syncRevenueCatState(customerInfo as any); } Alert.alert(copy.restorePurchases, '✓'); } catch (e) { Alert.alert('Error', e instanceof Error ? e.message : String(e)); } finally { setIsUpdating(false); } }; const handleDowngrade = async () => { if (planId === 'free') return; if (!isExpoGo) { await openAppleSubscriptions(); return; } // Expo Go / dev only: simulate cancel flow setCancelStep('survey'); }; const finalizeCancel = async () => { setIsUpdating(true); try { await simulateWebhookEvent('entitlement_revoked'); setCancelStep('none'); setSubModalVisible(false); } catch (e) { console.error('Downgrade failed', e); } finally { setIsUpdating(false); } }; return ( router.back()} style={styles.backButton}> {copy.title} {isLoadingBilling && session ? ( ) : ( <> {session && ( {copy.planLabel} {planId === 'pro' ? copy.planPro : copy.planFree} setSubModalVisible(true)} > {copy.manageSubscription} {copy.creditsAvailableLabel} {credits} )} {!session && ( Subscription Plans Choose a plan to unlock AI plant scans and care features. {/* Monthly */} GreenLens Pro MONTHLY {monthlyPrice} {copy.autoRenewMonthly} {copy.proBenefits.map((b, i) => ( {b} ))} handlePurchase('monthly_pro')} disabled={isUpdating || !storeReady} > Subscribe Monthly {/* Yearly */} GreenLens Pro YEARLY {yearlyPrice} {copy.autoRenewYearly} {copy.proBenefits.map((b, i) => ( {b} ))} handlePurchase('yearly_pro')} disabled={isUpdating || !storeReady} > Subscribe Yearly Linking.openURL('https://greenlns-landing.vercel.app/privacy')}> Privacy Policy · Linking.openURL('https://greenlns-landing.vercel.app/terms')}> Terms of Use {copy.restorePurchases} )} {copy.topupTitle} {([ { id: 'topup_small' as PurchaseProductId, label: topupLabels.topup_small }, { id: 'topup_medium' as PurchaseProductId, label: topupLabels.topup_medium, badge: copy.topupBestValue }, { id: 'topup_large' as PurchaseProductId, label: topupLabels.topup_large }, ] as { id: PurchaseProductId; label: string; badge?: string }[]).map((pack) => ( handlePurchase(pack.id)} disabled={isUpdating || !storeReady} > {isUpdating ? '...' : pack.label} {pack.badge && ( {pack.badge} )} ))} Linking.openURL('https://greenlns-landing.vercel.app/privacy')}> Privacy Policy · Linking.openURL('https://greenlns-landing.vercel.app/terms')}> Terms of Use {copy.restorePurchases} )} setSubModalVisible(false)}> {cancelStep === 'survey' ? copy.cancelTitle : cancelStep === 'offer' ? copy.offerTitle : copy.subscriptionTitle} { setSubModalVisible(false); setCancelStep('none'); }}> {cancelStep === 'none' ? ( <> {copy.subscriptionHint} {copy.freePlanName} {copy.freePlanPrice} {planId === 'free' && } handlePurchase('monthly_pro')} disabled={isUpdating} > {copy.proPlanName} {copy.proBadgeText} {monthlyPrice} {copy.autoRenewMonthly} {copy.proBenefits.map((b, i) => ( {b} ))} {planId === 'pro' && } handlePurchase('yearly_pro')} disabled={isUpdating || !storeReady} > {copy.proYearlyPlanName} {copy.proYearlyBadgeText} {yearlyPrice} {copy.autoRenewYearly} {copy.proBenefits.map((b, i) => ( {b} ))} {planId === 'pro' && } Linking.openURL('https://greenlns-landing.vercel.app/privacy')}> Privacy Policy · Linking.openURL('https://greenlns-landing.vercel.app/terms')}> Terms of Use {copy.restorePurchases} ) : cancelStep === 'survey' ? ( {copy.cancelQuestion} {[ { id: 'expensive', label: copy.reasonTooExpensive, icon: 'cash-outline' }, { id: 'not_using', label: copy.reasonNotUsing, icon: 'calendar-outline' }, { id: 'other', label: copy.reasonOther, icon: 'ellipsis-horizontal-outline' }, ].map((reason) => ( { setCancelStep('offer'); }} > {reason.label} ))} ) : ( {copy.offerText} { // Handle applying discount here (future implementation) Alert.alert('Erfolg', 'Rabatt angewendet! (Mock)'); setCancelStep('none'); setSubModalVisible(false); }} > {copy.offerAccept} {copy.offerDecline} )} {(isUpdating || (!storeReady && cancelStep === 'none')) && } ); } const styles = StyleSheet.create({ safeArea: { flex: 1 }, header: { flexDirection: 'row', alignItems: 'center', padding: 16 }, backButton: { width: 40, height: 40, justifyContent: 'center' }, title: { flex: 1, fontSize: 20, fontWeight: '700', textAlign: 'center' }, scrollContent: { padding: 16, gap: 16 }, card: { padding: 16, borderRadius: 16, borderWidth: StyleSheet.hairlineWidth, }, sectionTitle: { fontSize: 14, fontWeight: '600', textTransform: 'uppercase', letterSpacing: 0.5, marginBottom: 8, }, row: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, value: { fontSize: 18, fontWeight: '600', }, manageBtn: { paddingHorizontal: 16, paddingVertical: 8, borderRadius: 20, }, manageBtnText: { color: '#fff', fontSize: 14, fontWeight: '600', }, creditsValue: { fontSize: 32, fontWeight: '700', }, topupBtn: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', paddingVertical: 12, borderRadius: 12, borderWidth: 2, gap: 8, }, topupText: { fontSize: 16, fontWeight: '600', }, modalOverlay: { flex: 1, backgroundColor: '#00000080', justifyContent: 'flex-end', }, modalContent: { borderTopLeftRadius: 24, borderTopRightRadius: 24, padding: 24, borderTopWidth: StyleSheet.hairlineWidth, paddingBottom: 40, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8, }, modalTitle: { fontSize: 20, fontWeight: '700', }, modalHint: { fontSize: 14, marginBottom: 24, }, plansContainer: { gap: 12, }, planOption: { padding: 16, borderRadius: 12, borderWidth: 2, flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, planName: { fontSize: 18, fontWeight: '600', }, planPrice: { fontSize: 14, }, planHeaderRow: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 2, }, proBadge: { paddingHorizontal: 8, paddingVertical: 2, borderRadius: 6, }, proBadgeText: { color: '#fff', fontSize: 10, fontWeight: '800', }, proBenefits: { marginTop: 12, gap: 6, }, benefitRow: { flexDirection: 'row', alignItems: 'center', gap: 6, }, benefitText: { fontSize: 12, fontWeight: '500', }, cancelFlowContainer: { marginTop: 8, }, cancelHint: { fontSize: 15, marginBottom: 16, }, reasonList: { gap: 12, }, reasonOption: { flexDirection: 'row', alignItems: 'center', padding: 16, borderWidth: 1, borderRadius: 12, }, reasonIcon: { width: 36, height: 36, borderRadius: 18, justifyContent: 'center', alignItems: 'center', marginRight: 12, }, reasonText: { flex: 1, fontSize: 16, fontWeight: '500', }, offerCard: { borderRadius: 16, padding: 24, alignItems: 'center', marginBottom: 16, }, offerIconWrap: { width: 56, height: 56, borderRadius: 28, justifyContent: 'center', alignItems: 'center', marginBottom: 16, }, offerText: { fontSize: 16, textAlign: 'center', lineHeight: 24, marginBottom: 24, fontWeight: '500', }, offerAcceptBtn: { paddingHorizontal: 24, paddingVertical: 14, borderRadius: 24, width: '100%', alignItems: 'center', }, offerAcceptBtnText: { color: '#fff', fontSize: 16, fontWeight: '700', }, offerDeclineBtn: { paddingVertical: 12, alignItems: 'center', }, offerDeclineBtnText: { fontSize: 15, fontWeight: '500', }, guestPlanCard: { borderWidth: 2, borderRadius: 12, padding: 16, }, guestPlanHeader: { flexDirection: 'row', alignItems: 'center', gap: 8, marginBottom: 4, }, guestPlanName: { fontSize: 18, fontWeight: '700', }, guestPlanPrice: { fontSize: 22, fontWeight: '700', marginBottom: 2, }, guestPlanRenew: { fontSize: 12, }, guestSubscribeBtn: { marginTop: 14, paddingVertical: 12, borderRadius: 10, alignItems: 'center', }, legalLinksRow: { flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginTop: 16, }, legalLink: { fontSize: 12, fontWeight: '500', textDecorationLine: 'underline', }, legalSep: { fontSize: 12, }, restoreBtn: { alignItems: 'center', paddingVertical: 8, }, autoRenewText: { fontSize: 11, marginTop: 2, marginBottom: 4, }, });