메인 콘텐츠로 건너뛰기
FIM One은 관리자가 제어하는 기능 플래그 뒤에 완전한 Stripe 결제 파이프라인을 제공합니다. 결제가 필요 없는 프라이빗 배포는 이를 비활성화하고 UI를 볼 수 없습니다. SaaS 운영자는 한 번의 토글로 호스팅된 Checkout, Customer Portal, 웹훅 기반 구독 생명주기 및 할당량 적용을 즉시 사용할 수 있습니다.
결제는 기본적으로 비활성화되어 있습니다. 새로운 설치 및 기존 셀프호스트 모두 system_settings.billing_enabled = FALSE로 시작합니다. 관리자가 명시적으로 활성화할 때까지 결제 UI는 표시되지 않습니다.

무엇을 얻을 수 있는가

  • Free + Pro 티어 — 월간 토큰 할당량 포함 (기본값: Free 1M / Pro 5M; 활성화 후 조정 가능)
  • Stripe 호스팅 Checkout — 사용자가 업그레이드할 때 카드 데이터가 코드를 거치지 않음
  • Customer Portal — 사용자가 결제 방법 업데이트, 청구서 다운로드, 구독 취소 — 모두 Stripe UI에서 처리
  • Webhook 기반 라이프사이클 — 구독 자동 프로비저닝 및 갱신; 취소된 구독은 기간 종료 시 Free로 강등
  • 할당량 적용 — 기간당 토큰 사용량 추적; 스트림 중 차단 및 구조화된 업그레이드 프롬프트
  • 관리자 페이지 — 요금제 CRUD 및 구독 모니터링

전제 조건

  1. Stripe 계정 (라이브 모드 활성화됨). 싱가포르 법인은 KYC 완료 필요(사업 UEN, 이사 신분증, 은행 계좌). 승인은 일반적으로 1-3일 소요됩니다.
  2. Stripe 라이브 API 키 (제한된 유형 권장 — 표준 sk_live_***보다 해지하기 쉽고 권한 범위 지정 가능).
  3. 웹훅 엔드포인트 (<your-domain>/api/webhooks/stripe에서 공개적으로 접근 가능).
  4. 은행 계좌 (지급용). 다중 통화 결제(예: USD 계좌로 USD 지급)는 USD 기본값이 아닌 Stripe 계정의 경우 거래당 1.5-2% FX 손실을 피하기 위해 권장됩니다.

설정

1. Stripe 대시보드

Pro 상품 만들기

  1. Catalog → Products → + Add product
  2. Name: Pro, description: 5M tokens / month, priority support
  3. Pricing: Recurring, monthly, $20.00 USD (adjust to your pricing strategy)
  4. Save → copy the resulting price_*** ID (you will UPDATE the local billing_plans table with this value after activation)

제한된 API 키 생성

  1. Developers → API keys → + Create restricted key
  2. Name: fim-one production
  3. Permissions (minimum):
    • Customers: Write
    • Subscriptions: Write
    • Checkout Sessions: Write
    • Customer portal: Write
    • Prices: Read
    • Products: Read
  4. Save → copy rk_live_***

웹훅 엔드포인트 등록

  1. Developers → Webhooks → + Add endpoint
  2. URL: https://<your-domain>/api/webhooks/stripe
  3. 수신할 이벤트:
    • checkout.session.completed
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  4. 저장 후 “Reveal signing secret” 클릭 → whsec_*** 복사

다중 통화 결제 구성(권장)

Stripe 계정의 기본 통화가 청구하는 가격 통화와 다른 경우(일반적인 경우: SGD 계정에서 USD 청구):
  1. Settings → Bank accounts and currencies → Add a settlement currency
  2. 가격 통화 선택(예: USD)
  3. 일치하는 은행 계좌 연결(예: Aspire USD 가상 계좌)
  4. 저장 — Stripe가 USD 청구를 USD 지급으로 직접 라우팅하므로 환율 변환 없음

