- 영업수수료정산: 입금등록, 수당자동계산, 승인프로세스, 지급추적 - 컨설팅비용정산: 상담시간×시급 정산, 상태관리 - 고객사정산: 월별 매출/수수료/비용 순정산액 관리 - 구독료정산: 플랜별 과금, MRR/ARR 집계, 구독 라이프사이클 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.5 KiB
7.5 KiB
구독료정산
개요
구독료정산은 고객사별 구독 플랜과 과금을 관리하는 기능입니다. 플랜별 월정액 관리, 과금주기(월/연), MRR/ARR 집계, 사용자 수 추적을 지원합니다.
- 라우트:
GET /finance/subscription - UI 기술: React 18 + Babel (브라우저 트랜스파일링)
파일 구조
mng/
├── app/Http/Controllers/Finance/
│ └── SubscriptionController.php # MNG 컨트롤러 (4개 메서드)
├── app/Models/Finance/
│ └── Subscription.php # MNG 구독 모델
└── resources/views/finance/
└── subscription.blade.php # React 기반 단일 페이지
api/
├── app/Http/Controllers/Api/V1/
│ └── SubscriptionController.php # API 컨트롤러 (REST)
├── app/Services/
│ └── SubscriptionService.php # 비즈니스 로직 서비스
├── app/Models/Tenants/
│ └── Subscription.php # API 구독 모델 (상세 구현)
└── database/migrations/
├── 2026_02_04_230008_create_subscriptions_table.php
└── 2025_12_22_222722_add_cancel_columns_to_subscriptions_table.php
라우트
MNG 라우트
// routes/web.php (finance prefix 그룹 내)
GET /subscription → Blade 페이지 렌더링 (HX-Redirect)
// API 라우트 (subscriptions prefix)
GET /subscriptions/list → index() 목록 + 통계 (JSON)
POST /subscriptions/store → store() 등록
PUT /subscriptions/{id} → update() 수정
DELETE /subscriptions/{id} → destroy() 삭제
API 라우트
// routes/api.php (v1/subscriptions prefix)
GET / → index() 목록 (페이지네이션)
GET /{id} → show() 상세 조회
POST / → store() 생성
PUT /{id} → update() 수정
DELETE /{id} → destroy() 삭제
POST /{id}/cancel → cancel() 구독 취소
POST /{id}/suspend → suspend() 일시정지
POST /{id}/resume → resume() 재개
컨트롤러
SubscriptionController (MNG)
| 메서드 | HTTP | 설명 |
|---|---|---|
index() |
GET | 목록 + MRR/ARR 통계 (검색, 상태 필터) |
store() |
POST | 구독 등록 |
update() |
PUT | 구독 수정 |
destroy() |
DELETE | 삭제 (Soft Delete) |
index() 응답 구조
{
"success": true,
"data": [
{
"id": 1,
"customer": "A사",
"plan": "Business",
"monthlyFee": 500000,
"billingCycle": "monthly",
"startDate": "2026-01-01",
"nextBilling": "2026-03-01",
"status": "active",
"users": 10,
"memo": ""
}
],
"stats": {
"activeCount": 15,
"monthlyRecurring": 7500000,
"yearlyRecurring": 90000000,
"totalUsers": 150
}
}
핵심 로직
MRR/ARR 계산
MRR (Monthly Recurring Revenue)
= SUM(active 구독의 monthly_fee) (billingCycle=monthly)
+ SUM(active 구독의 monthly_fee / 12) (billingCycle=yearly를 월 환산)
ARR (Annual Recurring Revenue)
= MRR × 12
구독 상태 관리 (API 모델)
pending (대기)
↓ activate()
active (활성)
├── cancel(reason) → cancelled (취소)
├── suspend() → suspended (일시정지)
│ ↓ resume() → active (재활성)
└── (만료) → expired
구독 플랜
| 플랜 | 설명 |
|---|---|
| Starter | 소규모 기본 플랜 |
| Business | 중규모 비즈니스 플랜 |
| Enterprise | 대규모 기업 플랜 |
과금 주기
| 주기 | 설명 |
|---|---|
| monthly | 월간 과금 |
| yearly | 연간 과금 |
모델
Subscription (MNG)
테이블: subscriptions
| 필드 | 타입 | 설명 |
|---|---|---|
tenant_id |
bigint | 테넌트 ID |
customer |
string | 고객사명 |
plan |
string | 플랜명 (Starter/Business/Enterprise) |
monthly_fee |
int | 월 정액 |
billing_cycle |
string | monthly / yearly |
start_date |
date | 시작일 |
next_billing |
date | 다음 과금일 |
status |
string | active / trial / cancelled |
users |
int | 사용자 수 |
memo |
text | 메모 |
cancelled_at |
timestamp | 취소 일시 |
cancel_reason |
string(500) | 취소 사유 |
- SoftDeletes 적용
- Casts: monthly_fee, users → integer, start_date, next_billing → date
- Scope:
forTenant($tenantId)
Subscription (API - 상세 구현)
API 프로젝트의 모델은 더 상세한 구독 라이프사이클을 지원합니다:
상태 상수
const STATUS_ACTIVE = 'active';
const STATUS_CANCELLED = 'cancelled';
const STATUS_EXPIRED = 'expired';
const STATUS_PENDING = 'pending';
const STATUS_SUSPENDED = 'suspended';
Relationships
$subscription->tenant // BelongsTo Tenant
$subscription->plan // BelongsTo Plan
$subscription->payments // HasMany Payment
주요 Accessor
$subscription->statusLabel // 상태 한글 라벨
$subscription->isExpired // 만료 여부
$subscription->remainingDays // 잔여 일수
$subscription->isValid // 유효 여부 (active + 미만료)
$subscription->totalPaid // 총 결제 금액
주요 메서드
$subscription->activate() // 활성화
$subscription->renew($newEndDate) // 갱신
$subscription->cancel($reason) // 취소
$subscription->suspend() // 일시정지
$subscription->resume() // 재개
$subscription->isCancellable() // 취소 가능 여부
서비스 클래스 (API)
SubscriptionService
| 메서드 | 설명 |
|---|---|
index(params) |
페이지네이션 목록 (필터, 정렬) |
current() |
현재 활성 구독 조회 |
show(id) |
상세 (플랜, 최근 결제 포함) |
store(data) |
구독 생성 (결제 처리) |
update(id, data) |
구독 수정 |
cancel(id, reason) |
구독 취소 |
suspend(id) |
일시정지 |
resume(id) |
재개 |
renew(id, newEndDate) |
갱신 |
뷰 구성 (React)
subscription.blade.php
┌─ 페이지 헤더 ──────────────────────
│ 제목: "구독료정산"
│ [CSV 내보내기] [등록] 버튼
│
├─ 통계 카드 (4열) ──────────────────
│ 활성 구독 | MRR (월간 반복 수익) | ARR (연간 반복 수익) | 총 사용자
│
├─ 필터 영역 ────────────────────────
│ 검색 (고객사명) | 플랜 (Starter/Business/Enterprise) | 상태 (active/trial/cancelled)
│
├─ 구독 목록 테이블 ─────────────────
│ 고객사 | 플랜 | 월정액 | 과금주기 | 다음과금일 | 사용자 | 상태 | 작업
│ └─ 플랜: Starter(회색), Business(파랑), Enterprise(보라) 배지
│ └─ 상태: active(초록), trial(파랑), cancelled(빨강) 배지
│ └─ 작업: 수정/삭제 버튼
│
├─ 등록/수정 모달 ───────────────────
│ 고객사명, 플랜 선택
│ 월정액, 과금주기(월/연)
│ 시작일, 다음 과금일
│ 상태, 사용자 수, 메모
│ [삭제] [취소] [등록/저장]
│
└─ 비어있을 때: 안내 메시지
HTMX 호환성
- React 기반 페이지이므로 HX-Redirect 필요
@push('scripts')블록에 React/Babel 스크립트 포함