Skip to main content

53장. 결제나 구독 기능 맛보기

[10부] 결제나 구독 기능 맛보기

[10부] 결제나 구독 기능 맛보기

[10부] 결제나 구독 기능 맛보기

돈을 받는 순간, 서비스는 조금 더 현실이 된다

회원가입과 로그인, 글 작성과 수정, 배포까지 해냈다면 이제 서비스는 "작동하는 웹앱"의 모양을 갖췄다. 여기서 한 발 더 나아가면 자연스럽게 이런 질문이 생긴다.

"이 서비스로 돈을 받을 수도 있을까?"

이 질문은 아주 중요하다. 다만 동시에 아주 위험하기도 하다. 결제를 붙이는 순간부터 서비스는 단순한 취미 프로젝트를 넘어, 사용자의 돈과 신뢰를 다루는 제품이 되기 때문이다. 버튼 하나 잘못 만들어도 "결제는 됐는데 기능은 안 열렸어요" 같은 일이 생길 수 있다.

그래서 초보자에게 가장 좋은 태도는 두 가지를 함께 가지는 것이다. 첫째, 결제 기능을 무서워하지 않는 것. 둘째, 결제를 가볍게 보지 않는 것이다.

이 장의 목표는 당장 완전한 상용 결제 시스템을 만드는 데 있지 않다. 대신 결제와 구독이 어떤 구조로 움직이는지, 언제 도입해야 하는지, 초보자는 어디까지 직접 만들고 어디부터 외부 서비스를 써야 하는지 감을 잡는 데 있다. 이 감각이 있으면 다음 서비스를 만들 때 선택지가 훨씬 넓어진다.

일회성 결제와 구독은 무엇이 다를까

결제 기능은 크게 두 종류로 나눠 생각하면 쉽다.

하나는 일회성 결제다. 한 번 돈을 내고 한 번 받는 구조다. 예를 들어 전자책 구매, 템플릿 다운로드, 강의 1회 결제 같은 방식이 여기에 가깝다.

다른 하나는 구독이다. 일정 주기마다 반복해서 요금이 청구된다. 월간 요금제, 연간 멤버십, SaaS 이용권이 대표적이다. 구독은 한 번의 결제로 끝나지 않는다. 다음 달에도 결제가 되고, 카드가 만료될 수도 있고, 사용자가 중간에 해지할 수도 있다. 그래서 일회성 결제보다 구조가 조금 더 길고 복잡하다.1

작은 웹 서비스를 처음 수익화할 때는 보통 다음처럼 나눠 생각하면 편하다.

  • 자료 하나를 파는 서비스라면 일회성 결제

  • 기능을 계속 제공하는 서비스라면 구독

  • 아직 사용자 반응을 확인하는 단계라면 무료 + 나중에 구독 검토

중요한 건 "돈을 받는다"보다 "무엇에 대해 돈을 받는가"를 먼저 분명히 하는 것이다. 사용자는 결제 버튼이 아니라, 계속 쓰고 싶은 가치에 돈을 낸다.

결제 기능의 기본 구조는 생각보다 단순하다

겉으로 보면 결제는 복잡해 보이지만, 흐름 자체는 몇 단계로 정리할 수 있다.

  1. 사용자가 가격 페이지에서 플랜을 본다.

  2. 원하는 플랜을 선택한다.

  3. 결제 서비스의 체크아웃 화면으로 이동한다.

  4. 결제가 성공하거나 실패한다.

  5. 결제 결과가 우리 서버와 데이터베이스에 반영된다.

  6. 그 결과에 따라 사용자 권한이 바뀐다.

  7. 이후 사용자는 해지, 요금제 변경, 결제수단 수정 같은 관리를 한다.

이 구조에서 초보자가 꼭 기억해야 할 핵심은, 결제 성공 후 돌아온 화면이 끝이 아니라는 점이다. 진짜 중요한 것은 결제 서비스가 보내주는 웹훅이다. 웹훅은 "무슨 일이 일어났는지"를 외부 결제 서비스가 우리 서버에 알려주는 신호라고 생각하면 된다. 예를 들어 결제 완료, 정기 결제 성공, 결제 실패, 구독 해지 같은 사건이 생기면 결제 서비스가 우리 서버로 알림을 보낸다.2

