Skip to main content

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 ID
    • items (array, required): Array of subscription items
      • price_id (string): Price ID
      • quantity (number): Quantity (default: 1)
    • trial_period_days (number, optional): Number of days for trial period
    • metadata (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 update
  • params (required)
    • items (array, optional): New subscription items
    • metadata (object, optional): Updated metadata

Returns

ApiResponse<SubscriptionData>;
Subscription Changes

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>;
Cancellation Behavior

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>;
Reactivation

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

  1. Always show trial periods clearly to customers
  2. Send push notifications for subscription changes
  3. Handle failed payments gracefully with retry logic
  4. Allow easy cancellation to build trust
  5. Prorate upgrades/downgrades fairly
  6. Show upcoming charges before billing
  7. Keep payment methods updated to avoid failures

Next Steps