# 구독료정산 ## 개요 구독료정산은 고객사별 구독 플랜과 과금을 관리하는 기능입니다. 플랜별 월정액 관리, 과금주기(월/연), 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 라우트 ```php // 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 라우트 ```php // 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() 응답 구조 ```json { "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 프로젝트의 모델은 더 상세한 구독 라이프사이클을 지원합니다: #### 상태 상수 ```php const STATUS_ACTIVE = 'active'; const STATUS_CANCELLED = 'cancelled'; const STATUS_EXPIRED = 'expired'; const STATUS_PENDING = 'pending'; const STATUS_SUSPENDED = 'suspended'; ``` #### Relationships ```php $subscription->tenant // BelongsTo Tenant $subscription->plan // BelongsTo Plan $subscription->payments // HasMany Payment ``` #### 주요 Accessor ```php $subscription->statusLabel // 상태 한글 라벨 $subscription->isExpired // 만료 여부 $subscription->remainingDays // 잔여 일수 $subscription->isValid // 유효 여부 (active + 미만료) $subscription->totalPaid // 총 결제 금액 ``` #### 주요 메서드 ```php $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 스크립트 포함