많은 초보자가 성공 페이지로 리다이렉트되면 모든 것이 끝났다고 생각한다. 하지만 실제 서비스에서는 브라우저가 닫히거나, 네트워크가 끊기거나, 사용자가 중간에 페이지를 이동할 수도 있다. 그래서 결제 이후 상태를 믿을 수 있게 반영하려면 웹훅 처리가 사실상 중심축이다.2

그리고 처음에는 카드 입력 폼을 직접 만들기보다, Stripe Checkout 같은 호스팅된 결제 페이지를 쓰는 편이 훨씬 안전하고 쉽다. 구독 관리도 직접 만들기보다 Customer Portal 같은 호스팅된 관리 화면을 쓰면 해지, 카드 변경, 영수증 확인 같은 기능을 빠르게 제공할 수 있다.3

우리 서비스에 붙인다면 어떤 모습일까

이 책에서 만든 서비스는 글 작성, 수정, 삭제, 로그인, 검색이 되는 작은 풀스택 웹 서비스다. 이런 서비스에 결제를 붙인다고 하면, 갑자기 거창한 비즈니스 모델을 만들 필요는 없다.

예를 들어 이런 식으로 생각할 수 있다.

  • 무료 사용자: 글 10개까지 작성 가능

  • 프로 사용자: 글 개수 제한 해제

  • 프로 사용자: 비공개 글 기능 사용 가능

  • 프로 사용자: 파일 첨부 용량 확대

  • 프로 사용자: 광고 제거 또는 우선 지원 제공

여기서 중요한 건 "유료 기능을 얼마나 많이 넣을까"가 아니라 "무료 사용자도 충분히 써볼 수 있게 하되, 자주 쓰는 사람은 업그레이드할 이유가 생기게 만들까"다.

즉, 결제는 기능 추가의 마지막 장식이 아니다. 서비스의 핵심 가치와 연결되어야 한다. 만약 사용자가 글 몇 개 써보지도 않았는데 바로 결제부터 요구하면, 그 서비스는 매장 입구에 계산대를 둔 셈이다. 아직 장바구니도 안 들었는데 말이다.

데이터베이스에는 무엇을 저장할까

결제 서비스가 모든 것을 기억해준다고 해서 우리 서비스 데이터베이스에 아무것도 저장하지 않아도 되는 것은 아니다. 실제 서비스에서는 자주 필요한 청구 정보를 우리 데이터베이스에도 저장해 두는 편이 좋다. 그래야 매번 외부 API를 호출하지 않아도 되고, 화면에서도 빠르게 상태를 보여줄 수 있다.4

보통은 다음과 같은 정보가 자주 필요하다.

  • 우리 서비스의 사용자 ID

  • 결제 서비스의 고객 ID

  • 구독 ID

  • 현재 구독 상태(active, trialing, past_due, canceled 등)

  • 현재 결제 주기의 종료일

  • 어떤 가격제 또는 플랜을 사용 중인지

  • 기간 종료 후 해지 여부

Supabase를 쓴다면 users, customers, prices, subscriptions 같은 테이블을 나눠 관리하는 방식이 자주 사용된다. 특히 우리 사용자와 결제 서비스의 customer ID를 연결하는 테이블이 매우 중요하다. 그래야 웹훅이 들어왔을 때 "이 결제 이벤트가 우리 서비스의 누구 것인지" 정확히 찾을 수 있다.5

여기서 기억할 점이 하나 있다. 결제 서비스는 결제의 진실을 알고 있고, 우리 데이터베이스는 서비스에서 쓰기 좋은 상태를 저장한다고 생각하면 된다. 둘은 경쟁하는 관계가 아니라 역할이 다르다. 웹훅은 바로 이 둘을 맞춰 주는 다리다.2

