import React, { useState } from 'react'; import { View, Text, TouchableOpacity, ScrollView, StyleSheet, Modal, ActivityIndicator, Alert, Platform } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Ionicons } from '@expo/vector-icons'; import { useRouter } from 'expo-router'; import RevenueCatUI, { PAYWALL_RESULT } from "react-native-purchases-ui"; import { usePostHog } from 'posthog-react-native'; 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', }; } 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', }; } 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', }; }; export default function BillingScreen() { const router = useRouter(); const { isDarkMode, language, billingSummary, isLoadingBilling, simulatePurchase, simulateWebhookEvent, appearanceMode, colorPalette, session } = useApp(); const colors = useColors(isDarkMode, colorPalette); const copy = getBillingCopy(language); const posthog = usePostHog(); const [subModalVisible, setSubModalVisible] = useState(false); const [isUpdating, setIsUpdating] = useState(false); // Cancel Flow State const [cancelStep, setCancelStep] = useState<'none' | 'survey' | 'offer'>('none'); const [cancelReason, setCancelReason] = useState(null); const planId = billingSummary?.entitlement?.plan || 'free'; const credits = isLoadingBilling && !billingSummary ? '...' : (billingSummary?.credits?.available ?? '--'); const handlePurchase = async (productId: PurchaseProductId) => { setIsUpdating(true); try { const paywallResult: PAYWALL_RESULT = await RevenueCatUI.presentPaywall(); switch (paywallResult) { case PAYWALL_RESULT.NOT_PRESENTED: case PAYWALL_RESULT.ERROR: case PAYWALL_RESULT.CANCELLED: break; case PAYWALL_RESULT.PURCHASED: case PAYWALL_RESULT.RESTORED: await simulatePurchase(productId); setSubModalVisible(false); break; default: break; } } catch (e) { const msg = e instanceof Error ? e.message : String(e); console.error('Payment failed', e); if (__DEV__ && (msg.toLowerCase().includes('native') || msg.toLowerCase().includes('not found') || msg.toLowerCase().includes('undefined'))) { // Fallback for Expo Go since RevenueCat native module is not available console.log('Falling back to simulated purchase in Expo Go'); await simulatePurchase(productId); setSubModalVisible(false); } else { Alert.alert('Unerwarteter Fehler', msg); } } finally { setIsUpdating(false); } }; const handleSimulatePurchase = async (productId: PurchaseProductId) => { // Fallback for free option setIsUpdating(true); await simulatePurchase(productId); setIsUpdating(false); setSubModalVisible(false); }; const handleDowngrade = async () => { if (planId === 'free') return; // already on free plan 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 ? ( ) : ( <> {copy.planLabel} {planId === 'pro' ? copy.planPro : copy.planFree} setSubModalVisible(true)} > {copy.manageSubscription} {copy.creditsAvailableLabel} {credits} {copy.topupTitle} {([ { id: 'topup_small' as PurchaseProductId, label: copy.topupSmall }, { id: 'topup_medium' as PurchaseProductId, label: copy.topupMedium, badge: copy.topupBestValue }, { id: 'topup_large' as PurchaseProductId, label: copy.topupLarge }, ] as { id: PurchaseProductId; label: string; badge?: string }[]).map((pack) => ( handlePurchase(pack.id)} disabled={isUpdating} > {isUpdating ? '...' : pack.label} {pack.badge && ( {pack.badge} )} ))} )} 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} {copy.proPlanPrice} {copy.proBenefits.map((b, i) => ( {b} ))} {planId === 'pro' && } handlePurchase('yearly_pro')} disabled={isUpdating} > {copy.proYearlyPlanName} {copy.proYearlyBadgeText} {copy.proYearlyPlanPrice} {copy.proBenefits.map((b, i) => ( {b} ))} {planId === 'pro' && } ) : 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) => ( { setCancelReason(reason.id); 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 && 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', }, });