2. Backend .env

프로덕션 .env에서 다음 세 개의 키를 설정하세요:
STRIPE_SECRET_KEY=rk_live_***
STRIPE_WEBHOOK_SECRET=whsec_***
STRIPE_BILLING_RETURN_URL=https://<your-domain>/settings?tab=billing
전체 참조는 Environment Variables를 참조하세요. .env를 편집한 후 백엔드를 다시 시작하여 키가 적용되도록 하세요:
./deploy.sh   # or: docker compose restart fim-one

3. Admin에서 활성화

  1. 관리자로 로그인
  2. Admin → System Settings → Billing
  3. Enable Stripe Billing 토글을 ON으로 설정
  4. 백엔드가 STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRET이 모두 존재하는지 검증합니다 — 둘 중 하나라도 누락되면 400을 반환하고 토글은 OFF 상태로 유지됩니다
  5. 처음 활성화할 때만 백엔드가 멱등성 설정을 실행합니다:
    • Free + Pro 플랜을 시드합니다 (이미 존재하면 건너뜀)
    • system_settings.default_plan_id를 Free 플랜 id로 설정합니다
    • 플랜이 없는 모든 사용자에 대해 users.plan_id = free.id를 역채우기합니다
    • default_token_quota → Free 플랜 monthly_token_quota를 동기화하여 기존 관리자 제어 전역 할당량이 유지되도록 합니다
  6. 이후 토글 끄기/켜기는 순수 플래그 전환이며 데이터 부작용이 없습니다

4. Pro 플랜을 라이브 가격으로 업데이트

활성화 후, 시드된 Pro 플랜을 라이브 Stripe 가격으로 업데이트하세요:
  • Admin → Billing → Plans → Pro → Edit
  • 1단계에서 얻은 price_1***Stripe Price ID에 붙여넣기
  • 저장
또는 SQL을 통해 (직접 DB 접근을 선호하는 경우):
UPDATE billing_plans
SET stripe_price_id = 'price_1***'
WHERE slug = 'pro';

5. Smoke test

  1. /settings?tab=billing을 일반 사용자로 열기
  2. Switch to Pro 클릭
  3. Stripe Checkout이 열림; 소액 실제 카드로 완료 (이후 환불)
  4. Webhook이 실행되어야 함 — Stripe Dashboard → Webhooks → 최근 이벤트에서 2xx 응답 확인
  5. subscriptions 테이블에 구독 행이 나타남; users.plan_idpro로 변경됨
  6. UI에 Pro 플랜 + “Manage subscription” 버튼 표시

청구 비활성화