구독 기능에서 꼭 알아야 할 다섯 가지 이벤트

구독은 한 번 결제하고 끝나는 기능이 아니기 때문에, "가입 성공"만 보면 절반만 본 셈이다. 실제 운영에서는 아래 다섯 가지 사건을 특히 신경 써야 한다.2

  • checkout.session.completed: 처음 구독을 시작했을 때

  • invoice.paid: 다음 결제가 정상적으로 청구됐을 때

  • invoice.payment_failed: 카드 문제나 잔액 부족으로 결제가 실패했을 때

  • customer.subscription.updated: 요금제 변경이나 상태 변경이 생겼을 때

  • customer.subscription.deleted: 구독이 종료되었을 때

이 이벤트들을 처리해야 하는 이유는 간단하다. 사용자의 권한이 돈의 흐름과 정확히 맞물려야 하기 때문이다. 돈을 냈는데 기능이 안 열려도 문제고, 이미 해지했는데 계속 프리미엄 기능이 열려 있어도 문제다.

초보자 입장에서는 이 부분이 가장 어렵게 느껴질 수 있다. 하지만 원리는 단순하다. 이벤트를 받는다 → 해당 사용자를 찾는다 → 구독 상태를 업데이트한다 → 그 상태를 기준으로 권한을 판단한다. 이 흐름만 머릿속에 자리 잡으면 결제 기능의 절반은 이해한 셈이다.

언제 도입해야 할까

많은 사람이 서비스를 만들자마자 수익화부터 고민한다. 물론 그 마음은 아주 자연스럽다. 하지만 현실적으로는 결제를 너무 빨리 붙이는 것보다, 너무 늦게 붙이는 것보다, 조금 늦게 붙이는 편이 낫다.

왜냐하면 결제는 기능이 아니라 약속이기 때문이다. 돈을 받는 순간부터는 다음이 필요해진다.

  • 결제 실패 대응

  • 환불 기준

  • 해지 처리

  • 문의 대응

  • 가격 정책 설명

  • 세금, 영수증, 약관 검토

사용자가 거의 없고, 사람들이 어떤 기능을 좋아하는지도 아직 모르는 단계라면 먼저 무료로 핵심 가치를 검증하는 편이 낫다. 반대로 사용자가 반복해서 쓰고, "이 기능이 있으면 좋겠다"는 요청이 모이고, 무료 한계가 보이기 시작한다면 그때가 결제를 고민할 좋은 시점이다.

간단히 말하면 이런 순서가 자연스럽다.

사용자 반응 확인 → 반복 사용 확인 → 아쉬운 점 발견 → 유료 가치 정의 → 결제 도입

결제는 시작선이 아니라, 어느 정도 달린 뒤에 세우는 표지판에 가깝다.

초보자에게 가장 쉬운 도입 순서

처음부터 완성형 청구 시스템을 직접 설계하려고 하면 거의 반드시 지친다. 그래서 가장 쉬운 길은 "적게 만들고, 검증된 것을 최대한 활용하는 것"이다.

추천 순서는 이렇다.

1단계: 한 개의 유료 플랜만 만든다

월간 플랜 하나만 있어도 충분하다. 연간 플랜, 팀 플랜, 학생 할인, 쿠폰, 무료 체험까지 한 번에 넣으면 처음부터 머리가 복잡해진다.

2단계: 가격 페이지를 만든다

사용자가 무엇을 얻는지 쉬운 말로 적는다. 여기서 중요한 것은 결제 버튼보다 설명이다. "월 4,900원"보다 "글 개수 제한 해제, 비공개 글 기능 제공"이 더 설득력 있다.

3단계: 호스팅된 체크아웃으로 연결한다

사용자가 버튼을 누르면 우리 앱 안의 복잡한 결제 입력 폼으로 가지 않고, Stripe Checkout 같은 외부 결제 페이지로 이동하게 만든다. 이 방식은 카드 정보 처리 부담을 크게 줄여 준다.3

