2025-07-07 Web Development, Ecommerce
Stripe Subscriptions & Multi-Currency in Next.js 15
By O. Wolfson
This article expands on our Stripe + Next.js 15 integration, showing how to support recurring payments (subscriptions) and multi-currency checkout. You’ll learn best practices, see example code, and understand the tradeoffs in international SaaS/e-commerce pricing.
Table of Contents
- Overview
- Stripe Subscriptions: How They Work
- Adding a Subscription Checkout Session
- Multi-Currency Support
- Example: Multi-Currency Subscription Flow
- Appendix: Useful Stripe Dashboard Tips
1. Overview
With the basics in place (one-time payments using Stripe Elements), most modern apps will want to support:
- Recurring subscriptions (e.g., memberships, SaaS plans)
- Multi-currency (localized pricing for US, EU, Asia, etc.)
Stripe supports both with a secure, scalable API. Here’s how to add these features to your Next.js 15 (App Router) project.
2. Stripe Subscriptions: How They Work
- Subscriptions are powered by recurring Prices attached to a Product in Stripe.
- The recommended flow is to redirect the user to Stripe Checkout (
mode: "subscription"
). - Stripe handles billing, tax, SCA, invoices, retries, and more.
- You can manage and sync subscription status using Stripe webhooks.
3. Adding a Subscription Checkout Session
A. Create Subscription Prices in Stripe:
- Go to Products in the Stripe Dashboard.
- Add a new product (e.g., "Pro Membership").
- Add one or more Prices, each with
Type: Recurring
and a currency (USD, THB, EUR, etc.). - Each price has a unique Price ID.
B. API Route for Subscription Checkout:
Create a new API route, e.g. app/api/create-subscription-session/route.ts
:
tsimport { NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(request: Request) {
const { priceId, customerEmail } = await request.json();
try {
const session = await stripe.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card"],
line_items: [{ price: priceId, quantity: 1 }],
success_url:
"https://yourdomain.com/success?session_id={CHECKOUT_SESSION_ID}",
cancel_url: "https://yourdomain.com/cancel",
customer_email: customerEmail,
});
return NextResponse.json({ url: session.url });
} catch (err: any) {
return NextResponse.json({ error: err.message }, { status: 400 });
}
}
C. Client Component to Subscribe:
tsx"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
export default function SubscribeButton({
priceId,
customerEmail,
}: {
priceId: string;
customerEmail?: string;
}) {
const [loading, setLoading] = useState(false);
const handleSubscribe = async () => {
setLoading(true);
const res = await fetch("/api/create-subscription-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ priceId, customerEmail }),
});
const { url, error } = await res.json();
setLoading(false);
if (url) {
window.location.href = url;
} else {
alert(error ?? "Subscription failed.");
}
};
return (
<Button onClick={handleSubscribe} disabled={loading}>
{loading ? "Redirecting..." : "Subscribe"}
</Button>
);
}
4. Multi-Currency Support
A. How Stripe Multi-Currency Works
- Each Price in Stripe has a fixed currency (USD, EUR, THB, etc.).
- You can add multiple prices (one per region/currency) to a single product.
- At checkout, pass the correct Price ID (and thus currency) depending on the user’s region or preference.
- Stripe handles all currency-specific processing and conversion.
B. Adding Multi-Currency to Your UI
-
Create all needed prices in Stripe:
- E.g., "Pro Plan, USD" (
price_xxx_usd
) - "Pro Plan, THB" (
price_xxx_thb
)
- E.g., "Pro Plan, USD" (
-
Let users choose their currency (dropdown/radio):
tsxconst priceIds = {
usd: "price_xxx_usd",
thb: "price_xxx_thb",
// more...
};
const [currency, setCurrency] = useState<"usd" | "thb">("usd");
<SubscribeButton priceId={priceIds[currency]} />;
- Display the correct price/currency label in the button/UI.
5. Example: Multi-Currency Subscription Flow
tsx"use client";
import { useState } from "react";
import SubscribeButton from "@/components/subscribe-button";
const priceIds = {
usd: "price_xxx_usd",
thb: "price_xxx_thb",
};
export default function SubscriptionPage() {
const [currency, setCurrency] = useState<"usd" | "thb">("usd");
return (
<div>
<h1>Choose your plan</h1>
<div>
<label>
<input
type="radio"
value="usd"
checked={currency === "usd"}
onChange={() => setCurrency("usd")}
/>
USD $10/month
</label>
<label>
<input
type="radio"
value="thb"
checked={currency === "thb"}
onChange={() => setCurrency("thb")}
/>
THB 350/month
</label>
</div>
<SubscribeButton priceId={priceIds[currency]} />
</div>
);
}
6. Appendix: Useful Stripe Dashboard Tips
- Adding new prices: In your product, click “Add price”, choose currency and amount.
- Copying Price IDs: On the price row, click the three dots > Copy Price ID.
- Test with test cards: Same as with one-time payments.
- Webhooks: For production apps, listen for
customer.subscription.created
,updated
,deleted
events to update user status in your DB.
Summary
- Subscriptions are built around recurring Prices. Use Stripe Checkout for secure, best-practice flows.
- Multi-currency is handled by creating separate prices for each currency/region and letting the user (or your logic) select the right Price ID at checkout.
- Stripe does all the heavy lifting for payment, compliance, currency, and security.
Questions or need code for specific regions, currencies, or subscription models? Just ask.