9. Payout — PricingConfig·RevenueDistributionConfig·PayoutPeriod·PaymentLedger·DistributionEntry·MentorPayout·MentorBankAccount·TaxReport

정산 시스템 + 가변 가격·분배 설정. 3G 정산, 6C 본사 수익, 2C 가격 정책 반영.

PricingConfig (회원 결제 가격, 가변)

Purpose: 회원이 1세션에 내는 가격을 시간·슬롯 타입·멘토 등급별로 가변 설정. admin이 운영. Related PRDs: 🏢 멤버십 시스템 · 👤 멤버십·결제 · 🏢 예약 Lifecycle: admin 설정 → 활성 → 다이나믹 프라이싱 (Phase 2+) 적용

Fields

Field Type Required Default Description
id String cuid() PK
name String - “Phase 1 기본” / “피크 가산” 등
sessionPriceWon Int - 1세션 가격 (원) — 멤버십 회당 환산
pointSurchargeWon Int? - null Pro 인증 멘토 선택 시 추가 결제 (포인트)
scope String global global / time_band / slot_type / mentor_tier
scopeValue String? - null “peak” / “off_peak” / “fixed” / “ad_hoc” / “pro_certified”
priority Int 0 specific > general 우선순위
active Boolean true  
effectiveFrom DateTime -  
effectiveUntil DateTime? - null  
createdAt DateTime now()  
createdBy String - admin id

사용 예시

# Phase 1 기본
config_default (global, priority=0)
  sessionPriceWon: 30,000
  pointSurchargeWon: null

# 피크 시간 가산 (Phase 2)
config_peak (time_band='peak', priority=10)
  sessionPriceWon: 33,000  # +10%

# 고정 슬롯 우대 (멤버십 가격 ↓)
config_fixed (slot_type='fixed', priority=20)
  sessionPriceWon: 28,000  # -7%

# Pro 인증 멘토 (포인트 추가)
config_pro (mentor_tier='pro_certified', priority=5)
  sessionPriceWon: 30,000  # 회차는 동일
  pointSurchargeWon: 5,000  # 포인트 추가 차감

Validation

  • 1 시각·슬롯·멘토에 매칭되는 config 중 priority 최고 적용
  • effectiveUntil > effectiveFrom

적용 우선순위

mentor_tier (5) → slot_type (20) → time_band (10) → global (0)
숫자 높음 = 우선

Edge Cases

  • 가격 변경 시점 = 신규 예약부터 적용 (기존 예약 = 기존 가격 snapshot)
  • 동시 충돌 (multi-scope 매칭) → priority 같으면 effectiveFrom 더 최근
  • 가격 ↓ 변경 시 회원 알림 (긍정), 가격 ↑ 시 7일 전 알림

RevenueDistributionConfig (정산 분배, 가변)

Purpose: 세션별 분배 정책을 admin이 가변 설정. 비율 / 정액 / 혼합 모두 지원. 시간·등급·지점별 차등 가능. Related PRDs: 🏢 정산 시스템 · 3G 정산 · 6C 본사 수익 Lifecycle: admin 설정 → 활성 → 변경 (이력 기록)

Fields

Field Type Required Default Description
id String cuid() PK
name String - “Phase 1 직영 기본” 등
scope String global global / store / mentor_tier / time_band / slot_type
scopeValue String? - null  
priority Int 0  
mode String percentage percentage / flat / hybrid
mentorPercent Decimal(4,2)? - null 0.50
mentorFlatRate Int? - null 20,000원
hqPercent Decimal(4,2)? - null 0.50 (Phase 1 직영)
hqFlatRate Int? - null  
franchiseePercent Decimal(4,2)? - null Phase 2+
franchiseeFlatRate Int? - null  
active Boolean true  
effectiveFrom DateTime -  
effectiveUntil DateTime? - null  
createdAt DateTime now()  
createdBy String - admin id

사용 예시

# Phase 1 직영 (기본)
default (global, percentage, priority=0)
  mentorPercent: 0.50
  hqPercent: 0.50
  franchiseePercent: null

# Pro 인증 멘토 (자율 단가 정액)
pro_flat (mentor_tier='pro_certified', mode=flat, priority=10)
  mentorFlatRate: 28,000   # Pro 자율 단가
  hqFlatRate: nullable     # 나머지 자동 (회원 결제 - 멘토)

# 피크 시간 (멘토 추가 인센티브)
peak_bonus (time_band='peak', percentage, priority=5)
  mentorPercent: 0.55     # +5%
  hqPercent: 0.45

# Phase 2 가맹
franchise (global Phase 2+, percentage, priority=0)
  mentorPercent: 0.50
  hqPercent: 0.30
  franchiseePercent: 0.20

Validation

  • mode=percentage: mentorPercent + hqPercent + (franchiseePercent   0) = 1.0
  • mode=flat: 합 ≤ 회원 결제액 (예약 시 검증)
  • mode=hybrid: 일부 percentage + 일부 flat (멘토는 flat, 본사는 나머지 등)
  • effectiveFrom < effectiveUntil

