- 영업수수료정산: 입금등록, 수당자동계산, 승인프로세스, 지급추적 - 컨설팅비용정산: 상담시간×시급 정산, 상태관리 - 고객사정산: 월별 매출/수수료/비용 순정산액 관리 - 구독료정산: 플랜별 과금, MRR/ARR 집계, 구독 라이프사이클 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
261 lines
7.5 KiB
Markdown
261 lines
7.5 KiB
Markdown
# 구독료정산
|
||
|
||
## 개요
|
||
|
||
구독료정산은 고객사별 구독 플랜과 과금을 관리하는 기능입니다.
|
||
플랜별 월정액 관리, 과금주기(월/연), 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 스크립트 포함
|