As your users' needs evolve, so too might their subscription plans. Your application needs to gracefully handle updates to existing subscriptions, whether that's upgrading to a higher tier, downgrading, or changing billing intervals. Similarly, providing a clear and easy path for cancellations is crucial for customer satisfaction and retention.
Stripe provides robust APIs for managing subscription lifecycle events. We'll explore how to implement these functionalities in your Next.js application, ensuring a seamless experience for your customers.
When a user decides to change their subscription, you'll typically interact with the Stripe subscriptions API. The primary operation here is updating an existing subscription. You can modify fields like items (to change the price/plan), quantity, and proration_behavior.
The proration_behavior parameter is key to how Stripe handles immediate changes. Options include:
create_prorations: Stripe will create a prorated charge or credit immediately for the change. This is often the default and most common approach for upgrades.
none: Stripe will not create any prorations. The change will take effect at the next billing period.
Here's a conceptual example of how you might update a subscription on your backend:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
});
export async function POST(request) {
const { subscriptionId, newPriceId, currentQuantity } = await request.json();
try {
const subscription = await stripe.subscriptions.update(subscriptionId, {
items: [
{
id: subscriptionId, // If you're changing the price of an existing item
price: newPriceId,
quantity: currentQuantity,
},
],
proration_behavior: 'create_prorations',
});
return new Response(JSON.stringify({ subscription }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('Error updating subscription:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
}In your Next.js frontend, you would capture the user's desired plan and quantity, then send this information to your API route to perform the update.
Cancellation is another critical part of the subscription lifecycle. Stripe allows you to cancel a subscription, and you can control when that cancellation takes effect.
You can use the cancelAtPeriodEnd option. If set to true, the subscription will continue until the end of its current billing period and then be canceled. If set to false (or omitted), the subscription is canceled immediately.
Here's how you can implement a cancellation endpoint:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: '2022-11-15',
});
export async function POST(request) {
const { subscriptionId } = await request.json();
try {
const deletedSubscription = await stripe.subscriptions.del(subscriptionId, {
prorate: false, // Set to true if you want immediate prorated refund
});
// Alternatively, to cancel at period end:
// const subscription = await stripe.subscriptions.update(subscriptionId, {
// cancel_at_period_end: true,
// });
return new Response(JSON.stringify({ deletedSubscription }), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('Error cancelling subscription:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: {
'Content-Type': 'application/json',
},
});
}
}On the frontend, you would provide a "Cancel Subscription" button. When clicked, this button would call your backend API to initiate the cancellation process.
While direct API calls are useful for initiating changes, it's essential to listen to Stripe webhooks for authoritative updates on subscription status. This ensures your application state is always in sync with Stripe's.
Key webhook events to monitor include:
customer.subscription.updated: Fired when a subscription is updated, including changes in plan, quantity, or billing cycle.customer.subscription.deleted: Fired when a subscription is canceled (either immediately or at the end of the period).invoice.paid: Useful for confirming successful recurring payments.invoice.payment_failed: Crucial for handling payment failures and informing your users.
Your webhook handler should verify the signature of incoming requests from Stripe for security. Then, based on the event type, you'll update your database to reflect the subscription's new state, perhaps marking a user as "canceled" or updating their active plan details.
graph TD
A[User Initiates Update/Cancel] --> B(Frontend sends request to Next.js API)
B --> C{Update/Cancel Subscription via Stripe API}
C --> D{Stripe processes request}
D -- Subscription Updated/Canceled --> E[Stripe sends webhook event]
E --> F(Next.js webhook handler receives event)
F --> G[Verify Stripe Signature]
G -- Signature Valid --> H{Update internal database (user plan, status)}
H --> I[Confirmation to Frontend]
By combining direct API interactions for user-initiated actions with webhook listeners for asynchronous updates, you can build a robust and reliable subscription management system in your Next.js application.