4단계: 웹훅으로 상태를 저장한다

결제가 끝났다고 우리 DB의 subscription_status를 바로 바꾸는 것이 아니라, 웹훅이 들어오면 그때 반영한다. 이 습관이 나중에 큰 사고를 줄여 준다.2

5단계: 서버에서 권한을 검사한다

프리미엄 기능 페이지에 들어갈 때, 서버가 사용자의 구독 상태를 확인하고 접근을 허용하거나 막는다. 클라이언트 화면만 숨기는 방식은 너무 쉽게 뚫릴 수 있다.1

6단계: 구독 관리는 포털에 맡긴다

해지, 요금제 변경, 카드 정보 수정은 직접 전부 만들지 말고 Customer Portal 같은 관리 화면을 우선 활용한다.3

이 정도만 되어도 "돈을 받는 서비스의 최소 구조"는 충분히 경험할 수 있다.

Next.js와 Supabase 기준으로 보면 이렇게 연결된다

우리 책의 기본 도구를 기준으로 보면 구조는 더 선명해진다.

Next.js는 가격 페이지, 결제 시작 버튼, 결제 완료 후 안내 화면을 만든다. 서버 액션이나 서버 라우트를 통해 체크아웃 세션을 만들고, 필요한 경우 결제 서비스로 바로 리다이렉트할 수 있다.6

Supabase는 우리 서비스 사용자와 결제 고객 정보를 연결하고, 구독 상태를 저장하는 창고 역할을 한다. 예를 들어 profiles 또는 users 테이블에 기본 사용자 정보를 두고, 별도의 subscriptions 테이블에서 상태와 만료일을 관리할 수 있다.5

Vercel은 앱을 배포하고, 환경변수에 비밀 키를 저장하는 곳이 된다. 이때 STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET 같은 값은 절대로 코드에 직접 적지 않고 환경변수로 관리해야 한다.1

이 조합의 장점은 역할이 분리된다는 점이다. 화면은 Next.js, 데이터는 Supabase, 결제는 Stripe 같은 전문 서비스를 이용하면 초보자도 상대적으로 덜 복잡하게 출발할 수 있다.

자주 하는 실수

결제 기능을 처음 붙일 때 많은 초보자가 비슷한 곳에서 넘어지곤 한다.

성공 페이지를 너무 믿는 실수

사용자가 /success 페이지에 도착했다고 해서 결제가 완전히 처리됐다고 단정하면 안 된다. 실제 반영은 웹훅 기준으로 처리하는 습관이 안전하다.2

제품 ID와 가격 ID를 헷갈리는 실수

결제 서비스에서는 상품 자체와 실제 청구 가격이 구분된다. 특히 Stripe Checkout은 보통 Product ID가 아니라 Price ID를 사용한다. 이 둘을 헷갈리면 체크아웃 생성 단계에서 바로 막힌다.7

구독 상태를 외부 서비스에서만 읽는 실수

매 페이지마다 결제 API를 호출해서 상태를 확인하려고 하면 느리고 비효율적이다. 자주 쓰는 청구 정보는 우리 데이터베이스에도 저장해 두는 편이 좋다.4

해지를 즉시 권한 차단으로 처리하는 실수

많은 구독 서비스는 사용자가 해지를 눌러도 현재 결제 기간이 끝날 때까지는 계속 사용하게 둔다. 이 정책을 모르면 사용자 경험이 갑자기 거칠어질 수 있다.1

라이브 모드로 너무 빨리 들어가는 실수

처음에는 반드시 테스트 모드에서 시작해야 한다. 테스트 카드, 테스트 웹훅, 테스트 시나리오를 충분히 돌려 본 다음 라이브로 전환하는 편이 안전하다.4

AI와 함께 구현할 때는 이렇게 시키면 좋다

결제 기능은 AI에게 "Stripe 붙여줘"라고 한 줄로 시키기보다, 아주 잘게 나누는 편이 좋다. 결제는 실수의 비용이 크기 때문이다.

