monetization
CHAPTER 41 / 69
읽기 약 2분
FUNCTION
구독 결제 연동: Stripe
핵심 개념
Next.js + Stripe로 글로벌 구독 결제를 구현한다 — Checkout Session부터 웹훅까지.
본문
Stripe 설정
npm install stripe @stripe/stripe-js.env.local:
STRIPE_SECRET_KEY=sk_test_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...1. Checkout Session 생성 (서버)
// app/api/checkout/route.ts
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: NextRequest) {
const { priceId, userId } = await req.json();
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{ price: priceId, quantity: 1 }],
success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`,
cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing?canceled=true`,
customer_email: undefined,
metadata: { userId },
// 한국 결제 추가 (Stripe Korea)
payment_method_options: {
card: { request_three_d_secure: 'any' },
},
});
return NextResponse.json({ url: session.url });
}2. 결제 버튼 (클라이언트)
'use client';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
export function SubscribeButton({ priceId }: { priceId: string }) {
const handleClick = async () => {
const res = await fetch('/api/checkout', {
method: 'POST',
body: JSON.stringify({ priceId, userId: 'current-user-id' }),
});
const { url } = await res.json();
window.location.href = url; // Stripe Checkout으로 리다이렉트
};
return <button onClick={handleClick}>구독하기</button>;
}3. 웹훅 — 결제 확인
// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { headers } from 'next/headers';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: Request) {
const body = await req.text();
const sig = (await headers()).get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return new Response('Webhook signature verification failed', { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object;
// 사용자 DB에 구독 활성화 표시
await activateSubscription(session.metadata!.userId, session.subscription as string);
break;
}
case 'customer.subscription.deleted': {
const sub = event.data.object;
await deactivateSubscription(sub.id);
break;
}
case 'invoice.payment_failed': {
// 결제 실패 → 사용자에게 이메일
await sendPaymentFailedEmail(event.data.object.customer_email!);
break;
}
}
return new Response('ok', { status: 200 });
}4. 구독 관리 (업/다운그레이드/해지)
// 플랜 변경
async function changePlan(subscriptionId: string, newPriceId: string) {
const subscription = await stripe.subscriptions.retrieve(subscriptionId);
await stripe.subscriptions.update(subscriptionId, {
items: [{ id: subscription.items.data[0].id, price: newPriceId }],
proration_behavior: 'create_prorations', // 비율 정산
});
}
// 해지 (다음 결제일까지 유지)
async function cancelAtPeriodEnd(subscriptionId: string) {
await stripe.subscriptions.update(subscriptionId, {
cancel_at_period_end: true,
});
}⚠️ 프로덕션 체크리스트
- ✅ 웹훅 시크릿 검증
- ✅ Idempotency Key — 중복 요청 방지
- ✅ 테스트 모드 카드 (4242...) → 라이브 전환 시 키 교체
- ✅ Stripe Radar — 사기 거래 자동 차단
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 Stripe 통합 코드의 웹훅 검증·idempotency·에러 핸들링을 분석하고 프로덕션 수준으로 개선해줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
Stripe + Next.js 14 App Router로 구독 결제 + 무료 체험 + 다운그레이드를 구현한 완성 코드(서버+클라이언트)를 보여줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 Stripe 대시보드 데이터를 분석해서 결제 실패율·해지 패턴·MRR 변화를 종합 리포트로 만들어줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 1인 SaaS — Stripe(글로벌) vs 토스페이먼츠(국내) 실무 채택 트렌드와 비용 차이를 솔직히 알려줘.
⭐ 이것만 기억하세요
구독 결제 연동: Stripe는 이 3가지만 확실히 잡으세요
1.Stripe Checkout Session으로 PCI-DSS 부담 없이 글로벌 결제를 구현할 수 있다
2.웹훅으로 결제 상태를 비동기 동기화하면 결제-DB 불일치 사고를 막을 수 있다
3.다음 챕터에서 한국 시장 특화 결제 — 토스페이먼츠를 다룬다
공유하기
진행도 41 / 69