State Transitions

stateDiagram-v2
    [*] --> active: admin 생성
    active --> superseded: 새 config 우선순위 ↑
    active --> ended: effectiveUntil 도달

Indexes

  • [scope, scopeValue, active, priority] — 매칭 시 lookup
  • [effectiveFrom, effectiveUntil] — 시점별 조회

적용 우선순위 (동일 시점)

mentor_tier → store → slot_type → time_band → global
priority 높을수록 먼저

진행 중 예약 보호

예약 시점의 config snapshot을 DistributionEntry.configSnapshot (jsonb)에 저장 → config 변경되어도 이미 예약된 세션은 기존 비율 적용.

Edge Cases

  • 진행 중 예약의 config 변경 → snapshot 보존 (불변)
  • 비율 합 ≠ 1.0 → 저장 차단
  • 정액 합이 회원 결제 초과 → admin 알림 (손실 발생)
  • 우선순위 동률 → effectiveFrom 더 최근

DistributionEntry 보강

DistributionEntry에 configSnapshot 컬럼 추가 — 예약 시점 RevenueDistributionConfig 스냅샷.

model DistributionEntry {
  id
  paymentLedgerId
  recipientType
  recipientId
  amount
+ configSnapshot  Json  // 적용된 config 전체 snapshot
+ configId        String?  // 참조 (선택)
  status
}

PayoutPeriod

Purpose: 격주 정산 기간. cron으로 자동 생성. Related PRDs: 💪 정산 · 🏢 정산 시스템 Lifecycle: cron 생성 → 마감일 → 정산 산출 → 입금 완료

Fields

Field Type Required Default Description
id String cuid() PK
startDate DateTime - 격주 시작 (월요일)
endDate DateTime - 끝 (다음 주 일요일)
cutoffAt DateTime - 마감 시각 (endDate 23:59)
paymentDate DateTime - 입금 예정일 (cutoff + 영업일 2일)
status String open open/calculating/paid
createdAt DateTime now()  

Validation

  • endDate = startDate + 13일 (14일 격주)
  • paymentDate = cutoffAt + 영업일 2일
  • 한 시점에 1 active period (status=’open’)

Common Queries

  • 현재 격주: WHERE status='open' AND NOW() BETWEEN startDate AND endDate
  • 다가오는 입금일: WHERE status='paid' AND paymentDate > NOW()

Edge Cases

  • 공휴일 → paymentDate 연기
  • cutoff 시점 진행 중 세션 → 다음 격주로 이월

PaymentLedger

Purpose: 결제 원장. 7일 escrow 후 정산 가능 상태로 전환. Phase 2 가맹 시작 시 본격 활용. Related PRDs: 🏢 정산 시스템 Lifecycle: 결제 직후 escrow → 7일 후 releasable → 정산 시 distributed

Fields

Field Type Required Default Description
id String cuid() PK
paymentId String - FK Payment (1:1)
memberId String - denorm
amount Int -  
paidAt DateTime -  
status LedgerStatus escrow escrow/releasable/refunded/distributed
releasedAt DateTime? - null 7일 후 자동

Validation

  • paymentId unique (1:1)
  • status=’releasable’ 시 paidAt + 7일 ≤ NOW()
  • distributed 후엔 변경 불가 (immutable)

State Transitions

stateDiagram-v2
    [*] --> escrow: 결제 완료
    escrow --> releasable: cron 7일 후
    escrow --> refunded: 7일 내 환불
    releasable --> distributed: 정산 산출
    releasable --> refunded: 사후 환불 (드물)

Indexes

  • paymentId (unique)
  • [status, paidAt] — cron 7일 후 처리 대상

Common Queries

  • releasable 대상: WHERE status='escrow' AND paidAt < NOW() - 7d → 일괄 갱신
  • 미분배 releasable: WHERE status='releasable' AND distributedAt IS NULL

DistributionEntry

Purpose: 분배 단위 (멘토·본사·가맹점주 각각). Phase 1엔 mentor + hq만, Phase 2엔 franchisee 추가. Related PRDs: 🏢 정산 시스템 Lifecycle: 결제 분배 → pending → paid (정산 시)

Fields

Field Type Required Default Description
id String cuid() PK
paymentLedgerId String - FK
recipientType String - mentor / hq / franchisee
recipientId String - mentor id 또는 store id
amount Int - 분배액 (원)
appliedRate Decimal(3,2)? - null 비율 (멘토 50%, hq 30% 등)
status String pending pending / paid
paidInPeriodId String? - null MentorPayout/etc periodId 연결
createdAt DateTime now()  

Validation

  • 한 ledger의 모든 distribution.amount 합 = ledger.amount

Indexes

  • paymentLedgerId
  • [recipientType, recipientId, status]

