import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { usePaymentSheet } from '@stripe/stripe-react-native';
import { useToast } from '@/components/ui/Toast';
import { AppColors, BorderRadius, FontSizes, FontWeights, Spacing, Shadows } from '@/constants/theme';
import type { Beneficiary } from '@/types';
const STRIPE_API_URL = 'https://wellnuo.smartlaunchhub.com/api/stripe';
const SUBSCRIPTION_PRICE = 49; // $49/month
interface SubscriptionPaymentProps {
beneficiary: Beneficiary;
onSuccess?: () => void;
compact?: boolean; // For inline use vs full page
}
export function SubscriptionPayment({ beneficiary, onSuccess, compact = false }: SubscriptionPaymentProps) {
const [isProcessing, setIsProcessing] = useState(false);
const { initPaymentSheet, presentPaymentSheet } = usePaymentSheet();
const toast = useToast();
const isExpired = beneficiary?.subscription?.status === 'expired';
const handleSubscribe = async () => {
setIsProcessing(true);
try {
// 1. Create subscription payment sheet via new Stripe Subscriptions API
const response = await fetch(`${STRIPE_API_URL}/create-subscription-payment-sheet`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
beneficiaryId: beneficiary.id,
}),
});
const data = await response.json();
// Check if already subscribed
if (data.alreadySubscribed) {
toast.success(
'Already Subscribed!',
`${beneficiary.displayName} already has an active subscription.`
);
onSuccess?.();
return;
}
if (!data.clientSecret) {
throw new Error(data.error || 'Failed to create subscription');
}
// 2. Initialize the Payment Sheet
const { error: initError } = await initPaymentSheet({
merchantDisplayName: 'WellNuo',
paymentIntentClientSecret: data.clientSecret,
customerId: data.customer,
customerEphemeralKeySecret: data.ephemeralKey,
returnURL: 'wellnuo://stripe-redirect',
applePay: {
merchantCountryCode: 'US',
},
googlePay: {
merchantCountryCode: 'US',
testEnv: true,
},
});
if (initError) {
throw new Error(initError.message);
}
// 3. Present the Payment Sheet
const { error: presentError } = await presentPaymentSheet();
if (presentError) {
if (presentError.code === 'Canceled') {
// User canceled - don't show error, just return
setIsProcessing(false);
return;
}
throw new Error(presentError.message);
}
// 4. Payment sheet closed - verify the subscription is actually active
// This is CRITICAL: presentPaymentSheet can return success even without payment!
const statusResponse = await fetch(
`${STRIPE_API_URL}/subscription-status/${beneficiary.id}`
);
const statusData = await statusResponse.json();
// Check if subscription is actually active
if (!['active', 'trialing'].includes(statusData.status)) {
// Payment was not completed - subscription is still incomplete
throw new Error(
statusData.status === 'incomplete'
? 'Payment was not completed. Please try again and enter your card details.'
: `Subscription activation failed. Status: ${statusData.status}`
);
}
// Update local state with data from Stripe
toast.success(
'Subscription Activated!',
`Subscription for ${beneficiary.displayName} is now active.`
);
onSuccess?.();
} catch (error) {
toast.error(
'Payment Failed',
error instanceof Error ? error.message : 'Something went wrong. Please try again.'
);
} finally {
setIsProcessing(false);
}
};
const features = [
'24/7 AI wellness monitoring',
'Unlimited Julia AI chat',
'Detailed activity reports',
'Smart alerts & notifications',
];
if (compact) {
// Compact version for inline use
return (
{isExpired ? 'Subscription Expired' : 'Subscription Required'}
${SUBSCRIPTION_PRICE}/month
{isProcessing ? (
) : (
<>
{isExpired ? 'Renew Now' : 'Subscribe Now'}
>
)}
);
}
// Full version
return (
{/* Icon */}
{/* Title */}
{isExpired ? 'Subscription Expired' : 'Subscription Required'}
{isExpired
? `Your subscription for ${beneficiary.displayName} has expired. Renew now to continue monitoring their wellness.`
: `Activate a subscription to view ${beneficiary.displayName}'s dashboard and wellness data.`}
{/* Price Card */}
WellNuo Pro
Full access to all features
${SUBSCRIPTION_PRICE}
/month
{features.map((feature, index) => (
{feature}
))}
{/* Subscribe Button */}
{isProcessing ? (
) : (
<>
{isExpired ? 'Renew Subscription' : 'Subscribe Now'}
>
)}
{/* Security Badge */}
Secure payment powered by Stripe
);
}
const styles = StyleSheet.create({
// Full version styles
container: {
flex: 1,
padding: Spacing.xl,
alignItems: 'center',
justifyContent: 'center',
},
iconContainer: {
width: 100,
height: 100,
borderRadius: 50,
backgroundColor: AppColors.accentLight,
justifyContent: 'center',
alignItems: 'center',
marginBottom: Spacing.lg,
},
title: {
fontSize: FontSizes['2xl'],
fontWeight: FontWeights.bold,
color: AppColors.textPrimary,
textAlign: 'center',
marginBottom: Spacing.sm,
},
subtitle: {
fontSize: FontSizes.base,
color: AppColors.textSecondary,
textAlign: 'center',
lineHeight: 24,
marginBottom: Spacing.xl,
paddingHorizontal: Spacing.md,
},
priceCard: {
width: '100%',
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.xl,
padding: Spacing.lg,
marginBottom: Spacing.xl,
...Shadows.sm,
},
priceHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: Spacing.md,
},
planName: {
fontSize: FontSizes.lg,
fontWeight: FontWeights.semibold,
color: AppColors.textPrimary,
},
planDesc: {
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
marginTop: 2,
},
priceBadge: {
flexDirection: 'row',
alignItems: 'baseline',
},
priceAmount: {
fontSize: FontSizes['2xl'],
fontWeight: FontWeights.bold,
color: AppColors.primary,
},
priceUnit: {
fontSize: FontSizes.sm,
color: AppColors.textMuted,
marginLeft: 2,
},
features: {
gap: Spacing.sm,
},
featureRow: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.sm,
},
featureText: {
fontSize: FontSizes.sm,
color: AppColors.textSecondary,
},
subscribeButtonFull: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: Spacing.sm,
width: '100%',
backgroundColor: AppColors.primary,
paddingVertical: Spacing.lg,
borderRadius: BorderRadius.lg,
...Shadows.primary,
},
subscribeButtonTextFull: {
fontSize: FontSizes.lg,
fontWeight: FontWeights.semibold,
color: AppColors.white,
},
securityBadge: {
flexDirection: 'row',
alignItems: 'center',
gap: Spacing.xs,
marginTop: Spacing.lg,
},
securityText: {
fontSize: FontSizes.xs,
color: AppColors.success,
},
// Compact version styles
compactContainer: {
backgroundColor: AppColors.surface,
borderRadius: BorderRadius.xl,
padding: Spacing.lg,
...Shadows.sm,
},
compactHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: Spacing.md,
},
compactIconContainer: {
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: AppColors.accentLight,
justifyContent: 'center',
alignItems: 'center',
marginRight: Spacing.md,
},
compactInfo: {
flex: 1,
},
compactTitle: {
fontSize: FontSizes.base,
fontWeight: FontWeights.semibold,
color: AppColors.textPrimary,
},
compactPrice: {
fontSize: FontSizes.lg,
fontWeight: FontWeights.bold,
color: AppColors.primary,
marginTop: 2,
},
subscribeButton: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: Spacing.sm,
backgroundColor: AppColors.primary,
paddingVertical: Spacing.md,
borderRadius: BorderRadius.lg,
},
subscribeButtonText: {
fontSize: FontSizes.base,
fontWeight: FontWeights.semibold,
color: AppColors.white,
},
buttonDisabled: {
opacity: 0.7,
},
});