Admin → System Settings → Billing에서 Enable Stripe Billing 스위치를 OFF로 전환합니다. 비활성화되면:
  • 모든 /api/billing/* 엔드포인트가 503을 반환합니다
  • 웹훅 엔드포인트가 503을 반환합니다 (Stripe가 재시도한 후 Dashboard에 실패로 표시됩니다 — 청구가 영구적으로 비활성화된 경우 Stripe Dashboard에서 웹훅을 비활성화할 수 있습니다)
  • Plan & Billing 사용자 대면 탭이 사라집니다
  • Admin → Billing 네비게이션 그룹이 숨겨집니다
  • 할당량 체인이 요금제 계층을 건너뛰고 default_token_quota로 직접 폴백합니다
데이터는 보존됩니다: 기존 subscriptions, billing_plans, users.plan_id 행은 변경되지 않습니다. 다시 활성화하면 마이그레이션 없이 동일한 상태에서 재개됩니다.

계산 참고 — 할당량 및 토큰 수학

이것은 사용자가 소비할 수 있는 것, 카운터가 언제 재설정되는지, 그리고 해결 체인이 어떻게 구성되는지를 결정하는 모든 숫자 규칙의 권위 있는 참고 자료입니다. 가격 책정을 변경하거나, 할당량을 조정하거나, 사용량 대시보드를 구축하거나, v2/v3 작업을 계획하기 전에 이것을 읽으세요. 아직 배포되지 않은 향후 규칙은 예약된 슬롯에 문서화되어 있으므로 기여자들이 새로운 로직이 어디에 연결되는지 알 수 있습니다.

용어집

변수저장소의미범위
users.token_quotaper-user (override)3가지 상태 오버라이드; 아래 의미 참조NULL, 0, 또는 양의 정수
users.tokens_used_this_periodper-user (counter)마지막 리셋 이후 누적 토큰음이 아닌 정수
users.quota_reset_atper-user (anchor)유료 사용자의 Subscription.current_period_end 반영timestamp
users.plan_idper-user (FK)활성 플랜FK billing_plans.id
billing_plans.monthly_token_quotaper-plan이 플랜의 사용자에 대한 하드 캡음이 아닌 정수
system_settings.default_token_quotasingleton적용되는 플랜이 없을 때의 방어적 폴백음이 아닌 정수
system_settings.default_plan_idsingleton신규/미할당 사용자를 위한 무료 플랜 포인터FK 또는 NULL
system_settings.billing_enabledsingleton마스터 스위치 — 체인의 2단계를 제어boolean

토큰으로 계산되는 항목

토큰 소비는 LLM 호출 레이어에서 계산되며, 모든 완료 시 LiteLLM의 usage 객체에서 소싱됩니다.
  • 계산됨: 모든 모델 호출에서 프롬프트 토큰 + 완료 토큰
  • 계산됨: 다단계 / 도구 사용 에이전트 흐름의 모든 왕복 (각 모델 호출은 자체 차감)
  • 계산됨: 임베딩 요청 (KB 수집, 검색 점수 매김)
  • 계산 안 됨: 모델로 전송되지 않은 입력 (예: 사용자가 버린 업로드된 파일)
  • 계산 안 됨: 제공자에 도달하기 전에 실패한 요청 (인증 오류, 속도 제한 사전 확인)
  • 캐시된 입력: v1에서는 정가로 계산됨 (제공자 캐시 할인이 표시되지 않음). v2는 캐시된 프롬프트 토큰을 별도로 크레딧할 수 있습니다.

3단계 오버라이드 의미론

users.token_quota는 사용자별 관리 오버라이드입니다. 하나의 열에 세 가지 의미를 담고 있습니다:
의미사용 사례
NULL설정되지 않음 — 플랜 / 기본값으로 위임모든 일반 사용자의 기본 상태
0무제한관리자 / 내부 계정; “VIP 선물”
N > 0N에서 하드 캡유료 구독을 취소하지 않고 악용자 차단; 선결제 엔터프라이즈 할당량
오버라이드는 항상 플랜과 기본값을 우선합니다. 관리자가 Stripe를 건드리지 않고도 개별 사용자를 플랜 계층 위 또는 아래에 고정할 수 있도록 존재합니다.

할당량 해결 체인 — v1 (현재)

인증된 모든 요청에 대해 상한은 하향식으로 계산됩니다 — 첫 번째 일치가 우선입니다:
1. users.token_quota        ── NULL? skip. 0? unlimited. N>0? cap at N.
2. users.plan.monthly_token_quota   ── only when billing_enabled = TRUE
3. system_settings.default_token_quota  ── defensive fallback
4. unlimited                ── last resort if everything above is NULL
청구가 활성화되면 3단계에 거의 도달하지 않습니다. 활성화가 모든 사용자에 대해 users.plan_id를 역채우기하기 때문입니다. 잘못 구성된 플랜이 사용자를 자동으로 무제한으로 설정하지 않도록 하는 심층 방어 메커니즘으로 존재합니다.

기간 재설정

  • 유료 사용자의 경우, quota_reset_atSubscription.current_period_end를 반영합니다. invoice.payment_succeeded 웹훅 핸들러는 각 갱신 성공 시 tokens_used_this_period = 0으로 설정하고 quota_reset_at을 새로운 기간 종료로 진행합니다.
  • Free 사용자(Stripe 구독 없음)의 경우, 시간별 크론이 계획 할당 날짜를 기준으로 한 달력 월 경계에서 tokens_used_this_period를 0으로 롤링합니다.
  • 기간 중 계획 변경은 카운터를 재설정하지 않습니다 — 갱신만 재설정합니다. 이는 할당량 순환 악용(“구독 → Pro 할당량 사용 → 취소 → 다시 구독”)을 방지합니다.

스트림 중 적용

  • 채팅 호출 진입 시 사전 점검: 가장 저렴한 경로로, 사용자가 감당할 수 없는 요청을 차단합니다.
  • 스트리밍 중에는 모든 청크에서 실행 중인 토큰 수를 다시 평가합니다. 한도를 초과하면 네트워크 오류가 아닌 구조화된 종료 프레임으로 스트림을 닫습니다.
  • 프론트엔드는 종료자를 해석하고 /settings?tab=billing으로의 딥 링크와 함께 <QuotaExceededDialog>를 표시합니다.
  • 비스트리밍 응답은 HTTP 402를 반환하며, 본문은 { code: "QUOTA_EXCEEDED", reset_at, upgrade_url }입니다.

청구 비활성화 폴백

system_settings.billing_enabled = FALSE일 때:
  • 체인의 2단계가 건너뛰어집니다 — 체인이 override → default → unlimited로 축소됩니다.
  • /api/billing/*/api/webhooks/stripe503을 반환합니다.
  • Plan & Billing 사용자 탭 및 Admin → Billing 네비게이션 그룹이 숨겨집니다.
  • 모든 청구 데이터(구독, 플랜, users.plan_id)는 보존됩니다 — 다시 활성화하면 마이그레이션 없이 동일한 상태에서 재개됩니다.

