447 lines
15 KiB
Markdown
447 lines
15 KiB
Markdown
|
|
# Phase 5: 수익 관리
|
|||
|
|
|
|||
|
|
**기간:** 2주
|
|||
|
|
**우선순위:** 높음 (SaaS 비즈니스 수익 관리)
|
|||
|
|
**의존성:** Phase 1 (테넌트관리)
|
|||
|
|
|
|||
|
|
## 📋 Phase 개요
|
|||
|
|
|
|||
|
|
SaaS 비즈니스 모델의 수익 관리 시스템을 구축합니다.
|
|||
|
|
|
|||
|
|
**포함 기능:**
|
|||
|
|
1. 구독관리 (Subscription Management)
|
|||
|
|
2. 결제관리 (Payment Management)
|
|||
|
|
3. 환불관리 (Refund Management)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1️⃣ 구독관리 (Subscription Management)
|
|||
|
|
|
|||
|
|
### 기능 목록
|
|||
|
|
|
|||
|
|
#### 1.1 구독 플랜 관리
|
|||
|
|
- **경로:** `/mng/subscriptions/plans`
|
|||
|
|
- **기능:**
|
|||
|
|
- 플랜 생성/수정/삭제 (Free, Basic, Pro, Enterprise)
|
|||
|
|
- 가격, 기능 제한 설정
|
|||
|
|
- 사용자 수, 저장 용량 한도
|
|||
|
|
- 월간/연간 결제 옵션
|
|||
|
|
- **권한:** `subscriptions.plans.manage`
|
|||
|
|
|
|||
|
|
#### 1.2 테넌트 구독 목록
|
|||
|
|
- **경로:** `/mng/subscriptions`
|
|||
|
|
- **기능:**
|
|||
|
|
- 전체 테넌트 구독 현황
|
|||
|
|
- 검색 (테넌트명, 플랜)
|
|||
|
|
- 필터 (플랜, 상태, 만료 임박)
|
|||
|
|
- 정렬 (만료일, 가입일)
|
|||
|
|
- **권한:** `subscriptions.index`
|
|||
|
|
|
|||
|
|
#### 1.3 구독 상세 조회
|
|||
|
|
- **경로:** `/mng/subscriptions/{id}`
|
|||
|
|
- **기능:**
|
|||
|
|
- 현재 플랜 정보
|
|||
|
|
- 구독 이력 (업그레이드/다운그레이드)
|
|||
|
|
- 사용량 통계 (사용자 수, 저장 용량)
|
|||
|
|
- 다음 결제 예정일
|
|||
|
|
- **권한:** `subscriptions.show`
|
|||
|
|
|
|||
|
|
#### 1.4 구독 플랜 변경
|
|||
|
|
- **경로:** `/mng/subscriptions/{id}/change-plan`
|
|||
|
|
- **기능:**
|
|||
|
|
- 플랜 업그레이드/다운그레이드
|
|||
|
|
- 즉시 적용 or 다음 결제일 적용
|
|||
|
|
- 차액 정산 (프로레이션)
|
|||
|
|
- **권한:** `subscriptions.change-plan`
|
|||
|
|
|
|||
|
|
#### 1.5 구독 해지
|
|||
|
|
- **경로:** `/mng/subscriptions/{id}/cancel`
|
|||
|
|
- **기능:**
|
|||
|
|
- 즉시 해지 or 기간 만료 후 해지
|
|||
|
|
- 해지 사유 기록
|
|||
|
|
- 데이터 백업 안내
|
|||
|
|
- **권한:** `subscriptions.cancel`
|
|||
|
|
|
|||
|
|
### DB 스키마
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE subscription_plans (
|
|||
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
name VARCHAR(100) NOT NULL COMMENT '플랜명 (Free, Basic, Pro, Enterprise)',
|
|||
|
|
slug VARCHAR(50) UNIQUE NOT NULL,
|
|||
|
|
description TEXT NULL,
|
|||
|
|
price_monthly DECIMAL(10,2) NOT NULL COMMENT '월 가격',
|
|||
|
|
price_yearly DECIMAL(10,2) NOT NULL COMMENT '연 가격',
|
|||
|
|
max_users INT DEFAULT 10 COMMENT '최대 사용자 수',
|
|||
|
|
storage_limit BIGINT DEFAULT 1073741824 COMMENT '저장 용량 (바이트)',
|
|||
|
|
features JSON NULL COMMENT '플랜 기능 목록',
|
|||
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|||
|
|
sort_order INT DEFAULT 0,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
|
|||
|
|
INDEX idx_slug (slug),
|
|||
|
|
INDEX idx_is_active (is_active)
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|||
|
|
|
|||
|
|
CREATE TABLE subscriptions (
|
|||
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
tenant_id BIGINT UNSIGNED UNIQUE NOT NULL,
|
|||
|
|
plan_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
status ENUM('active', 'past_due', 'cancelled', 'expired') DEFAULT 'active',
|
|||
|
|
billing_cycle ENUM('monthly', 'yearly') DEFAULT 'monthly',
|
|||
|
|
current_period_start DATE NOT NULL,
|
|||
|
|
current_period_end DATE NOT NULL,
|
|||
|
|
next_billing_date DATE NOT NULL,
|
|||
|
|
cancelled_at TIMESTAMP NULL,
|
|||
|
|
cancel_reason TEXT NULL,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
|
|||
|
|
INDEX idx_tenant_id (tenant_id),
|
|||
|
|
INDEX idx_plan_id (plan_id),
|
|||
|
|
INDEX idx_status (status),
|
|||
|
|
INDEX idx_next_billing_date (next_billing_date),
|
|||
|
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (plan_id) REFERENCES subscription_plans(id)
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|||
|
|
|
|||
|
|
CREATE TABLE subscription_history (
|
|||
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
subscription_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
old_plan_id BIGINT UNSIGNED NULL,
|
|||
|
|
new_plan_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
action ENUM('upgrade', 'downgrade', 'renew', 'cancel') NOT NULL,
|
|||
|
|
reason TEXT NULL,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
|
|||
|
|
INDEX idx_subscription_id (subscription_id),
|
|||
|
|
FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 엔드포인트
|
|||
|
|
|
|||
|
|
| Method | Endpoint | Description | FormRequest |
|
|||
|
|
|--------|----------|-------------|-------------|
|
|||
|
|
| GET | `/mng/subscriptions/plans` | 플랜 목록 | - |
|
|||
|
|
| POST | `/mng/subscriptions/plans` | 플랜 생성 | `StorePlanRequest` |
|
|||
|
|
| PUT | `/mng/subscriptions/plans/{id}` | 플랜 수정 | `UpdatePlanRequest` |
|
|||
|
|
| GET | `/mng/subscriptions` | 구독 목록 | - |
|
|||
|
|
| GET | `/mng/subscriptions/{id}` | 구독 상세 | - |
|
|||
|
|
| POST | `/mng/subscriptions/{id}/change-plan` | 플랜 변경 | `ChangePlanRequest` |
|
|||
|
|
| POST | `/mng/subscriptions/{id}/cancel` | 구독 해지 | `CancelSubscriptionRequest` |
|
|||
|
|
|
|||
|
|
### Service 클래스
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// app/Services/SubscriptionService.php
|
|||
|
|
class SubscriptionService
|
|||
|
|
{
|
|||
|
|
public function listPlans(): Collection;
|
|||
|
|
public function createPlan(array $data): SubscriptionPlan;
|
|||
|
|
public function updatePlan(SubscriptionPlan $plan, array $data): SubscriptionPlan;
|
|||
|
|
|
|||
|
|
public function list(array $filters): LengthAwarePaginator;
|
|||
|
|
public function find(int $id): Subscription;
|
|||
|
|
public function create(int $tenantId, int $planId, string $billingCycle): Subscription;
|
|||
|
|
public function changePlan(Subscription $subscription, int $newPlanId, bool $immediate = false): Subscription;
|
|||
|
|
public function cancel(Subscription $subscription, string $reason, bool $immediate = false): bool;
|
|||
|
|
public function renew(Subscription $subscription): bool;
|
|||
|
|
|
|||
|
|
public function getUsageStats(int $tenantId): array;
|
|||
|
|
public function checkLimits(int $tenantId): array; // 사용자 수, 저장 용량 체크
|
|||
|
|
public function calculateProration(Subscription $subscription, int $newPlanId): float;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 개발 체크리스트
|
|||
|
|
|
|||
|
|
- [ ] `SubscriptionPlan`, `Subscription`, `SubscriptionHistory` 모델 작성
|
|||
|
|
- [ ] `SubscriptionService` 클래스 작성
|
|||
|
|
- [ ] 프로레이션 계산 로직 구현
|
|||
|
|
- [ ] 사용량 체크 로직 (사용자 수, 저장 용량)
|
|||
|
|
- [ ] 구독 만료 자동 처리 (스케줄러)
|
|||
|
|
- [ ] FormRequest 작성
|
|||
|
|
- [ ] `SubscriptionController` 작성
|
|||
|
|
- [ ] Blade 템플릿 작성
|
|||
|
|
- [ ] i18n 키 작성
|
|||
|
|
- [ ] 테스트 작성
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2️⃣ 결제관리 (Payment Management)
|
|||
|
|
|
|||
|
|
### 기능 목록
|
|||
|
|
|
|||
|
|
#### 2.1 결제 내역 조회
|
|||
|
|
- **경로:** `/mng/payments`
|
|||
|
|
- **기능:**
|
|||
|
|
- 전체 결제 내역 목록
|
|||
|
|
- 검색 (테넌트명, 결제 번호)
|
|||
|
|
- 필터 (상태, 날짜 범위, 플랜)
|
|||
|
|
- 정렬 (결제일, 금액)
|
|||
|
|
- 엑셀 내보내기
|
|||
|
|
- **권한:** `payments.index`
|
|||
|
|
|
|||
|
|
#### 2.2 결제 상세 조회
|
|||
|
|
- **경로:** `/mng/payments/{id}`
|
|||
|
|
- **기능:**
|
|||
|
|
- 결제 정보 (금액, 방법, 일시)
|
|||
|
|
- 구독 정보
|
|||
|
|
- 영수증 출력
|
|||
|
|
- PG사 거래 번호
|
|||
|
|
- **권한:** `payments.show`
|
|||
|
|
|
|||
|
|
#### 2.3 결제 처리
|
|||
|
|
- **경로:** `/mng/payments/process`
|
|||
|
|
- **기능:**
|
|||
|
|
- PG 연동 (토스페이먼츠, 이니시스 등)
|
|||
|
|
- 정기 결제 등록
|
|||
|
|
- 일회성 결제
|
|||
|
|
- 결제 실패 처리
|
|||
|
|
- **권한:** `payments.process`
|
|||
|
|
|
|||
|
|
#### 2.4 결제 실패 관리
|
|||
|
|
- **경로:** `/mng/payments/failed`
|
|||
|
|
- **기능:**
|
|||
|
|
- 실패 내역 조회
|
|||
|
|
- 재시도
|
|||
|
|
- 알림 발송
|
|||
|
|
- **권한:** `payments.failed.manage`
|
|||
|
|
|
|||
|
|
### DB 스키마
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE payments (
|
|||
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
subscription_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
payment_number VARCHAR(50) UNIQUE NOT NULL COMMENT '결제 번호 (자동 생성)',
|
|||
|
|
amount DECIMAL(10,2) NOT NULL COMMENT '결제 금액',
|
|||
|
|
payment_method ENUM('card', 'bank_transfer', 'virtual_account', 'paypal') DEFAULT 'card',
|
|||
|
|
status ENUM('pending', 'completed', 'failed', 'refunded') DEFAULT 'pending',
|
|||
|
|
pg_provider VARCHAR(50) NULL COMMENT 'PG사 (toss, inicis 등)',
|
|||
|
|
pg_transaction_id VARCHAR(255) NULL COMMENT 'PG사 거래 번호',
|
|||
|
|
paid_at TIMESTAMP NULL COMMENT '결제 완료 일시',
|
|||
|
|
failure_reason TEXT NULL,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
|
|||
|
|
INDEX idx_tenant_id (tenant_id),
|
|||
|
|
INDEX idx_subscription_id (subscription_id),
|
|||
|
|
INDEX idx_payment_number (payment_number),
|
|||
|
|
INDEX idx_status (status),
|
|||
|
|
INDEX idx_paid_at (paid_at),
|
|||
|
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (subscription_id) REFERENCES subscriptions(id) ON DELETE CASCADE
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|||
|
|
|
|||
|
|
CREATE TABLE payment_cards (
|
|||
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
card_number_masked VARCHAR(20) NOT NULL COMMENT '마스킹된 카드번호 (1234-****-****-5678)',
|
|||
|
|
card_type VARCHAR(50) NULL COMMENT '카드사',
|
|||
|
|
expiry_date VARCHAR(7) NOT NULL COMMENT 'MM/YYYY',
|
|||
|
|
is_default BOOLEAN DEFAULT FALSE,
|
|||
|
|
billing_key VARCHAR(255) NULL COMMENT 'PG사 빌링키 (정기결제)',
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
deleted_at TIMESTAMP NULL,
|
|||
|
|
|
|||
|
|
INDEX idx_tenant_id (tenant_id),
|
|||
|
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 엔드포인트
|
|||
|
|
|
|||
|
|
| Method | Endpoint | Description | FormRequest |
|
|||
|
|
|--------|----------|-------------|-------------|
|
|||
|
|
| GET | `/mng/payments` | 결제 내역 목록 | - |
|
|||
|
|
| GET | `/mng/payments/{id}` | 결제 상세 | - |
|
|||
|
|
| POST | `/mng/payments/process` | 결제 처리 | `ProcessPaymentRequest` |
|
|||
|
|
| POST | `/mng/payments/{id}/retry` | 결제 재시도 | - |
|
|||
|
|
| GET | `/mng/payments/{id}/receipt` | 영수증 출력 | - |
|
|||
|
|
| GET | `/mng/payments/failed` | 실패 내역 | - |
|
|||
|
|
|
|||
|
|
### Service 클래스
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// app/Services/PaymentService.php
|
|||
|
|
class PaymentService
|
|||
|
|
{
|
|||
|
|
public function list(array $filters): LengthAwarePaginator;
|
|||
|
|
public function find(int $id): Payment;
|
|||
|
|
public function process(int $tenantId, int $subscriptionId, float $amount, string $method): Payment;
|
|||
|
|
public function complete(Payment $payment, string $pgTransactionId): bool;
|
|||
|
|
public function fail(Payment $payment, string $reason): bool;
|
|||
|
|
public function retry(Payment $payment): Payment;
|
|||
|
|
public function generateReceipt(Payment $payment): string; // PDF 경로
|
|||
|
|
|
|||
|
|
public function registerCard(int $tenantId, array $cardData): PaymentCard;
|
|||
|
|
public function getCards(int $tenantId): Collection;
|
|||
|
|
public function processRecurring(Subscription $subscription): Payment;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 개발 체크리스트
|
|||
|
|
|
|||
|
|
- [ ] `Payment`, `PaymentCard` 모델 작성
|
|||
|
|
- [ ] `PaymentService` 클래스 작성
|
|||
|
|
- [ ] PG 연동 구현 (토스페이먼츠 우선)
|
|||
|
|
- [ ] 정기 결제 스케줄러
|
|||
|
|
- [ ] 결제 실패 알림 (이메일, SMS)
|
|||
|
|
- [ ] 영수증 PDF 생성
|
|||
|
|
- [ ] 결제 번호 자동 생성
|
|||
|
|
- [ ] FormRequest 작성
|
|||
|
|
- [ ] 테스트 작성 (Mock PG)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3️⃣ 환불관리 (Refund Management)
|
|||
|
|
|
|||
|
|
### 기능 목록
|
|||
|
|
|
|||
|
|
#### 3.1 환불 요청 목록
|
|||
|
|
- **경로:** `/mng/refunds`
|
|||
|
|
- **기능:**
|
|||
|
|
- 전체 환불 요청 목록
|
|||
|
|
- 검색 (테넌트명, 결제 번호)
|
|||
|
|
- 필터 (상태, 날짜)
|
|||
|
|
- 정렬 (요청일, 금액)
|
|||
|
|
- **권한:** `refunds.index`
|
|||
|
|
|
|||
|
|
#### 3.2 환불 요청 상세
|
|||
|
|
- **경로:** `/mng/refunds/{id}`
|
|||
|
|
- **기능:**
|
|||
|
|
- 환불 요청 정보 (사유, 금액)
|
|||
|
|
- 원 결제 정보
|
|||
|
|
- 환불 처리 이력
|
|||
|
|
- **권한:** `refunds.show`
|
|||
|
|
|
|||
|
|
#### 3.3 환불 승인/반려
|
|||
|
|
- **경로:** `/mng/refunds/{id}/approve` 또는 `/reject`
|
|||
|
|
- **기능:**
|
|||
|
|
- 전액 환불 or 부분 환불
|
|||
|
|
- 승인/반려 사유 기록
|
|||
|
|
- PG 환불 API 호출
|
|||
|
|
- 알림 발송
|
|||
|
|
- **권한:** `refunds.approve`
|
|||
|
|
|
|||
|
|
#### 3.4 환불 처리
|
|||
|
|
- **경로:** `/mng/refunds/{id}/process`
|
|||
|
|
- **기능:**
|
|||
|
|
- PG사 환불 API 연동
|
|||
|
|
- 환불 완료 처리
|
|||
|
|
- 구독 상태 업데이트
|
|||
|
|
- **권한:** `refunds.process`
|
|||
|
|
|
|||
|
|
### DB 스키마
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE refunds (
|
|||
|
|
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
payment_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
tenant_id BIGINT UNSIGNED NOT NULL,
|
|||
|
|
refund_number VARCHAR(50) UNIQUE NOT NULL COMMENT '환불 번호',
|
|||
|
|
refund_amount DECIMAL(10,2) NOT NULL COMMENT '환불 금액',
|
|||
|
|
refund_type ENUM('full', 'partial') DEFAULT 'full',
|
|||
|
|
reason TEXT NOT NULL COMMENT '환불 사유',
|
|||
|
|
status ENUM('pending', 'approved', 'rejected', 'completed', 'failed') DEFAULT 'pending',
|
|||
|
|
approved_by BIGINT UNSIGNED NULL COMMENT '승인자',
|
|||
|
|
approved_at TIMESTAMP NULL,
|
|||
|
|
rejection_reason TEXT NULL,
|
|||
|
|
processed_at TIMESTAMP NULL COMMENT '환불 완료 일시',
|
|||
|
|
pg_refund_transaction_id VARCHAR(255) NULL,
|
|||
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
|
|||
|
|
INDEX idx_payment_id (payment_id),
|
|||
|
|
INDEX idx_tenant_id (tenant_id),
|
|||
|
|
INDEX idx_status (status),
|
|||
|
|
FOREIGN KEY (payment_id) REFERENCES payments(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
|||
|
|
FOREIGN KEY (approved_by) REFERENCES users(id) ON DELETE SET NULL
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 엔드포인트
|
|||
|
|
|
|||
|
|
| Method | Endpoint | Description | FormRequest |
|
|||
|
|
|--------|----------|-------------|-------------|
|
|||
|
|
| GET | `/mng/refunds` | 환불 요청 목록 | - |
|
|||
|
|
| GET | `/mng/refunds/{id}` | 환불 요청 상세 | - |
|
|||
|
|
| POST | `/mng/refunds` | 환불 요청 생성 | `StoreRefundRequest` |
|
|||
|
|
| POST | `/mng/refunds/{id}/approve` | 환불 승인 | `ApproveRefundRequest` |
|
|||
|
|
| POST | `/mng/refunds/{id}/reject` | 환불 반려 | `RejectRefundRequest` |
|
|||
|
|
| POST | `/mng/refunds/{id}/process` | 환불 처리 | - |
|
|||
|
|
|
|||
|
|
### Service 클래스
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// app/Services/RefundService.php
|
|||
|
|
class RefundService
|
|||
|
|
{
|
|||
|
|
public function list(array $filters): LengthAwarePaginator;
|
|||
|
|
public function find(int $id): Refund;
|
|||
|
|
public function create(int $paymentId, float $amount, string $reason, string $type = 'full'): Refund;
|
|||
|
|
public function approve(Refund $refund, int $approverId): bool;
|
|||
|
|
public function reject(Refund $refund, string $reason): bool;
|
|||
|
|
public function process(Refund $refund): bool; // PG 환불 API 호출
|
|||
|
|
public function complete(Refund $refund, string $pgRefundTransactionId): bool;
|
|||
|
|
public function fail(Refund $refund, string $reason): bool;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 개발 체크리스트
|
|||
|
|
|
|||
|
|
- [ ] `Refund` 모델 작성
|
|||
|
|
- [ ] `RefundService` 클래스 작성
|
|||
|
|
- [ ] PG 환불 API 연동
|
|||
|
|
- [ ] 부분 환불 로직 구현
|
|||
|
|
- [ ] 환불 후 구독 상태 업데이트
|
|||
|
|
- [ ] FormRequest 작성
|
|||
|
|
- [ ] `RefundController` 작성
|
|||
|
|
- [ ] 환불 알림 발송
|
|||
|
|
- [ ] i18n 키 작성
|
|||
|
|
- [ ] 테스트 작성
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 Phase 5 완료 조건
|
|||
|
|
|
|||
|
|
### 기능 완성도
|
|||
|
|
- [ ] 구독 플랜 관리 완성
|
|||
|
|
- [ ] 결제 처리 (PG 연동) 동작
|
|||
|
|
- [ ] 환불 워크플로우 동작
|
|||
|
|
- [ ] 정기 결제 스케줄러 동작
|
|||
|
|
|
|||
|
|
### 코드 품질
|
|||
|
|
- [ ] Service-First, FormRequest 준수
|
|||
|
|
- [ ] PG 연동 에러 처리
|
|||
|
|
- [ ] i18n 키 사용
|
|||
|
|
- [ ] Pint, PHPStan 통과
|
|||
|
|
|
|||
|
|
### 비즈니스 로직
|
|||
|
|
- [ ] 프로레이션 계산 정확
|
|||
|
|
- [ ] 사용량 제한 체크 동작
|
|||
|
|
- [ ] 구독 자동 갱신/만료 처리
|
|||
|
|
- [ ] 결제 실패 재시도 로직
|
|||
|
|
|
|||
|
|
### 보안
|
|||
|
|
- [ ] PG 통신 암호화
|
|||
|
|
- [ ] 카드 정보 마스킹
|
|||
|
|
- [ ] 빌링키 안전 저장
|
|||
|
|
- [ ] 결제 내역 접근 권한
|
|||
|
|
|
|||
|
|
### 테스트
|
|||
|
|
- [ ] 구독 변경 시나리오 테스트
|
|||
|
|
- [ ] 결제 처리 Mock 테스트
|
|||
|
|
- [ ] 환불 워크플로우 테스트
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**최종 업데이트:** 2025-11-21
|
|||
|
|
**작성자:** Claude Code
|
|||
|
|
**버전:** 1.0.0
|