예를 들어 Claude Code나 Codex에는 이렇게 나눠서 요청할 수 있다.

프롬프트 1: 가격 페이지 만들기

"Next.js App Router 기준으로 /pricing 페이지를 만들어줘. 무료 플랜과 프로 플랜 두 개를 카드 형태로 보여주고, 프로 플랜 버튼을 누르면 서버 액션 createCheckoutSession을 호출하도록 구성해줘. 아직 실제 Stripe 키는 연결하지 말고 UI와 함수 자리만 만들어줘."

프롬프트 2: 체크아웃 세션 생성

"Stripe Checkout을 사용해 구독 체크아웃 세션을 생성하는 서버 액션 예시를 만들어줘. priceId, 로그인한 사용자 이메일, success_url, cancel_url`을 포함해줘. 비밀 키는 환경변수에서 읽도록 해줘."

프롬프트 3: 웹훅 처리

"Next.js Route Handler로 Stripe 웹훅 엔드포인트 예시를 작성해줘. checkout.session.completed, invoice.paid, invoice.payment_failed, customer.subscription.updated, customer.subscription.deleted 이벤트를 처리하고, Supabase의 subscriptions 테이블을 업데이트하는 흐름을 의사코드 수준으로 정리해줘."

프롬프트 4: 권한 검사

"구독 상태가 active 또는 trialing인 경우에만 /pro 페이지에 접근 가능하도록 Next.js 서버 컴포넌트 기준 예시를 만들어줘."

이렇게 쪼개면 결과를 검토하기도 쉽고, 잘못된 코드가 들어왔을 때 어디가 문제인지 찾기도 쉬워진다.

결제 기능을 붙이기 전, 마지막으로 확인할 질문

결제 버튼을 만들기 전에 아래 질문에 답할 수 있으면 좋다.

  • 사용자는 무엇에 돈을 내는가?

  • 무료 버전만으로도 가치를 느낄 수 있는가?

  • 유료 기능은 정말 자주 쓰는 불편을 해결하는가?

  • 결제 실패, 해지, 환불 문의가 오면 어떻게 대응할 것인가?

  • 구독 상태를 우리 데이터베이스에 어떻게 저장할 것인가?

  • 테스트 모드에서 끝까지 검증했는가?

이 질문들에 막히면 아직 결제를 붙일 때가 아닐 수도 있다. 그건 나쁜 일이 아니다. 오히려 좋은 신호다. 수익화는 서두른다고 빨라지지 않고, 준비된 만큼 안정적으로 붙는다.

작은 서비스의 첫 수익화는 이렇게 생각하면 된다

처음 만든 서비스에서 큰 매출이 바로 나오지 않아도 괜찮다. 오히려 더 중요한 것은, 돈을 받는 구조를 한 번 이해해보는 경험이다.

구독 기능을 배운다는 것은 단순히 "월 결제 버튼"을 만드는 일이 아니다. 가격을 정하는 법, 사용자의 기대를 맞추는 법, 시스템 상태를 신뢰할 수 있게 관리하는 법을 함께 배우는 일이다. 서비스가 진짜 제품으로 넘어가는 지점이 바로 여기다.

그러니 첫 수익화는 거창할 필요가 없다. 월 1천 명 고객이 아니라, 먼저 한 명이 왜 돈을 낼지 생각해보면 된다. 그 한 명을 설득할 수 있는 서비스라면, 그다음 길도 조금씩 보이기 시작한다.

참고

1Stripe Subscriptions in a Next.js Application

2Stripe Subscription Lifecycle in Next.js — The Complete Developer Guide (2026)

3Comprehensive Guide for Stripe Subscriptions with React & Nextjs 13+

4How to Implement Stripe Billing in Next.js

5Next.js-Supabase Stripe Subscriptions Integration

6Stripe + Next.js 15: The Complete 2025 Guide

7How to Build a Stripe Checkout in Next.js with Subscription Tiers

책 목록

바이브 코딩으로 완성하는 첫 풀스택 웹 서비스

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기