Reserved: quota chain v2 — Team seats

아직 출시되지 않았습니다. v2 작업이 알려진 착륙 지점을 가지도록 여기에 문서화되어 있습니다.
Team 플랜이 출시될 때:
  • Subscription.quantity는 좌석 수를 전달합니다(Stripe 기본).
  • 사용자의 유효한 플랜은 Team 멤버십을 통해 해결되며, 그 후 개인 플랜으로 폴백됩니다:
    effective_plan = team.plan if team_member(user) else user.plan
    
  • 할당량은 좌석당(각 Team 멤버는 전체 monthly_token_quota를 받음)이며, 풀링된 버킷이 아닙니다. 풀링된 버킷은 선착순 소진을 만들고 고객 친화적이지 않습니다.
  • 재정의 의미는 변경되지 않습니다 — Team 관리자는 여전히 users.token_quota = N을 통해 개별 멤버를 하드 캡할 수 있으며, 이는 team 플랜 체인 위에 있습니다.

Reserved: quota chain v3 — native Org allocation (no Stripe)

아직 출시되지 않음. Stripe를 통해 사용자당 비용을 지불하지 않고 내부적으로 할당량을 배분하는 온프레미스/엔터프라이즈 배포용으로 예약됨.
  • 새로운 테이블 org_quota_allocations(user_id, monthly_token_quota, org_id)는 상위 예산을 멤버 전체에 배분합니다.
  • 할당량은 공유 풀이 아닌 사용자별 — 모든 멤버가 명확한 개별 SLA를 가집니다.
  • 업데이트된 체인:
    override → max(plan_quota, org_allocation) → default → unlimited
    
  • max(), sum() 아님. 유료 Pro 사용자는 조직 관리자가 낮은 할당량을 설정하더라도 결코 지불한 것보다 적게 받지 않습니다. Stripe 지불 할당량은 절대 불변입니다.

예약됨: 종량제 크레딧 잔액 (v3 별도 차원)

