Payments
This guide explains how to implement and handle payments in our application using Stripe for secure payment processing and subscription management.
Setup
Prerequisites
- Stripe account
- Configured products and prices in Stripe Dashboard
- Webhook endpoint setup
Installation
npm install stripe
Environment Variables
# Stripe Configuration
STRIPE_SECRET_KEY=your_stripe_secret_key
STRIPE_WEBHOOK_SECRET=your_webhook_secret
STRIPE_STARTER_PRICE_ID=price_id_for_starter_plan
STRIPE_PRO_PRICE_ID=price_id_for_pro_plan
Implementation
Stripe Client Setup
Configure the Stripe client in lib/stripe.ts:
import Stripe from "stripe"
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? "", {
apiVersion: "2024-06-20",
typescript: true,
})
Payment Button Component
Reusable component for initiating Stripe checkout:
export function StripeButton({
type,
text = "Buy",
variant = "default",
from = "landing",
size = "default",
icon,
className,
}: StripeButtonProps) {
const [isLoading, setIsLoading] = useState<boolean>(false)
const createBillingSession = async () => {
try {
setIsLoading(true)
const data = await stripeRedirect({ type, from })
window.location.href = data
} catch (error: any) {
toast.error(error?.message || "An error occurred")
} finally {
setIsLoading(false)
}
}
return (
<Button
variant={variant}
className={cn("w-full", className)}
onClick={createBillingSession}
disabled={isLoading}
size={size}
>
{text}
{icon && <Icon className="ml-2 size-4 shrink-0" />}
</Button>
)
}
Stripe Redirect Handler
Server action to create Stripe checkout sessions:
export async function stripeRedirect({ type, from = "landing" }: stripeRedirectProps) {
// Verify environment and user
if (!process.env.NEXT_PUBLIC_APP_URL) {
throw new Error("NEXT_PUBLIC_APP_URL is not defined")
}
try {
const successUrl = absoluteUrl(`${createRoute("success").href}?from=${from}`)
const cancelUrl = absoluteUrl(`${createRoute("cancel").href}?from=${from}`)
const plan = PLANS.find((plan) => plan.type === type)
if (!plan?.price.priceIds.production) {
throw new Error("Plan not found or price not configured")
}
const stripeSession = await stripe.checkout.sessions.create({
success_url: successUrl + "&session_id={CHECKOUT_SESSION_ID}",
cancel_url: cancelUrl,
payment_method_types: ["card"],
mode: "payment",
billing_address_collection: "auto",
customer_email: from === "dashboard" ? user?.email : undefined,
line_items: [
{
quantity: 1,
price: plan.price.priceIds.production,
},
],
metadata: {
userId: from === "dashboard" ? user!.id : null,
type: type,
planName: plan.name,
},
})
return stripeSession.url || ""
} catch (error: any) {
throw new Error(error)
}
}
Features
Multiple Plans
Support for different pricing tiers and subscription plans
Secure Checkout
PCI-compliant payment processing with Stripe Checkout
User Context
Different flows for authenticated and anonymous users
Metadata Tracking
Track plan types and user information in payments
Payment Flow
Initiate Payment
User clicks payment button, triggering stripeRedirect server action
Create Session
Server creates Stripe checkout session with plan details
Redirect to Checkout
User is redirected to Stripe's hosted checkout page
Process Payment
Stripe handles payment processing and security
Handle Result
User is redirected back with success or cancel status
Best Practices
Security
- Never log sensitive payment data
- Use webhook signatures for verification
- Implement proper error handling
- Validate payment status server-side
User Experience
- Show clear pricing information
- Handle loading states properly
- Provide clear error messages
- Implement proper success/cancel flows