How to Integrate Payment Systems (Stripe): Claude Code 활용 가이드
integrate payment systems (stripe): Claude Code 활용. 실용적인 코드 예시와 단계별 가이드를 포함합니다.
決済システムの구현にClaude Code를 사용하는メリット
決済システムは보안と正確性が求められる重要な機能です。Claude Code는 Stripeの모범 사례に沿った구현を생성し、Webhook処理や에러 핸들링の見落としを防ぎます。
Stripe Checkout에 의한決済
> Stripe Checkout를 사용한サブスクリプション決済를 구현해줘。
> フリー・プロ・엔터프라이즈の3プランで。
> 성공・취소時のリダイレクトも설정して。
チェックアウト세션の생성
// src/services/payment-service.ts
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2024-12-18.acacia',
});
const PLANS = {
pro: {
priceId: process.env.STRIPE_PRO_PRICE_ID!,
name: 'Pro',
},
enterprise: {
priceId: process.env.STRIPE_ENTERPRISE_PRICE_ID!,
name: 'Enterprise',
},
} as const;
type PlanKey = keyof typeof PLANS;
export class PaymentService {
async createCheckoutSession(userId: string, plan: PlanKey) {
// 既存のStripe顧客を검색、하지 않으면생성
let customer = await this.findOrCreateCustomer(userId);
const session = await stripe.checkout.sessions.create({
customer: customer.id,
mode: 'subscription',
payment_method_types: ['card'],
line_items: [
{
price: PLANS[plan].priceId,
quantity: 1,
},
],
success_url: `${process.env.APP_URL}/payment/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/pricing`,
metadata: { userId, plan },
subscription_data: {
trial_period_days: 14,
},
});
return { url: session.url };
}
async createPortalSession(userId: string) {
const customer = await this.getCustomerByUserId(userId);
const session = await stripe.billingPortal.sessions.create({
customer: customer.id,
return_url: `${process.env.APP_URL}/settings/billing`,
});
return { url: session.url };
}
private async findOrCreateCustomer(userId: string) {
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user) throw new Error('User not found');
if (user.stripeCustomerId) {
return stripe.customers.retrieve(user.stripeCustomerId) as Promise<Stripe.Customer>;
}
const customer = await stripe.customers.create({
email: user.email,
metadata: { userId },
});
await prisma.user.update({
where: { id: userId },
data: { stripeCustomerId: customer.id },
});
return customer;
}
private async getCustomerByUserId(userId: string) {
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user?.stripeCustomerId) throw new Error('No Stripe customer found');
return stripe.customers.retrieve(user.stripeCustomerId) as Promise<Stripe.Customer>;
}
}
APIルートの설정
// src/app/api/checkout/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PaymentService } from '@/services/payment-service';
import { getSession } from '@/lib/auth';
const paymentService = new PaymentService();
export async function POST(req: NextRequest) {
const session = await getSession();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { plan } = await req.json();
if (!['pro', 'enterprise'].includes(plan)) {
return NextResponse.json({ error: 'Invalid plan' }, { status: 400 });
}
const checkout = await paymentService.createCheckoutSession(session.userId, plan);
return NextResponse.json(checkout);
}
Webhook処理
> Stripeのwebhook핸들러ー를 구현해줘。
> サブスクリプションの생성・업데이트・취소を処理して。
> 署名검증も구현して。
// src/app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';
import { prisma } from '@/lib/db';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
export async function POST(req: NextRequest) {
const body = await req.text();
const signature = req.headers.get('stripe-signature')!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
console.error('Webhook signature verification failed');
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session;
await handleCheckoutCompleted(session);
break;
}
case 'customer.subscription.updated': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionUpdated(subscription);
break;
}
case 'customer.subscription.deleted': {
const subscription = event.data.object as Stripe.Subscription;
await handleSubscriptionCanceled(subscription);
break;
}
case 'invoice.payment_failed': {
const invoice = event.data.object as Stripe.Invoice;
await handlePaymentFailed(invoice);
break;
}
}
return NextResponse.json({ received: true });
}
async function handleCheckoutCompleted(session: Stripe.Checkout.Session) {
const userId = session.metadata?.userId;
if (!userId) return;
await prisma.user.update({
where: { id: userId },
data: {
plan: session.metadata?.plan || 'pro',
stripeSubscriptionId: session.subscription as string,
subscriptionStatus: 'active',
},
});
}
async function handleSubscriptionUpdated(subscription: Stripe.Subscription) {
const user = await prisma.user.findFirst({
where: { stripeSubscriptionId: subscription.id },
});
if (!user) return;
await prisma.user.update({
where: { id: user.id },
data: { subscriptionStatus: subscription.status },
});
}
async function handleSubscriptionCanceled(subscription: Stripe.Subscription) {
const user = await prisma.user.findFirst({
where: { stripeSubscriptionId: subscription.id },
});
if (!user) return;
await prisma.user.update({
where: { id: user.id },
data: {
plan: 'free',
subscriptionStatus: 'canceled',
stripeSubscriptionId: null,
},
});
}
async function handlePaymentFailed(invoice: Stripe.Invoice) {
const customerId = invoice.customer as string;
const user = await prisma.user.findFirst({
where: { stripeCustomerId: customerId },
});
if (!user) return;
// 支払い실패の알림メールを전송
await sendPaymentFailedEmail(user.email);
}
料金表컴포넌트
function PricingTable() {
const handleCheckout = async (plan: string) => {
const res = await fetch('/api/checkout', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ plan }),
});
const { url } = await res.json();
window.location.href = url;
};
return (
<div className="grid gap-6 md:grid-cols-3">
<PricingCard
name="Free"
price="$0"
features={['基本機能', '月5プロジェクト']}
buttonText="現在のプラン"
disabled
/>
<PricingCard
name="Pro"
price="$1,980/月"
features={['全機能', '無制限プロジェクト', '優先サポート']}
buttonText="Proにアップグレード"
onSelect={() => handleCheckout('pro')}
highlighted
/>
<PricingCard
name="Enterprise"
price="$9,800/月"
features={['全機能', 'SSO', '専任サポート', 'SLA保証']}
buttonText="お問い合わせ"
onSelect={() => handleCheckout('enterprise')}
/>
</div>
);
}
정리
Claude Code를 활용하면 Stripe決済の통합をチェックアウトからWebhook処理まで安全かつ효율적으로구현할 수 있습니다。決済は보안に直結するため、コードレビューは必ず行いましょう。프로젝트の決済方針はCLAUDE.mdに記述しておくことをお勧めします。コード品質の維持には리팩터링の자동화も활용して주세요。
Claude Code의 상세 정보는Anthropic공식 문서를 확인하세요.Stripeの구현가이드はStripe공식 문서도 참고하세요.
Claude Code 워크플로우를 한 단계 업그레이드하세요
지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code로 리팩토링을 자동화하는 방법
Claude Code를 활용해 코드 리팩토링을 효율적으로 자동화하는 방법을 알아봅니다. 실전 프롬프트와 구체적인 리팩토링 패턴을 소개합니다.
Claude Code로 사이드 프로젝트 개발 속도를 극대화하는 방법 [예제 포함]
Claude Code를 활용해 개인 프로젝트 개발 속도를 획기적으로 높이는 방법을 알아봅니다. 실전 예제와 아이디어부터 배포까지의 워크플로를 포함합니다.
Complete CORS Configuration Guide: Claude Code 활용 가이드
complete cors configuration guide: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.