아직 출시되지 않음. 위의 체인과 별개의 축——크레딧은 구독 티어가 아닌 일회성 충전입니다.
  • 새로운 테이블 user_credits(user_id, balance_cents, currency) — Stripe Checkout mode='payment'를 통해 자금 조달됨.
  • 소비 순서: 구독 할당량 우선, 그 다음 크레딧 잔액 (구독이 소진된 후에만 크레딧 차감 시작).
  • 크레딧 잔액은 환불 불가능 (선불식 업계 표준).
  • UI는 두 개의 바를 노출: Subscription quota: 4.2M / 5M used + Credits: $7.40 remaining.

기본값

배포 시 기본값 — 명시된 경우를 제외하고 모두 설치 후 조정 가능합니다.
변수기본값조정 방법
billing_plans.monthly_token_quota (Free)1,000,000Admin → Billing → Plans → Free → Edit
billing_plans.monthly_token_quota (Pro)5,000,000Admin → Billing → Plans → Pro → Edit
system_settings.default_token_quota1,000,000 (활성화 시 Free와 동기화)Admin → System Settings → Quotas
system_settings.billing_enabledFALSEAdmin → System Settings → Billing
Pro 정가$20.00 USD / monthStripe Dashboard (price object)
구독한 Stripe 웹훅 이벤트6Stripe Dashboard → Webhooks
Stripe 가격 캐시 TTL5 minutesstripe_client.py에 하드코딩됨
구독 수명 주기 cron매시간web/main.py의 APScheduler
Free 티어 리셋 cron매시간 (달력 월 경계)web/main.py의 APScheduler

가격 책정 모델

V1은 정액 구독입니다. Free + Pro, 월간 청구, USD만 지원합니다. V1 범위 외 (의도적으로 로드맵으로 연기됨):
  • Team 플랜 (Stripe seats / subscription.quantity)
  • 연간 청구
  • 다중 통화 표시
  • 쿠폰 / 프로모션 코드
  • 세금 처리 (Stripe Tax 통합 — 별도 규정 검토 필요)
  • 사용량 기반 계량 / 초과 요금
  • 선불 크레딧 잔액 (일회성 충전)
다음 계획된 사항은 로드맵을 참조하세요.

문제 해결

활성화 엔드포인트에는 STRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRET이 모두 설정되어야 합니다. .env에 존재하는지 확인하고 편집 후 백엔드가 다시 시작되었는지 확인하세요.
청구가 비활성화되어 있거나(토글이 OFF) 요청 서명 검증에 실패했습니다(STRIPE_WEBHOOK_SECRET 불일치). Stripe 대시보드 → 웹훅 → 최근 이벤트에서 실제 오류 본문을 확인하세요.
checkout.session.completed 웹훅이 백엔드에 도달하지 않았습니다. Stripe 대시보드의 엔드포인트 URL이 <your-domain>/api/webhooks/stripe와 정확히 일치하는지(후행 경로 포함) 확인하세요. 웹훅 최근 전달 기록에서 실패를 확인하세요.
시드 마이그레이션이 테스트 모드 가격 ID를 작성합니다. 프로덕션 청구를 활성화한 후 관리자 → 청구 → 요금제 → Pro → 편집을 통해 Pro 요금제를 라이브 price_1***을 사용하도록 업데이트하거나 직접 SQL UPDATE를 실행하세요.
Stripe 대시보드 → 설정 → 브랜딩에서 비즈니스 브랜딩을 구성하세요. 로고, 비즈니스 이름(예: “FIM Labs Pte. Ltd.”), 주소를 추가하세요. Stripe는 이를 모든 자동 생성 영수증 및 청구서에 적용합니다.
Stripe 계정 기본 통화가 청구 통화와 다르면 Stripe는 매번 지급할 때 환전합니다(1.5-2% 스프레드). 설정 → 은행 계좌 및 통화에서 일치하는 결제 통화를 추가하고 동일 통화 은행 계좌를 연결하면 Stripe가 환전 없이 동일 통화 결제를 라우팅합니다.