Subscriptions API
The Subscriptions API allows you to create and manage recurring billing subscriptions for your customers.
Methods
getSubscriptions
Retrieve a list of all subscriptions with optional pagination.
const { getSubscriptions } = useEasy();
const result = await getSubscriptions({
limit: 50,
offset: 0,
});
Parameters
params(optional)limit(number): Maximum number of subscriptions to return (default: 10, max: 100)offset(number): Number of subscriptions to skip for pagination
Returns
ApiResponse<SubscriptionData[]>;
Example
import { useEasy } from "@easylabs/react-native";
import { useState, useEffect } from "react";
import {
FlatList,
View,
Text,
ActivityIndicator,
StyleSheet,
} from "react-native";
function SubscriptionList() {
const { getSubscriptions } = useEasy();
const [subscriptions, setSubscriptions] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchSubscriptions = async () => {
try {
const result = await getSubscriptions({ limit: 50 });
setSubscriptions(result.data);
} catch (error) {
console.error("Failed to fetch subscriptions:", error);
} finally {
setLoading(false);
}
};
fetchSubscriptions();
}, []);
if (loading) return <ActivityIndicator size="large" />;
return (
<FlatList
data={subscriptions}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.card}>
<Text style={styles.id}>Subscription {item.id}</Text>
<Text style={styles.status}>Status: {item.status}</Text>
</View>
)}
/>
);
}
const styles = StyleSheet.create({
card: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: "#eee",
},
id: {
fontSize: 16,
fontWeight: "600",
},
status: {
fontSize: 14,
color: "#666",
marginTop: 4,
},
});
getSubscription
Retrieve a specific subscription by ID.
const { getSubscription } = useEasy();
const result = await getSubscription("sub_123");
const subscription = result.data;
Parameters
subscriptionId(string, required): The ID of the subscription to retrieve
Returns
ApiResponse<SubscriptionData>;
Example
import { useEasy } from "@easylabs/react-native";
import { useState, useEffect } from "react";
import { View, Text, StyleSheet } from "react-native";
function SubscriptionDetails({ subscriptionId }) {
const { getSubscription } = useEasy();
const [subscription, setSubscription] = useState(null);
useEffect(() => {
const fetchSubscription = async () => {
try {
const result = await getSubscription(subscriptionId);
setSubscription(result.data);
} catch (error) {
console.error("Subscription not found:", error);
}
};
fetchSubscription();
}, [subscriptionId]);
if (!subscription) return <Text>Loading...</Text>;
return (
<View style={styles.container}>
<Text style={styles.title}>Subscription {subscription.id}</Text>
<Text style={styles.detail}>Status: {subscription.status}</Text>
<Text style={styles.detail}>
Current Period:{" "}
{new Date(subscription.current_period_start).toLocaleDateString()} -{" "}
{new Date(subscription.current_period_end).toLocaleDateString()}
</Text>
{subscription.cancel_at_period_end && (
<Text style={styles.warning}>Will cancel at period end</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 16,
},
title: {
fontSize: 20,
fontWeight: "bold",
marginBottom: 12,
},
detail: {
fontSize: 14,
marginVertical: 4,
},
warning: {
fontSize: 14,
color: "#F44336",
fontWeight: "600",
marginTop: 8,
},
});
createSubscription
Create a new subscription for a customer.
const { createSubscription } = useEasy();
const result = await createSubscription({
identity_id: "cust_123",
items: [{ price_id: "price_abc", quantity: 1 }],
trial_period_days: 14,
metadata: {
plan: "premium",
},
});
Parameters
params(required)identity_id(string, required): Customer IDitems(array, required): Array of subscription itemsprice_id(string): Price IDquantity(number): Quantity (default: 1)
trial_period_days(number, optional): Number of days for trial periodmetadata(object, optional): Additional custom data
Returns
ApiResponse<SubscriptionData>;
Example
import { useEasy } from "@easylabs/react-native";
import { useState } from "react";
import {
View,
Text,
TouchableOpacity,
Switch,
StyleSheet,
Alert,
} from "react-native";
import { Picker } from "@react-native-picker/picker";
function CreateSubscriptionForm({ customerId }) {
const { createSubscription } = useEasy();
const [priceId, setPriceId] = useState("");
const [trial, setTrial] = useState(false);
const [loading, setLoading] = useState(false);
const handleSubmit = async () => {
if (!priceId) {
Alert.alert("Error", "Please select a plan");
return;
}
setLoading(true);
try {
const result = await createSubscription({
identity_id: customerId,
items: [{ price_id: priceId, quantity: 1 }],
trial_period_days: trial ? 14 : undefined,
metadata: {
source: "mobile",
},
});
Alert.alert("Success", `Subscription created: ${result.data.id}`);
} catch (error) {
Alert.alert("Error", "Failed to create subscription");
} finally {
setLoading(false);
}
};
return (
<View style={styles.form}>
<Text style={styles.label}>Select Plan</Text>
<Picker
selectedValue={priceId}
onValueChange={(value) => setPriceId(value)}
style={styles.picker}
>
<Picker.Item label="Choose a plan..." value="" />
<Picker.Item label="Monthly - $29.99" value="price_monthly" />
<Picker.Item label="Annual - $299.99" value="price_annual" />
</Picker>
<View style={styles.switchContainer}>
<Text style={styles.switchLabel}>Start with 14-day trial</Text>
<Switch value={trial} onValueChange={setTrial} />
</View>
<TouchableOpacity
style={[styles.button, loading && styles.buttonDisabled]}
onPress={handleSubmit}
disabled={loading}
>
<Text style={styles.buttonText}>
{loading ? "Creating..." : "Subscribe"}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
form: {
padding: 16,
},
label: {
fontSize: 16,
fontWeight: "600",
marginBottom: 8,
},
picker: {
marginBottom: 16,
},
switchContainer: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 20,
},
switchLabel: {
fontSize: 14,
},
button: {
backgroundColor: "#007AFF",
padding: 16,
borderRadius: 8,
alignItems: "center",
},
buttonDisabled: {
backgroundColor: "#ccc",
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});
updateSubscription
Update an existing subscription.
const { updateSubscription } = useEasy();
const result = await updateSubscription("sub_123", {
items: [{ price_id: "price_new", quantity: 1 }],
metadata: {
upgraded: true,
},
});
Parameters
subscriptionId(string, required): The ID of the subscription to updateparams(required)items(array, optional): New subscription itemsmetadata(object, optional): Updated metadata
Returns
ApiResponse<SubscriptionData>;
Updating subscription items will prorate charges and adjust billing accordingly. The change takes effect immediately.
Example
import { useEasy } from "@easylabs/react-native";
import { useState } from "react";
import { View, Text, TouchableOpacity, StyleSheet, Alert } from "react-native";
import { Picker } from "@react-native-picker/picker";
function UpgradeSubscription({ subscriptionId, currentPriceId }) {
const { updateSubscription } = useEasy();
const [newPriceId, setNewPriceId] = useState("");
const handleUpgrade = async () => {
try {
const result = await updateSubscription(subscriptionId, {
items: [{ price_id: newPriceId, quantity: 1 }],
metadata: {
upgraded_from: currentPriceId,
upgraded_at: new Date().toISOString(),
},
});
Alert.alert("Success", "Subscription upgraded!");
} catch (error) {
Alert.alert("Error", "Failed to upgrade subscription");
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>Upgrade Your Plan</Text>
<Picker
selectedValue={newPriceId}
onValueChange={(value) => setNewPriceId(value)}
style={styles.picker}
>
<Picker.Item label="Pro - $49.99/month" value="price_pro" />
<Picker.Item
label="Enterprise - $99.99/month"
value="price_enterprise"
/>
</Picker>
<TouchableOpacity style={styles.button} onPress={handleUpgrade}>
<Text style={styles.buttonText}>Upgrade Now</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 16,
},
title: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 16,
},
picker: {
marginBottom: 16,
},
button: {
backgroundColor: "#007AFF",
padding: 16,
borderRadius: 8,
alignItems: "center",
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});
cancelSubscription
Cancel a subscription at the end of the current billing period.
const { cancelSubscription } = useEasy();
const result = await cancelSubscription("sub_123");
Parameters
subscriptionId(string, required): The ID of the subscription to cancel
Returns
ApiResponse<SubscriptionData>;
The subscription will remain active until the end of the current billing period. The customer retains access until that date.
Example
import { useEasy } from "@easylabs/react-native";
import { useState } from "react";
import { TouchableOpacity, Text, StyleSheet, Alert } from "react-native";
function CancelSubscriptionButton({ subscriptionId, onCancelled }) {
const { cancelSubscription } = useEasy();
const [confirming, setConfirming] = useState(false);
const handleCancel = async () => {
if (!confirming) {
setConfirming(true);
setTimeout(() => setConfirming(false), 5000);
return;
}
try {
const result = await cancelSubscription(subscriptionId);
Alert.alert("Success", "Subscription cancelled");
onCancelled();
} catch (error) {
Alert.alert("Error", "Failed to cancel subscription");
}
};
return (
<TouchableOpacity style={styles.button} onPress={handleCancel}>
<Text style={styles.buttonText}>
{confirming
? "Tap again to confirm cancellation"
: "Cancel Subscription"}
</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: "#F44336",
padding: 16,
borderRadius: 8,
alignItems: "center",
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});
reactivateSubscription
Reactivate a cancelled subscription before the period ends.
const { reactivateSubscription } = useEasy();
const result = await reactivateSubscription("sub_123");
Parameters
subscriptionId(string, required): The ID of the subscription to reactivate
Returns
ApiResponse<SubscriptionData>;
This only works if the subscription was cancelled but the current billing period hasn't ended yet. After the period ends, you'll need to create a new subscription.
Example
import { useEasy } from "@easylabs/react-native";
import { TouchableOpacity, Text, StyleSheet, Alert } from "react-native";
function ReactivateSubscriptionButton({ subscriptionId }) {
const { reactivateSubscription } = useEasy();
const handleReactivate = async () => {
try {
const result = await reactivateSubscription(subscriptionId);
Alert.alert("Success", "Subscription reactivated!");
} catch (error) {
Alert.alert("Error", "Failed to reactivate subscription");
}
};
return (
<TouchableOpacity style={styles.button} onPress={handleReactivate}>
<Text style={styles.buttonText}>Reactivate Subscription</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: "#4CAF50",
padding: 16,
borderRadius: 8,
alignItems: "center",
},
buttonText: {
color: "#fff",
fontSize: 16,
fontWeight: "600",
},
});
Type Definitions
SubscriptionData
interface SubscriptionData {
id: string;
identity_id: string;
status: SubscriptionStatus;
items: SubscriptionItem[];
current_period_start: string;
current_period_end: string;
cancel_at_period_end: boolean;
canceled_at?: string;
ended_at?: string;
trial_start?: string;
trial_end?: string;
metadata?: Record<string, unknown>;
created_at: string;
updated_at: string;
}
SubscriptionStatus
type SubscriptionStatus =
| "active" // Subscription is active and billing
| "canceled" // Subscription has been cancelled
| "incomplete" // Initial payment failed
| "incomplete_expired" // Initial payment failed and grace period expired
| "past_due" // Payment failed but retrying
| "paused" // Subscription is paused
| "trialing" // In trial period
| "unpaid"; // Payment failed and retries exhausted
SubscriptionItem
interface SubscriptionItem {
id: string;
price_id: string;
quantity: number;
}
CreateSubscriptionParams
interface CreateSubscriptionParams {
identity_id: string;
items: Array<{
price_id: string;
quantity?: number;
}>;
trial_period_days?: number;
metadata?: Record<string, unknown>;
}
Subscription Lifecycle
┌─────────┐
│ Created │
└────┬────┘
│
▼
┌──────────┐ ┌──────────┐
│ Trialing │───▶│ Active │
└──────────┘ └────┬─────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────┐
│Past Due │ │ Canceled │ │ Paused │
└─────────┘ └──────────┘ └──────────┘
│ │
▼ ▼
┌────────┐ ┌────────┐
│ Unpaid │ │ Ended │
└────────┘ └────────┘
Common Patterns
Subscription with Trial
const result = await createSubscription({
identity_id: "cust_123",
items: [{ price_id: "price_monthly", quantity: 1 }],
trial_period_days: 14,
});
Multiple Items (Metered Billing)
const result = await createSubscription({
identity_id: "cust_123",
items: [
{ price_id: "price_base", quantity: 1 },
{ price_id: "price_addon1", quantity: 5 },
{ price_id: "price_addon2", quantity: 3 },
],
});
Upgrade/Downgrade
// Upgrade to higher tier
await updateSubscription("sub_123", {
items: [{ price_id: "price_premium", quantity: 1 }],
});
// Downgrade to lower tier
await updateSubscription("sub_123", {
items: [{ price_id: "price_basic", quantity: 1 }],
});
Error Handling
try {
const result = await createSubscription({ ... });
} catch (error) {
if (error.message.includes('payment method')) {
Alert.alert('Error', 'No valid payment method found');
} else if (error.message.includes('price')) {
Alert.alert('Error', 'Invalid price ID');
} else if (error.message.includes('customer')) {
Alert.alert('Error', 'Customer not found');
} else {
Alert.alert('Error', 'Subscription creation failed');
}
}
Best Practices
- Always show trial periods clearly to customers
- Send push notifications for subscription changes
- Handle failed payments gracefully with retry logic
- Allow easy cancellation to build trust
- Prorate upgrades/downgrades fairly
- Show upcoming charges before billing
- Keep payment methods updated to avoid failures