Common Queries

  • 멘토 분배 미정산: WHERE recipientType='mentor' AND recipientId=? AND status='pending'
  • 정산 산출: 격주별 분배 합계

Edge Cases

  • 환불 시 분배 회수 → status=’pending’ → 다음 격주에 차감 적용
  • Phase 1 = mentor + hq만 분배 (가맹점주 분배 ❌)

MentorPayout

Purpose: 멘토 격주 정산 (입금 단위). Related PRDs: 💪 정산 · 🏢 정산 시스템 Lifecycle: 격주 산출 → calculated → paid (입금)

Fields

Field Type Required Default Description
id String cuid() PK
mentorId String - FK
periodId String - FK PayoutPeriod
sessionCount Int - 격주 누적 세션 수
grossAmount Int - 회당 × 단가
deductions Int 0 노쇼·6h 취소 패널티
netAmount Int - 실 입금액 (gross - deductions)
status PayoutStatus calculated calculated / paid / failed
paidAt DateTime? - null  
transactionId String? - null 은행 송금 ID
failureReason String? - null  

Validation

  • (mentorId, periodId) unique
  • netAmount = grossAmount - deductions
  • netAmount ≥ 0

State Transitions

stateDiagram-v2
    [*] --> calculated: 격주 마감 + 산출
    calculated --> paid: 입금 성공
    calculated --> failed: 입금 실패 (계좌 오류 등)
    failed --> calculated: 재시도 (계좌 정정 후)

Indexes

  • [mentorId, periodId] (unique)
  • [status, periodId] — admin 처리 큐

Common Queries

  • 멘토 정산 이력: WHERE mentorId=? ORDER BY periodId DESC
  • 미입금: WHERE status='calculated' OR status='failed'

PayoutLineItem (세션별 라인)

Purpose: MentorPayout의 세션별 상세. Lifecycle: MentorPayout 생성 시 함께 생성.

Fields

Field Type Required Default Description
id String cuid() PK
payoutId String - FK MentorPayout
sessionId String - FK Session
sessionDate DateTime -  
rateApplied Int - 진행 당시 단가 (snapshot)
mentorTierAtTime MentorTier - 진행 당시 등급
amount Int - 이 세션 정산액

Indexes

  • payoutId — 정산 명세 조회

PayoutDeduction (차감)

Purpose: 노쇼·6h 취소 등 차감 사유 상세. Lifecycle: MentorPayout 산출 시 추가.

Fields

Field Type Required Default Description
id String cuid() PK
payoutId String - FK
type String - no_show / late_cancel / tier_demotion
sessionId String? - null 관련 세션
amount Int - 차감액
reason String - 텍스트

MentorBankAccount

Purpose: 멘토 정산 계좌. encrypted 저장. Related PRDs: 💪 정산 Lifecycle: 등록 → 본인 확인 (1원 송금) → active → (변경) update

Fields

Field Type Required Default Description
mentorId String - PK (1:1)
bank String - “신한”
accountNumberEnc String - AES 암호화
accountNumberLast4 String - 마스킹 표시용
accountHolder String - 본인 명의
verifiedAt DateTime? - null 1원 송금 확인
lastUpdatedAt DateTime now()  

Validation

  • accountHolder = mentor.name (본인 명의)
  • verifiedAt NULL 시 정산 ❌

Indexes

  • mentorId (PK)

Edge Cases

  • 계좌 변경 후 첫 입금 = 1원 송금 + 본인 확인
  • 본인 명의 ≠ 멘토 명의 → 정산 차단
  • 변경 후 진행 중인 정산 = 기존 계좌 유지

TaxReport

Purpose: 사업소득 명세서 (월별, 원천세 3.3%). Related PRDs: 💪 정산 Lifecycle: 월 1회 cron 생성 → PDF 발급 → 멘토 다운로드 가능

Fields

Field Type Required Default Description
id String cuid() PK
recipientType String mentor mentor / franchisee
recipientId String -  
period String - “2026-04”
grossIncome Int - 총 수입
withholding Int - 원천세 (3.3%)
netIncome Int - 실수령
reportUrl String? - null PDF 다운로드
generatedAt DateTime now()  

Validation

  • (recipientType, recipientId, period) unique
  • withholding = grossIncome × 0.033 (반올림)
  • netIncome = grossIncome - withholding

Indexes

  • [recipientType, recipientId, period] (unique)

Common Queries

  • 멘토 연간 누적: SUM(grossIncome) WHERE recipientType='mentor' AND recipientId=? AND period LIKE '2026-%'

Edge Cases

  • 원천세 신고 = 본사 책임 (멘토는 종합소득세 신고 시 사용)
  • 사업소득 신고 누락 → 운영 알림

📘 사용 PRD

💪 정산 · 🏢 정산 시스템 · 🏢 멤버십 시스템 · 💳 결제 흐름


2026-05-13 초안 — Payout 7 모델 상세 명세