The Complete Stripe Guide | Payments, Checkout, Webhooks, Subscriptions, Production Use

The Complete Stripe Guide | Payments, Checkout, Webhooks, Subscriptions, Production Use

What this post covers

This is a complete guide to building a payment system with Stripe. It walks through Checkout Session, Payment Intent, Webhooks, and subscription billing with practical examples.

From the field: After migrating an in-house payment stack to Stripe, development time dropped by about 80% and payment success rates improved by about 15%.

Introduction: “Payments are hard to implement”

Real-world scenarios

Scenario 1: Security concerns

Handling card data directly is risky. Stripe provides PCI DSS compliance. Scenario 2: I need many payment methods

Integrating each one separately is complex. Stripe offers a unified API. Scenario 3: I need subscription billing

Building it from scratch is difficult. Stripe provides a subscription system.


1. What is Stripe?

Core characteristics

Stripe is an online payments platform. Key capabilities:

  • Checkout: Hosted payment page
  • Payment Intent: Custom payment flows
  • Webhook: Event notifications
  • Subscription: Recurring billing
  • Multiple payment methods: Cards, Apple Pay, Google Pay

2. Installation and setup

Install

npm install stripe @stripe/stripe-js

Environment variables

STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Server initialization

// lib/stripe.ts
import Stripe from 'stripe';
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2023-10-16',
});

3. Checkout Session

Server (API Route)

// app/api/checkout/route.ts
import { stripe } from '@/lib/stripe';
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
  const { priceId } = await req.json();
  const session = await stripe.checkout.sessions.create({
    mode: 'payment',
    line_items: [
      {
        price: priceId,
        quantity: 1,
      },
    ],
    success_url: `${req.headers.get('origin')}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${req.headers.get('origin')}/cancel`,
  });
  return NextResponse.json({ url: session.url });
}

Client

// components/CheckoutButton.tsx
'use client';
export default function CheckoutButton({ priceId }: { priceId: string }) {
  const handleCheckout = async () => {
    const response = await fetch('/api/checkout', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ priceId }),
    });
    const { url } = await response.json();
    window.location.href = url;
  };
  return <button onClick={handleCheckout}>Checkout</button>;
}

4. Payment Intent

Server

The example below uses TypeScript and async/await. Review each part to understand its role.

// app/api/payment-intent/route.ts
export async function POST(req: Request) {
  const { amount } = await req.json();
  const paymentIntent = await stripe.paymentIntents.create({
    amount: amount * 100, // cents
    currency: 'usd',
    automatic_payment_methods: {
      enabled: true,
    },
  });
  return NextResponse.json({ clientSecret: paymentIntent.client_secret });
}

Client

The client uses TypeScript, React hooks, and Stripe Elements. It fetches a clientSecret, wraps the form in Elements, and confirms the payment with error handling.

'use client';
import { loadStripe } from '@stripe/stripe-js';
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { useState, useEffect } from 'react';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
function CheckoutForm() {
  const stripe = useStripe();
  const elements = useElements();
  const [message, setMessage] = useState('');
  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!stripe || !elements) return;
    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        return_url: `${window.location.origin}/success`,
      },
    });
    if (error) {
      setMessage(error.message || 'An error occurred');
    }
  };
  return (
    <form onSubmit={handleSubmit}>
      <PaymentElement />
      <button type="submit" disabled={!stripe}>
        Pay
      </button>
      {message && <div>{message}</div>}
    </form>
  );
}
export default function CheckoutPage() {
  const [clientSecret, setClientSecret] = useState('');
  useEffect(() => {
    fetch('/api/payment-intent', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ amount: 50 }),
    })
      .then((res) => res.json())
      .then((data) => setClientSecret(data.clientSecret));
  }, []);
  return (
    <div>
      {clientSecret && (
        <Elements stripe={stripePromise} options={{ clientSecret }}>
          <CheckoutForm />
        </Elements>
      )}
    </div>
  );
}

5. Webhook

Server

Verify the Stripe signature on the raw body, then branch on event.type to fulfill orders or log outcomes.

// app/api/webhook/route.ts
import { stripe } from '@/lib/stripe';
import { headers } from 'next/headers';
export async function POST(req: Request) {
  const body = await req.text();
  const signature = headers().get('stripe-signature')!;
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return new Response(`Webhook Error: ${err.message}`, { status: 400 });
  }
  switch (event.type) {
    case 'checkout.session.completed':
      const session = event.data.object;
      console.log('Payment successful:', session.id);
      await fulfillOrder(session);
      break;
    case 'payment_intent.succeeded':
      const paymentIntent = event.data.object;
      console.log('PaymentIntent succeeded:', paymentIntent.id);
      break;
    case 'payment_intent.payment_failed':
      const failedPayment = event.data.object;
      console.log('Payment failed:', failedPayment.id);
      break;
  }
  return new Response(JSON.stringify({ received: true }));
}

Registering webhooks

# Local testing
stripe listen --forward-to localhost:3000/api/webhook
# Production
# Stripe Dashboard → Webhooks → Add endpoint

6. Subscription payments

Creating products and prices

Create products and prices in the Stripe Dashboard or via the API.

// Create in Stripe Dashboard or via API
const product = await stripe.products.create({
  name: 'Premium Plan',
});
const price = await stripe.prices.create({
  product: product.id,
  unit_amount: 1999, // $19.99
  currency: 'usd',
  recurring: {
    interval: 'month',
  },
});

Subscription Checkout

const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [
    {
      price: 'price_xxx',
      quantity: 1,
    },
  ],
  success_url: `${origin}/success`,
  cancel_url: `${origin}/cancel`,
});

Managing subscriptions

// Cancel subscription
await stripe.subscriptions.cancel('sub_xxx');
// Update subscription
await stripe.subscriptions.update('sub_xxx', {
  items: [
    {
      id: 'si_xxx',
      price: 'price_new',
    },
  ],
});

Summary and checklist

Key takeaways

  • Stripe: Online payments platform
  • Checkout: Hosted payment page
  • Payment Intent: Custom flows
  • Webhook: Event notifications
  • Subscription: Recurring billing
  • Security: PCI DSS compliance

Implementation checklist

  • Create a Stripe account
  • Install the SDK
  • Implement Checkout
  • Configure Webhooks
  • Implement subscription billing
  • Test
  • Deploy to production

  • Next.js App Router guide
  • The complete Supabase guide
  • The complete Webhook guide

Keywords in this post

Stripe, Payment, Checkout, Webhook, Subscription, Backend, E-commerce

Frequently asked questions (FAQ)

Q. What are the fees?

A. Domestic cards: 3.6% + ₩50; international cards: 4.3% + ₩50.

Q. Can I use Stripe in Korea?

A. Yes. Korean businesses can use Stripe where the product is available for your entity type.

Q. How do I test?

A. Use test mode and Stripe’s test card numbers.

Q. Is it safe for production?

A. Yes. Millions of businesses worldwide rely on Stripe as a stable payments platform.