Skip to content

Instantly share code, notes, and snippets.

@Josiassejod1
Created May 30, 2025 18:59
Show Gist options
  • Save Josiassejod1/70d2f10a5c348dd160972c17fa172aec to your computer and use it in GitHub Desktop.
Save Josiassejod1/70d2f10a5c348dd160972c17fa172aec to your computer and use it in GitHub Desktop.
Working Example of Stripe Subscription React Native
import React, { useState, useEffect } from 'react';
import { StyleSheet, View, TouchableOpacity, ActivityIndicator, Alert, Linking } from 'react-native';
import { ThemedView } from '@/components/ThemedView';
import { ThemedText } from '@/components/ThemedText';
import { useSubscription } from '@/context/SubscriptionContext';
import { useStripe } from '@stripe/stripe-react-native';
import { getAuth } from 'firebase/auth';
import { STRIPE_CONFIG } from '@/config/stripe';
import Stripe from 'stripe';
export default function SubscriptionPlans() {
const { isSubscribed, subscriptionTier, loading, error, handleSubscribe, handleCancelSubscription } = useSubscription();
const { initPaymentSheet, presentPaymentSheet } = useStripe();
const [processing, setProcessing] = useState(false);
const [subscriptionInfo, setSubscriptionInfo] = useState<any>(null);
const [subscriptionLoading, setSubscriptionLoading] = useState(false);
const auth = getAuth();
const VERCEL_URL = __DEV__ ? 'http://localhost:3000' : 'https://example.com';
useEffect(() => {
const handleDeepLink = (event: { url: any; }) => {
const url = event.url;
if (url && url.startsWith('example://stripe-redirect')) {
// Parse the result, or just react to success
// You may want to confirm the PaymentIntent status on your backend
Alert.alert('Payment Complete', 'You were redirected back after payment.');
}
};
const subscription = Linking.addEventListener('url', handleDeepLink);
// For the case where the app was cold-started from the link
Linking.getInitialURL().then((url) => {
if (url && url.startsWith('example')) {
handleDeepLink({ url });
}
});
return () => {
subscription.remove();
};
}, []);
// Fetch subscription info from backend
const fetchSubscriptionInfo = async () => {
setSubscriptionLoading(true);
try {
const user = auth.currentUser;
if (!user) return;
const response = await fetch(`${VERCEL_URL}/api/get-subscription`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${await user.getIdToken()}`
}
});
if (response.ok) {
const data = await response.json();
setSubscriptionInfo(data);
} else {
setSubscriptionInfo(null);
}
} catch (err) {
setSubscriptionInfo(null);
} finally {
setSubscriptionLoading(false);
}
};
useEffect(() => {
fetchSubscriptionInfo();
}, [isSubscribed]);
const handleSubscribePress = async () => {
try {
setProcessing(true);
const user = auth.currentUser;
if (!user) {
Alert.alert('Error', 'You must be logged in to subscribe');
return;
}
console.log(user.getIdToken())
// Create payment intent on your backend
const response = await fetch(`${VERCEL_URL}/api/create-payment-intent`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await user.getIdToken()}`
},
body: JSON.stringify({
priceId: STRIPE_CONFIG.plans.premium.id,
userId: user.uid,
email: user.email
}),
});
const { clientSecret, subscriptionId } = await response.json();
// Initialize the Payment sheet
const { error } = await initPaymentSheet({
paymentIntentClientSecret: clientSecret,
merchantDisplayName: 'Jorge\'s Journey',
returnURL: 'jorgejourney://stripe-redirect',
});
if (error) {
console.log(error,"error");
throw error;
}
// Present the Payment Sheet
const { error: presentError } = await presentPaymentSheet();
if (presentError) {
console.log(presentError,"presentError");
throw presentError;
}
// If successful, update subscription status
await handleSubscribe(clientSecret, subscriptionId);
Alert.alert('Success', 'Welcome to Premium! 🎉');
} catch (err) {
Alert.alert('Error', err instanceof Error ? err.message : 'Failed to process subscription');
} finally {
setProcessing(false);
}
};
const handleCancelPress = async () => {
try {
setProcessing(true);
const user = auth.currentUser;
if (!user) {
Alert.alert('Error', 'You must be logged in to cancel subscription');
setProcessing(false);
return;
}
await handleCancelSubscription();
Alert.alert('Success', 'Subscription cancellation request processed.');
await fetchSubscriptionInfo();
} catch (err: any) {
const errorMessage = err?.message || (typeof err === 'string' ? err : 'Failed to process cancellation.');
Alert.alert('Error', errorMessage);
} finally {
setProcessing(false);
}
};
if (loading || subscriptionLoading) {
return (
<ThemedView style={styles.container}>
<ActivityIndicator size="large" color="#FFD700" />
</ThemedView>
);
}
if (error) {
return (
<ThemedView style={styles.container}>
<ThemedText style={styles.errorText}>{error}</ThemedText>
</ThemedView>
);
}
return (
<ThemedView style={styles.container}>
<ThemedText style={styles.title}>Choose Your Plan</ThemedText>
<View style={styles.planContainer}>
<View style={styles.planCard}>
<ThemedText style={styles.planTitle}>Free</ThemedText>
<ThemedText style={styles.planPrice}>$0</ThemedText>
<ThemedText style={styles.planDescription}>
• Basic lessons{'\n'}
• Limited practice exercises{'\n'}
• Community access
</ThemedText>
{subscriptionTier === 'free' && (
<ThemedText style={styles.currentPlan}>Current Plan</ThemedText>
)}
</View>
<View style={[styles.planCard, styles.premiumCard]}>
<ThemedText style={styles.planTitle}>Premium</ThemedText>
<ThemedText style={styles.planPrice}>$4.99/month</ThemedText>
<ThemedText style={styles.planDescription}>
• All Free features{'\n'}
• Unlimited practice{'\n'}
• Premium lessons{'\n'}
• Offline access{'\n'}
• Priority support
</ThemedText>
{isSubscribed ? (
<TouchableOpacity
style={[styles.button, styles.cancelButton]}
onPress={handleCancelPress}
disabled={processing}
>
<ThemedText style={styles.buttonText}>
{processing ? 'Processing...' : 'Cancel Subscription'}
</ThemedText>
</TouchableOpacity>
) : (
<TouchableOpacity
style={[styles.button, styles.subscribeButton]}
onPress={handleSubscribePress}
disabled={processing}
>
<ThemedText style={styles.buttonText}>
{processing ? 'Processing...' : 'Subscribe Now'}
</ThemedText>
</TouchableOpacity>
)}
</View>
</View>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 20,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 32,
fontWeight: 'bold',
marginBottom: 40,
textAlign: 'center',
},
planContainer: {
width: '100%',
maxWidth: 600,
gap: 20,
},
planCard: {
padding: 20,
borderRadius: 15,
backgroundColor: '#FFF',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
premiumCard: {
backgroundColor: '#FFF5E1',
borderWidth: 2,
borderColor: '#FFD700',
},
planTitle: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 10,
},
planPrice: {
fontSize: 36,
fontWeight: 'bold',
color: '#E65100',
marginBottom: 20,
},
planDescription: {
fontSize: 16,
lineHeight: 24,
marginBottom: 20,
},
button: {
padding: 15,
borderRadius: 10,
alignItems: 'center',
},
subscribeButton: {
backgroundColor: '#FFD700',
},
cancelButton: {
backgroundColor: '#FF6B6B',
},
buttonText: {
fontSize: 18,
fontWeight: 'bold',
color: '#333',
},
currentPlan: {
fontSize: 16,
color: '#666',
textAlign: 'center',
marginTop: 10,
},
errorText: {
color: '#FF6B6B',
fontSize: 16,
textAlign: 'center',
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment