티스토리 뷰

반응형

Stripe 구독(Subscription) + 사용자 인증(Auth) + AI 기능 제한까지

실제 SaaS 완성 구조 만들기
(NestJS + Next.js + Stripe + RAG/LLM 권한 제어)


이전 글까지 우리는

  • Next.js 프론트
  • NestJS 백엔드
  • Kubernetes 자동배포
  • Terraform IaC
  • Cloudflare CDN
  • AI 기능(RAG + 이미지 임베딩)

까지 실제 SaaS의 거의 모든 백엔드 기능을 구축했다.

이번 글은 마지막 퍼즐이다.

“실제 돈을 받는 SaaS 구조를 만들려면
반드시 필요한 3가지: 인증 + 결제 + 권한제어

이번 편에서는 Stripe 기반의 구독 모델을 실제로 NestJS와 Next.js에 붙이고,
구독 플랜에 따라 AI 기능 제한(쿼터/요금제별 기능 제공) 하는 SaaS 구조를 완성한다.


🧭 이번 글에서 완성할 기능

기능 설명

🔐 사용자 인증 JWT 기반 Login · Signup
💳 Stripe 구독 결제 Basic / Pro / Enterprise 요금제
🔁 Stripe Webhook 결제 성공 → DB 반영
🧱 Role/Plan 시스템 Free / Pro / Enterprise
🚦 AI 기능 제한 하루 사용량 제한, 기능 잠금
🌐 Next.js UI 통합 결제 페이지 + 구독 상태 표시

실제 SaaS가 되는 순간이다.


1️⃣ Stripe 기본 설정

① Stripe 설치

백엔드(NestJS)

npm install stripe

프론트(Next.js)

npm install @stripe/stripe-js

② Stripe 대시보드에서 Product / Price 생성

반응형

예시 요금제:

Plan Price(월) 제한

Free 0원 AI 요청 하루 5회
Pro 9,900원 AI 요청 200회
Enterprise custom 무제한

각 플랜마다 Price ID를 발급받는다:

price_123_basic
price_456_pro
price_789_enterprise

2️⃣ NestJS 인증(Auth) 구축

DTO

export class SignupDto {
  email: string;
  password: string;
  name: string;
}

Service

async signup(data: SignupDto) {
  const hash = await bcrypt.hash(data.password, 10);

  return this.prisma.user.create({
    data: {
      email: data.email,
      password: hash,
      plan: 'FREE',   // 신규 유저 기본 플랜
      ai_limit: 5,    // 기본 사용량
    },
  });
}

Login → JWT 발급

async login(email: string, pass: string) {
  const user = await this.prisma.user.findUnique({ where: { email }});

  const ok = await bcrypt.compare(pass, user.password);
  if (!ok) throw new UnauthorizedException();

  const payload = { userId: user.id, plan: user.plan };
  const token = await this.jwt.signAsync(payload);

  return { token };
}

3️⃣ Stripe Checkout Session 생성 (NestJS)

import Stripe from 'stripe';

@Injectable()
export class BillingService {
  private stripe = new Stripe(process.env.STRIPE_KEY!, {
    apiVersion: '2023-10-16',
  });

  async createCheckoutSession(userId: number, priceId: string) {
    return this.stripe.checkout.sessions.create({
      mode: 'subscription',
      customer_email: await this.getUserEmail(userId),
      line_items: [{ price: priceId, quantity: 1 }],
      success_url: `${process.env.FRONT_URL}/billing/success`,
      cancel_url: `${process.env.FRONT_URL}/billing/cancel`,
      metadata: { userId },
    });
  }
}

Next.js에서 이 API를 호출하면 Stripe 결제창이 뜬다.


4️⃣ Stripe Webhook으로 구독 상태 업데이트

Stripe Webhook은 SaaS의 핵심이다.
Stripe → 우리 서버 로직이 이벤트 기반으로 자동 반응하도록 만드는 장치이기 때문이다.


NestJS Webhook 엔드포인트

@Post('webhook')
@RawBody()  // NestJS 설정 필요
async webhook(@Req() req: Request) {
  const sig = req.headers['stripe-signature'];

  const event = this.stripe.webhooks.constructEvent(
    req.body,
    sig,
    process.env.STRIPE_WEBHOOK_SECRET
  );

  switch (event.type) {
    case 'customer.subscription.created':
    case 'customer.subscription.updated':
      await this.handleSubscription(event.data.object);
      break;
    case 'invoice.payment_failed':
      await this.handlePaymentFailed(event.data.object);
      break;
  }

  return { received: true };
}

Webhook 로직 (plan + ai_limit 업데이트)

async handleSubscription(sub: Stripe.Subscription) {
  const userId = Number(sub.metadata.userId);

  const plan =
    sub.items.data[0].price.id === 'price_basic'
      ? 'PRO'
      : 'ENTERPRISE';

  await this.prisma.user.update({
    where: { id: userId },
    data: {
      plan,
      ai_limit: plan === 'PRO' ? 200 : 99999999,
    },
  });
}

결제 → 자동으로 DB 업데이트 → 즉시 플랜 활성화.


5️⃣ AI 기능 제한 로직

AI API 요청 시마다 유저의 티어를 확인해야 한다.


NestJS Guard 생성

@Injectable()
export class AiRateLimitGuard implements CanActivate {
  constructor(private prisma: PrismaService) {}

  async canActivate(ctx: ExecutionContext) {
    const req = ctx.switchToHttp().getRequest();
    const user = req.user;

    const dbUser = await this.prisma.user.findUnique({
      where: { id: user.userId },
    });

    if (dbUser.ai_limit <= 0) {
      throw new ForbiddenException("AI 사용량이 모두 소진되었습니다.");
    }

    // 사용량 감소
    await this.prisma.user.update({
      where: { id: user.userId },
      data: { ai_limit: dbUser.ai_limit - 1 },
    });

    return true;
  }
}

적용

@UseGuards(JwtAuthGuard, AiRateLimitGuard)
@Post('chat')
async chat(@Body('prompt') prompt: string) {
  return this.aiService.chat(prompt);
}

무료 유저는 하루 5회, Pro는 200회 제한이 자동 적용된다.


6️⃣ Next.js UI – 요금제 화면

요금제 선택 화면 예시:

export default function Pricing() {
  return (
    <div>
      <h1>요금제</h1>

      <div>
        <h2>FREE</h2>
        <p>AI 5회/일</p>
        <button>기본 제공</button>
      </div>

      <div>
        <h2>PRO</h2>
        <p>월 9,900원 / AI 200회</p>
        <a href="/billing/checkout?price=price_basic">구독하기</a>
      </div>
    </div>
  );
}

클릭하면 Next.js route → NestJS API → Stripe Checkout으로 연결된다.


7️⃣ 최종 전체 아키텍처

                          [Next.js]
                              │
                              ▼
                      /billing/checkout
                              │
                              ▼
                       [NestJS Billing API]
                      create checkout session
                              │
                              ▼
                           Stripe UI
                              │
          ┌───────────────────┴────────────────────┐
          ▼                                        ▼
 Stripe Webhook                          Stripe Dashboard
 (subscription created/updated)                 |
          │                                    |
          ▼                                    ▼
  Update User Plan in DB (Prisma)       Admin 자동화 가능
          │
          ▼
 User plan → AI limit 변경
          │
          ▼
 AI 요청 시 Guard로 제한 적용

8️⃣ 이번 글 요약

기능 완료

Stripe Checkout ✔️
Webhook 처리 ✔️
구독 플랜 관리 ✔️
AI 기능 제한 ✔️
프론트 UI 연동 ✔️
JWT 인증 기반 사용자 시스템 ✔️

이제 만들어진 프로젝트는 진짜로 상용 SaaS 서비스의 구조를 가진다.


 

Stripe, SaaS, Subscription, NestJS, Next.js, JWT, Webhook, AI요금제, RateLimit, RAG, VectorDB

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함
반응형