이커머스 정기구독 박스 플랫폼 - 월간 큐레이션 & 자동 결제 - 바이브코딩 레시피
고급 PHP Laravel AI: Claude

이커머스 정기구독 박스 플랫폼 - 월간 큐레이션 & 자동 결제

리포지토리명 subscription-box-ecommerce-platform
조회수 81 0건 제출

문제 설명

🎁 프로젝트 개요

월간 구독 박스 서비스를 운영할 수 있는 풀스택 이커머스 플랫폼을 만듭니다. 사용자는 다양한 구독 플랜을 선택하고, 매월 자동으로 큐레이션된 상품 박스를 받을 수 있습니다. Laravel 13.x + Blade 템플릿 기반으로 실무 수준의 결제 연동, 스케줄링, 알림 시스템을 구현합니다.

✅ 필수 기능

  • 구독 플랜 관리: 월간/분기/연간 플랜, 가격, 혜택 설명
  • 회원가입/로그인: Laravel Breeze 인증 시스템
  • 구독 신청 플로우: 플랜 선택 → 배송지 입력 → 결제 정보 등록
  • 결제 연동: Stripe/Toss Payments 테스트 모드 연동
  • 자동 갱신 시스템: Laravel Queue + Schedule을 활용한 월별 자동 결제
  • 구독 관리 페이지: 사용자별 구독 현황, 다음 결제일, 구독 취소/일시정지
  • 월간 박스 큐레이션: 관리자가 매월 박스 테마와 포함 상품 설정
  • 배송 추적: 주문 상태(준비중/배송중/완료) 업데이트 및 알림
  • 이메일 알림: 구독 시작, 결제 성공/실패, 배송 시작 알림 (Laravel Mail + Queue)
  • 리뷰 시스템: 받은 박스에 대한 별점 및 사진 리뷰 작성
  • 추천인 코드: 친구 초대 시 할인 쿠폰 지급

🎨 보너스 기능

  • 박스 커스터마이징: 알레르기/선호도 설정으로 일부 상품 교체 옵션
  • 포인트 적립: 리뷰 작성 시 포인트 적립, 다음 결제 시 사용
  • 구독 선물하기: 타인에게 구독권 선물 기능
  • 박스 언박싱 갤러리: 사용자들이 올린 언박싱 사진 공유 피드
  • Redis 캐싱: 인기 플랜, 베스트 리뷰 등 캐싱으로 성능 최적화
  • Webhook 처리: Stripe/Toss 결제 이벤트 실시간 수신 및 처리
  • 관리자 대시보드: 월별 매출, 구독자 수, 이탈률 통계 (Chart.js)
  • A/B 테스팅: 플랜 가격/설명 변형 테스트

🎯 학습 목표

  • Laravel 13.x의 고급 기능 활용 (Queue, Schedule, Event/Listener, Policy)
  • 실무 결제 시스템 연동 및 보안 처리
  • 복잡한 DB 관계 설계 (구독, 결제 내역, 박스 큐레이션)
  • 이메일 큐잉 및 백그라운드 작업 처리
  • Blade 컴포넌트와 Alpine.js로 인터랙티브 UI 구현
  • 실제 서비스 수준의 에러 핸들링 및 로깅

제약 조건

  • Laravel 13.x + Blade 템플릿 사용 (React/Vue 금지)
  • PHP 8.3 이상 필수
  • Tailwind CSS 4.x로 스타일링
  • Alpine.js 3.x로 프론트엔드 인터랙션
  • SQLite (개발) 또는 MySQL (프로덕션) 데이터베이스
  • Stripe 또는 Toss Payments 테스트 모드 연동 (실제 결제 금지)
  • Laravel Breeze로 인증 시스템 구축
  • Laravel Queue (database driver) + Schedule 활용
  • Laravel Mail + Mailtrap (개발용 메일 테스트)
  • 이미지 업로드는 Laravel Storage + public disk 사용
  • 반응형 디자인 필수 (모바일/태블릿/데스크톱)
  • 코드 주석 및 README 작성
  • Git 커밋 메시지 컨벤션 준수 (Conventional Commits)

프롬프트 레시피

아래 프롬프트를 순서대로 AI에게 보내면 됩니다. 복사 버튼을 눌러 바로 사용하세요.

1
Step 1
mkdir subscription-box-ecommerce-platform && cd subscription-box-ecommerce-platform

이 폴더에서 Laravel 13.x 프로젝트를 시작합니다.

# Step 1: 프로젝트 초기 설정 및 인증 시스템 구축

Laravel 13.x 프로젝트를 생성하고 Laravel Breeze를 설치하여 인증 시스템을 구축합니다.

## 요구사항

1. Laravel 13.x 프로젝트 생성 (`composer create-project laravel/laravel .`)
2. `.env` 파일 설정:
   - `DB_CONNECTION=sqlite` (개발용)
   - `QUEUE_CONNECTION=database`
   - `MAIL_MAILER=log` (개발 초기에는 로그로)
3. Laravel Breeze 설치 (`composer require laravel/breeze --dev`)
4. Breeze Blade 스택 설치 (`php artisan breeze:install blade`)
5. Tailwind CSS 설정 확인 및 Alpine.js CDN 추가 (`resources/views/layouts/app.blade.php`)
6. 데이터베이스 마이그레이션 실행 (`php artisan migrate`)
7. 기본 라우트 확인:
   - `/` (홈페이지)
   - `/login`, `/register` (Breeze 제공)
   - `/dashboard` (로그인 후 대시보드)

## 추가 작업

- `resources/views/welcome.blade.php`를 수정하여 랜딩 페이지 구성:
  - 히어로 섹션: "매월 새로운 즐거움을 선물하세요"
  - 구독 플랜 미리보기 (3개 카드: 월간/분기/연간)
  - CTA 버튼: "지금 구독하기" → `/register`로 연결
- Tailwind CSS로 스타일링 (그라디언트 배경, 카드 레이아웃)
- 네비게이션 바에 "플랜 보기", "로그인", "회원가입" 링크 추가

## 실행 명령어

```bash
php artisan serve
```

브라우저에서 `http://localhost:8000` 접속하여 랜딩 페이지와 회원가입/로그인이 작동하는지 확인하세요.

기대 결과: Laravel 13.x 프로젝트가 생성되고 Breeze 인증 시스템이 설치됩니다. 랜딩 페이지에 구독 플랜 미리보기가 표시되며, 회원가입/로그인이 정상 작동합니다. Tailwind CSS와 Alpine.js가 적용된 상태입니다.

2
Step 2
# Step 2: 데이터베이스 설계 및 마이그레이션 생성

구독 플랫폼에 필요한 모든 테이블을 설계하고 마이그레이션을 생성합니다.

## 필요한 테이블

1. **subscription_plans** (구독 플랜)
   - id, name, slug, description, price, billing_cycle (monthly/quarterly/yearly), features (JSON), is_active, created_at, updated_at

2. **subscriptions** (사용자 구독)
   - id, user_id (FK), plan_id (FK), status (active/paused/cancelled), start_date, next_billing_date, cancelled_at, pause_start_date, pause_end_date, created_at, updated_at

3. **payments** (결제 내역)
   - id, subscription_id (FK), amount, currency, status (pending/success/failed), payment_method, stripe_payment_id, paid_at, created_at, updated_at

4. **monthly_boxes** (월간 박스 큐레이션)
   - id, title, description, theme, month, year, image_path, created_at, updated_at

5. **box_items** (박스 포함 상품)
   - id, box_id (FK), name, description, image_path, quantity, created_at, updated_at

6. **shipments** (배송)
   - id, subscription_id (FK), box_id (FK), tracking_number, status (preparing/shipped/delivered), shipped_at, delivered_at, created_at, updated_at

7. **reviews** (리뷰)
   - id, user_id (FK), box_id (FK), rating (1-5), comment, images (JSON), created_at, updated_at

8. **referral_codes** (추천인 코드)
   - id, user_id (FK), code (unique), discount_percent, uses_count, max_uses, expires_at, created_at, updated_at

9. **user_addresses** (배송지)
   - id, user_id (FK), is_default, recipient_name, phone, postal_code, address_line1, address_line2, city, state, created_at, updated_at

## 요구사항

- 각 테이블에 대한 마이그레이션 파일 생성 (`php artisan make:migration create_[table_name]_table`)
- 외래 키 제약조건 설정 (`->foreign('user_id')->references('id')->on('users')->onDelete('cascade')`)
- 인덱스 추가 (status, user_id, plan_id 등 자주 조회되는 컬럼)
- `subscription_plans` 테이블에 시더 데이터 추가:
  - 월간 플랜 (29,000원)
  - 분기 플랜 (79,000원, 9% 할인)
  - 연간 플랜 (299,000원, 14% 할인)
- 마이그레이션 실행 (`php artisan migrate`)
- 시더 실행 (`php artisan db:seed`)

## 추가 작업

- Eloquent 모델 생성 (각 테이블에 대응):
  - `php artisan make:model SubscriptionPlan`
  - `php artisan make:model Subscription`
  - `php artisan make:model Payment`
  - `php artisan make:model MonthlyBox`
  - `php artisan make:model BoxItem`
  - `php artisan make:model Shipment`
  - `php artisan make:model Review`
  - `php artisan make:model ReferralCode`
  - `php artisan make:model UserAddress`
- 모델 관계 정의 (hasMany, belongsTo, belongsToMany 등)
- `SubscriptionPlan` 모델에 `features` 필드를 JSON 캐스팅 (`protected $casts = ['features' => 'array'];`)

기대 결과: 9개의 테이블이 생성되고 마이그레이션이 실행됩니다. subscription_plans 테이블에 3개의 플랜이 시더로 삽입됩니다. 각 모델이 생성되고 관계가 정의됩니다. 외래 키와 인덱스가 올바르게 설정됩니다.

3
Step 3
# Step 3: 구독 플랜 페이지 및 신청 플로우 구현

사용자가 구독 플랜을 선택하고 배송지를 입력한 후 결제 페이지로 이동하는 플로우를 구현합니다.

## 요구사항

### 1. 플랜 목록 페이지 (`/plans`)

- `SubscriptionPlanController@index` 생성
- 활성화된 플랜 조회 (`SubscriptionPlan::where('is_active', true)->get()`)
- `resources/views/plans/index.blade.php` 생성:
  - 3개 플랜을 카드 형태로 표시 (그리드 레이아웃)
  - 각 카드에 플랜명, 가격, 혜택 목록, "선택하기" 버튼
  - 가격 비교 뱃지 ("가장 인기", "최고 할인" 등)
  - Alpine.js로 월간/연간 토글 스위치 구현 (가격 동적 변경)
- 라우트 추가 (`routes/web.php`)

### 2. 플랜 선택 및 배송지 입력 페이지 (`/subscribe/{plan}`)

- `SubscriptionController@create` 생성
- 선택한 플랜 정보 표시
- 배송지 입력 폼:
  - 수령인 이름, 전화번호, 우편번호, 주소, 상세주소
  - "기본 배송지로 저장" 체크박스
- Alpine.js로 우편번호 검색 모달 구현 (Daum 우편번호 API 또는 더미 데이터)
- 폼 유효성 검사 (Laravel Validation)
- "다음 단계: 결제" 버튼 → `/subscribe/{plan}/payment`로 이동
- 라우트 추가 (auth 미들웨어 필수)

### 3. 세션에 데이터 저장

- 배송지 정보를 세션에 저장 (`session()->put('shipping_address', $request->all())`)
- 다음 단계에서 세션 데이터 활용

### 4. Blade 컴포넌트 생성

- `resources/views/components/plan-card.blade.php`: 플랜 카드 재사용 컴포넌트
- props: `$plan` (플랜 객체)
- Tailwind CSS로 스타일링 (hover 효과, 그림자, 버튼 애니메이션)

## 추가 작업

- 네비게이션 바에 "플랜 보기" 링크 추가 (`/plans`)
- 로그인하지 않은 사용자가 "선택하기" 클릭 시 로그인 페이지로 리디렉션
- 플랜 페이지에 FAQ 섹션 추가 (Alpine.js 아코디언)
- 반응형 디자인 확인 (모바일에서 카드가 세로로 쌓이도록)

## 실행 명령어

```bash
php artisan make:controller SubscriptionPlanController
php artisan make:controller SubscriptionController
```

브라우저에서 `/plans` 접속하여 플랜 목록이 표시되는지, 플랜 선택 후 배송지 입력 폼이 나타나는지 확인하세요.

기대 결과: 플랜 목록 페이지가 생성되고 3개의 플랜이 카드 형태로 표시됩니다. 사용자가 플랜을 선택하면 배송지 입력 페이지로 이동하며, 폼 유효성 검사가 작동합니다. 세션에 배송지 정보가 저장되고, Alpine.js로 인터랙티브한 UI가 구현됩니다.

4
Step 4
# Step 4: Stripe 결제 연동 및 구독 생성

Stripe 테스트 모드를 연동하여 결제 페이지를 구현하고, 결제 성공 시 구독을 생성합니다.

## 요구사항

### 1. Stripe 설치 및 설정

- Stripe PHP SDK 설치 (`composer require stripe/stripe-php`)
- `.env`에 Stripe 키 추가:
  ```
  STRIPE_KEY=pk_test_...
  STRIPE_SECRET=sk_test_...
  ```
- `config/services.php`에 Stripe 설정 추가:
  ```php
  'stripe' => [
      'key' => env('STRIPE_KEY'),
      'secret' => env('STRIPE_SECRET'),
  ],
  ```

### 2. 결제 페이지 (`/subscribe/{plan}/payment`)

- `SubscriptionController@showPayment` 생성
- 세션에서 배송지 정보 조회
- 선택한 플랜 정보 표시 (요약)
- Stripe Checkout 또는 Stripe Elements 통합:
  - Stripe Checkout 세션 생성 (`\Stripe\Checkout\Session::create()`)
  - `success_url`, `cancel_url` 설정
  - `line_items`에 플랜 정보 추가
  - `mode => 'subscription'` (정기 결제)
- "결제하기" 버튼 클릭 시 Stripe Checkout 페이지로 리디렉션

### 3. 결제 성공 처리 (`/subscribe/success`)

- `SubscriptionController@success` 생성
- Stripe 세션 ID로 결제 정보 조회
- `subscriptions` 테이블에 새 구독 생성:
  - `user_id`, `plan_id`, `status => 'active'`, `start_date`, `next_billing_date` (1개월 후)
- `user_addresses` 테이블에 배송지 저장
- `payments` 테이블에 결제 내역 저장:
  - `stripe_payment_id`, `amount`, `status => 'success'`, `paid_at`
- 세션 데이터 삭제 (`session()->forget('shipping_address')`)
- 성공 페이지 표시 (`resources/views/subscriptions/success.blade.php`):
  - "구독이 시작되었습니다!" 메시지
  - 다음 결제일 안내
  - "구독 관리" 버튼 → `/my-subscription`

### 4. 결제 취소 처리 (`/subscribe/cancel`)

- `SubscriptionController@cancel` 생성
- "결제가 취소되었습니다" 메시지 표시
- 플랜 페이지로 돌아가기 버튼

### 5. 라우트 추가

```php
Route::middleware('auth')->group(function () {
    Route::get('/subscribe/{plan}/payment', [SubscriptionController::class, 'showPayment'])->name('subscribe.payment');
    Route::get('/subscribe/success', [SubscriptionController::class, 'success'])->name('subscribe.success');
    Route::get('/subscribe/cancel', [SubscriptionController::class, 'cancel'])->name('subscribe.cancel');
});
```

## 추가 작업

- Stripe 테스트 카드 번호 안내 (`4242 4242 4242 4242`)
- 결제 실패 시 에러 핸들링 (try-catch)
- 로깅 추가 (`Log::info('Subscription created', ['user_id' => $user->id])`)
- 결제 성공 시 환영 이메일 발송 (다음 단계에서 구현)

## 테스트

1. 플랜 선택 → 배송지 입력 → 결제 페이지 이동
2. Stripe Checkout에서 테스트 카드로 결제
3. 성공 페이지로 리디렉션되는지 확인
4. 데이터베이스에 구독, 배송지, 결제 내역이 저장되었는지 확인 (`php artisan tinker`로 조회)

```bash
php artisan tinker
>>> \App\Models\Subscription::with('plan', 'user')->latest()->first();
```

기대 결과: Stripe 테스트 모드가 연동되고 결제 페이지가 구현됩니다. 사용자가 Stripe Checkout에서 결제를 완료하면 구독이 생성되고, 데이터베이스에 구독, 배송지, 결제 내역이 저장됩니다. 성공 페이지가 표시되며 다음 결제일이 안내됩니다.

5
Step 5
# Step 5: 구독 관리 페이지 및 일시정지/취소 기능 구현

사용자가 자신의 구독을 관리할 수 있는 페이지를 만들고, 일시정지 및 취소 기능을 구현합니다.

## 요구사항

### 1. 구독 관리 페이지 (`/my-subscription`)

- `SubscriptionController@show` 생성
- 현재 사용자의 활성 구독 조회 (`auth()->user()->subscriptions()->where('status', 'active')->first()`)
- `resources/views/subscriptions/show.blade.php` 생성:
  - 구독 플랜 정보 (이름, 가격, 혜택)
  - 구독 상태 (active/paused/cancelled) 뱃지
  - 시작일, 다음 결제일
  - 배송지 정보
  - 결제 수단 정보 (마지막 4자리)
  - 액션 버튼:
    - "일시정지" (모달 열기)
    - "구독 취소" (모달 열기)
    - "배송지 변경" (폼 토글)
    - "결제 수단 변경" (Stripe Customer Portal 링크)

### 2. 일시정지 기능

- Alpine.js 모달 구현:
  - "일시정지 기간 선택" (1개월/2개월/3개월)
  - "일시정지 사유" 텍스트 입력 (선택)
  - "확인" 버튼 → POST `/my-subscription/pause`
- `SubscriptionController@pause` 생성:
  - 구독 상태를 `paused`로 변경
  - `pause_start_date`, `pause_end_date` 설정
  - `next_billing_date`를 일시정지 종료일 이후로 조정
  - 성공 메시지 표시 ("구독이 일시정지되었습니다")
  - 알림 이메일 발송 (다음 단계에서 구현)

### 3. 구독 취소 기능

- Alpine.js 모달 구현:
  - "정말 취소하시겠습니까?" 확인 메시지
  - "취소 사유" 선택 (드롭다운: 가격 부담, 상품 불만족, 배송 문제 등)
  - "확인" 버튼 → POST `/my-subscription/cancel`
- `SubscriptionController@cancelSubscription` 생성:
  - 구독 상태를 `cancelled`로 변경
  - `cancelled_at` 타임스탬프 기록
  - Stripe 구독 취소 API 호출 (`\Stripe\Subscription::update($stripeSubscriptionId, ['cancel_at_period_end' => true])`)
  - 성공 메시지 표시 ("구독이 취소되었습니다. 현재 결제 기간 종료 시까지 서비스를 이용할 수 있습니다.")
  - 알림 이메일 발송

### 4. 배송지 변경 기능

- Alpine.js로 배송지 폼 토글
- 기존 배송지 정보 불러오기 (input value 채우기)
- "저장" 버튼 → PATCH `/my-subscription/address`
- `SubscriptionController@updateAddress` 생성:
  - 배송지 정보 업데이트
  - 성공 메시지 표시

### 5. 라우트 추가

```php
Route::middleware('auth')->group(function () {
    Route::get('/my-subscription', [SubscriptionController::class, 'show'])->name('subscription.show');
    Route::post('/my-subscription/pause', [SubscriptionController::class, 'pause'])->name('subscription.pause');
    Route::post('/my-subscription/cancel', [SubscriptionController::class, 'cancelSubscription'])->name('subscription.cancel');
    Route::patch('/my-subscription/address', [SubscriptionController::class, 'updateAddress'])->name('subscription.updateAddress');
});
```

## 추가 작업

- 구독이 없는 사용자가 접근 시 플랜 페이지로 리디렉션
- 일시정지 중인 구독은 "재개하기" 버튼 표시
- 취소된 구독은 "재구독하기" 버튼 표시
- Policy 클래스 생성 (`SubscriptionPolicy`):
  - `update`, `cancel` 메서드로 권한 체크
  - 컨트롤러에서 `$this->authorize('update', $subscription)` 호출

## 테스트

1. 구독 관리 페이지 접속 → 구독 정보 표시 확인
2. 일시정지 버튼 → 모달 → 기간 선택 → 확인 → DB에서 상태 확인
3. 구독 취소 버튼 → 모달 → 확인 → DB에서 `cancelled_at` 확인
4. 배송지 변경 → 저장 → DB에서 업데이트 확인

```bash
php artisan make:policy SubscriptionPolicy --model=Subscription
```

기대 결과: 구독 관리 페이지가 생성되고 사용자의 구독 정보가 표시됩니다. 일시정지, 취소, 배송지 변경 기능이 정상 작동하며, Alpine.js 모달로 인터랙티브한 UX가 구현됩니다. Policy로 권한이 체크되고, Stripe API와 연동하여 구독이 취소됩니다.

6
Step 6
# Step 6: 자동 갱신 시스템 및 이메일 알림 구현

Laravel Queue + Schedule을 활용하여 매월 자동 결제를 처리하고, 이메일 알림을 발송합니다.

## 요구사항

### 1. Queue 설정

- `.env`에서 `QUEUE_CONNECTION=database` 확인
- Queue 테이블 마이그레이션 생성 및 실행:
  ```bash
  php artisan queue:table
  php artisan migrate
  ```
- Queue Worker 실행:
  ```bash
  php artisan queue:work
  ```

### 2. 자동 결제 Job 생성

- `php artisan make:job ProcessSubscriptionRenewal`
- `app/Jobs/ProcessSubscriptionRenewal.php` 구현:
  - 오늘이 `next_billing_date`인 활성 구독 조회
  - 각 구독에 대해 Stripe 결제 API 호출 (`\Stripe\PaymentIntent::create()`)
  - 결제 성공 시:
    - `payments` 테이블에 새 결제 내역 추가
    - `next_billing_date`를 1개월 후로 업데이트
    - 성공 이메일 발송 (`SendPaymentSuccessEmail` Job 디스패치)
  - 결제 실패 시:
    - `payments` 테이블에 실패 내역 추가
    - 실패 이메일 발송 (`SendPaymentFailedEmail` Job 디스패치)
    - 3회 실패 시 구독 자동 취소
  - 로깅 추가 (`Log::info`, `Log::error`)

### 3. Schedule 설정

- `app/Console/Kernel.php`의 `schedule` 메서드에 추가:
  ```php
  protected function schedule(Schedule $schedule)
  {
      $schedule->job(new ProcessSubscriptionRenewal)
               ->dailyAt('09:00')
               ->timezone('Asia/Seoul');
  }
  ```
- 로컬에서 테스트:
  ```bash
  php artisan schedule:work
  ```

### 4. 이메일 알림 구현

- Mailable 클래스 생성:
  ```bash
  php artisan make:mail SubscriptionStarted
  php artisan make:mail PaymentSuccess
  php artisan make:mail PaymentFailed
  php artisan make:mail SubscriptionCancelled
  php artisan make:mail ShipmentShipped
  ```
- 각 Mailable 클래스 구현:
  - `build()` 메서드에서 `view()`, `subject()` 설정
  - 생성자에서 필요한 데이터 주입 (구독, 결제 내역 등)
- Blade 이메일 템플릿 생성 (`resources/views/emails/`):
  - `subscription-started.blade.php`: 환영 메시지, 플랜 정보, 다음 결제일
  - `payment-success.blade.php`: 결제 완료 알림, 영수증 링크
  - `payment-failed.blade.php`: 결제 실패 안내, 결제 수단 변경 링크
  - `subscription-cancelled.blade.php`: 취소 확인, 재구독 링크
  - `shipment-shipped.blade.php`: 배송 시작 알림, 송장 번호
- Mailtrap 설정 (`.env`):
  ```
  MAIL_MAILER=smtp
  MAIL_HOST=sandbox.smtp.mailtrap.io
  MAIL_PORT=2525
  MAIL_USERNAME=your_username
  MAIL_ENCRYPTION=tls
  ```

### 5. 이메일 발송 Job 생성

- `php artisan make:job SendPaymentSuccessEmail`
- `php artisan make:job SendPaymentFailedEmail`
- 각 Job에서 `Mail::to($user)->send(new PaymentSuccess($payment))` 호출
- Queue로 비동기 발송 (`dispatch(new SendPaymentSuccessEmail($payment))`)

### 6. 컨트롤러에서 이메일 발송 통합

- `SubscriptionController@success`에서 구독 시작 이메일 발송:
  ```php
  Mail::to($user)->send(new SubscriptionStarted($subscription));
  ```
- `SubscriptionController@cancelSubscription`에서 취소 이메일 발송:
  ```php
  Mail::to($user)->send(new SubscriptionCancelled($subscription));
  ```

## 추가 작업

- 이메일 템플릿을 반응형으로 디자인 (Tailwind CSS 인라인 스타일 또는 MJML)
- 이메일에 브랜드 로고 추가
- 결제 실패 시 재시도 로직 구현 (Job의 `tries`, `retryAfter` 속성 활용)
- Horizon 설치 (선택, Queue 모니터링):
  ```bash
  composer require laravel/horizon
  php artisan horizon:install
  php artisan horizon
  ```

## 테스트

1. Tinker로 테스트 구독 생성 (next_billing_date를 오늘로 설정)
2. `php artisan schedule:work` 실행 → Job이 디스패치되는지 확인
3. `php artisan queue:work` 실행 → Job 처리 및 이메일 발송 확인
4. Mailtrap 대시보드에서 이메일 수신 확인

```bash
php artisan tinker
>>> $subscription = \App\Models\Subscription::first();
>>> $subscription->next_billing_date = now();
>>> $subscription->save();
>>> dispatch(new \App\Jobs\ProcessSubscriptionRenewal);
```

기대 결과: 자동 갱신 Job이 생성되고 Schedule에 등록됩니다. 매일 오전 9시에 결제가 자동 처리되며, 결제 성공/실패 시 이메일이 발송됩니다. Queue로 비동기 처리되어 성능이 최적화됩니다. Mailtrap에서 이메일을 확인할 수 있습니다.

7
Step 7
# Step 7: 월간 박스 큐레이션 및 배송 관리 구현

관리자가 매월 박스 테마와 포함 상품을 설정하고, 배송 상태를 관리하는 기능을 구현합니다.

## 요구사항

### 1. 관리자 권한 설정

- `users` 테이블에 `is_admin` 컬럼 추가 (마이그레이션):
  ```bash
  php artisan make:migration add_is_admin_to_users_table
  ```
- 미들웨어 생성 (`php artisan make:middleware IsAdmin`):
  - `is_admin`이 true인지 체크
  - 아니면 403 에러 반환
- `app/Http/Kernel.php`에 미들웨어 등록:
  ```php
  protected $middlewareAliases = [
      'admin' => \App\Http\Middleware\IsAdmin::class,
  ];
  ```

### 2. 박스 큐레이션 관리 페이지 (`/admin/boxes`)

- `AdminBoxController@index` 생성
- 모든 박스 목록 조회 (최신순)
- `resources/views/admin/boxes/index.blade.php` 생성:
  - 테이블 형태로 박스 목록 표시 (제목, 테마, 월/년, 상품 개수, 액션)
  - "새 박스 만들기" 버튼 → `/admin/boxes/create`
  - 각 박스에 "수정", "삭제" 버튼
- 라우트 추가 (`routes/web.php`):
  ```php
  Route::middleware(['auth', 'admin'])->prefix('admin')->group(function () {
      Route::resource('boxes', AdminBoxController::class);
  });
  ```

### 3. 박스 생성 페이지 (`/admin/boxes/create`)

- `AdminBoxController@create` 생성
- `resources/views/admin/boxes/create.blade.php` 생성:
  - 폼 필드:
    - 제목 (텍스트)
    - 설명 (텍스트 에리어)
    - 테마 (텍스트, 예: "봄맞이 리프레시")
    - 월 (드롭다운, 1-12)
    - 년 (텍스트, 예: 2025)
    - 대표 이미지 (파일 업로드)
  - "저장" 버튼 → POST `/admin/boxes`
- `AdminBoxController@store` 구현:
  - 폼 데이터 유효성 검사
  - 이미지 업로드 처리 (`$request->file('image')->store('boxes', 'public')`)
  - `monthly_boxes` 테이블에 저장
  - 성공 메시지 표시 → 박스 상세 페이지로 리디렉션

### 4. 박스 상세 페이지 (`/admin/boxes/{box}`)

- `AdminBoxController@show` 생성
- 박스 정보 및 포함 상품 목록 표시
- "상품 추가" 버튼 → Alpine.js 모달 열기:
  - 상품명, 설명, 수량, 이미지 입력
  - "추가" 버튼 → POST `/admin/boxes/{box}/items`
- `AdminBoxItemController@store` 생성:
  - `box_items` 테이블에 저장
  - 이미지 업로드 처리
  - 성공 메시지 표시 → 페이지 새로고침
- 각 상품에 "삭제" 버튼

### 5. 배송 관리 기능

- `ShipmentController@index` 생성 (`/admin/shipments`)
- 모든 배송 목록 조회 (필터: 준비중/배송중/완료)
- `resources/views/admin/shipments/index.blade.php` 생성:
  - 테이블 형태로 배송 목록 표시 (구독자명, 박스명, 상태, 송장번호)
  - 상태 변경 드롭다운 (Alpine.js):
    - "준비중" → "배송중" (송장번호 입력 모달)
    - "배송중" → "완료"
  - 상태 변경 시 PATCH `/admin/shipments/{shipment}` 호출
- `ShipmentController@update` 구현:
  - 상태 업데이트
  - 송장번호 저장
  - 배송 시작 시 이메일 발송 (`ShipmentShipped` Mailable)
  - 성공 메시지 표시

### 6. 자동 배송 생성 Job

- `php artisan make:job CreateMonthlyShipments`
- 매월 1일에 실행되도록 Schedule 설정:
  ```php
  $schedule->job(new CreateMonthlyShipments)
           ->monthlyOn(1, '10:00');
  ```
- Job 구현:
  - 현재 월의 박스 조회 (`MonthlyBox::where('month', now()->month)->where('year', now()->year)->first()`)
  - 모든 활성 구독 조회
  - 각 구독에 대해 `shipments` 테이블에 새 배송 생성 (status: preparing)
  - 로깅

## 추가 작업

- 네비게이션 바에 관리자 메뉴 추가 (is_admin인 경우에만 표시)
- 박스 이미지 썸네일 생성 (Intervention Image 라이브러리)
- 배송 상태 변경 이력 로깅 (Spatie Activity Log)
- 박스 미리보기 페이지 (일반 사용자용) → `/boxes/{box}`

## 테스트

1. 관리자 계정으로 로그인 (Tinker로 `is_admin` 설정)
2. `/admin/boxes` 접속 → 새 박스 만들기
3. 박스에 상품 추가
4. `/admin/shipments` 접속 → 배송 상태 변경
5. Mailtrap에서 배송 시작 이메일 확인

```bash
php artisan tinker
>>> $user = \App\Models\User::first();
>>> $user->is_admin = true;
>>> $user->save();
```

기대 결과: 관리자 권한 시스템이 구축되고, 박스 큐레이션 관리 페이지가 생성됩니다. 관리자가 월간 박스를 만들고 상품을 추가할 수 있으며, 배송 상태를 관리할 수 있습니다. 매월 1일에 자동으로 배송이 생성되고, 배송 시작 시 이메일이 발송됩니다.

8
Step 8
# Step 8: 리뷰 시스템 및 박스 갤러리 구현

사용자가 받은 박스에 대한 리뷰를 작성하고, 다른 사용자들의 리뷰를 볼 수 있는 갤러리를 만듭니다.

## 요구사항

### 1. 리뷰 작성 페이지 (`/boxes/{box}/review`)

- `ReviewController@create` 생성
- 배송 완료된 박스만 리뷰 작성 가능 (Policy 체크)
- `resources/views/reviews/create.blade.php` 생성:
  - 박스 정보 표시 (제목, 테마, 포함 상품)
  - 별점 선택 (1-5, Alpine.js로 별 아이콘 클릭 인터랙션)
  - 리뷰 내용 (텍스트 에리어, 최소 50자)
  - 사진 업로드 (최대 5장, multiple input)
  - "리뷰 작성" 버튼 → POST `/boxes/{box}/review`
- Alpine.js로 별점 선택 UI 구현:
  - 빈 별 아이콘 hover 시 채워진 별로 변경
  - 클릭 시 선택 상태 유지
- 이미지 미리보기 기능 (Alpine.js + FileReader API)

### 2. 리뷰 저장

- `ReviewController@store` 구현:
  - 폼 데이터 유효성 검사 (별점 필수, 내용 50자 이상)
  - 이미지 업로드 처리 (최대 5장, `reviews` 폴더에 저장)
  - 이미지 경로를 JSON으로 저장 (`images` 컬럼)
  - `reviews` 테이블에 저장
  - 성공 메시지 표시 → 리뷰 목록 페이지로 리디렉션
  - 리뷰 작성 시 포인트 적립 (보너스 기능, 다음 단계)

### 3. 박스 갤러리 페이지 (`/boxes/{box}`)

- `BoxController@show` 생성
- 박스 정보 및 포함 상품 표시
- 모든 리뷰 조회 (최신순, 페이지네이션)
- `resources/views/boxes/show.blade.php` 생성:
  - 박스 대표 이미지 및 설명
  - 평균 별점 표시 (5점 만점, 소수점 1자리)
  - 리뷰 개수
  - 리뷰 카드 그리드:
    - 작성자명, 별점, 작성일
    - 리뷰 내용 (100자 제한, "더보기" 버튼)
    - 리뷰 사진 (Lightbox 효과, Alpine.js)
  - "리뷰 작성하기" 버튼 (배송 완료 시에만 표시)

### 4. Lightbox 구현

- Alpine.js로 이미지 클릭 시 모달 열기
- 모달에 큰 이미지 표시
- 이전/다음 버튼으로 사진 넘기기
- ESC 키 또는 배경 클릭 시 닫기

### 5. 리뷰 Policy 생성

- `php artisan make:policy ReviewPolicy --model=Review`
- `create` 메서드:
  - 해당 박스를 받은 사용자인지 확인 (`Shipment::where('subscription_id', $user->subscription->id)->where('box_id', $box->id)->where('status', 'delivered')->exists()`)
  - 이미 리뷰를 작성했는지 확인 (중복 방지)
- 컨트롤러에서 `$this->authorize('create', [Review::class, $box])` 호출

### 6. 라우트 추가

```php
Route::get('/boxes/{box}', [BoxController::class, 'show'])->name('boxes.show');
Route::middleware('auth')->group(function () {
    Route::get('/boxes/{box}/review', [ReviewController::class, 'create'])->name('reviews.create');
    Route::post('/boxes/{box}/review', [ReviewController::class, 'store'])->name('reviews.store');
});
```

## 추가 작업

- 리뷰 수정/삭제 기능 (본인만 가능)
- 리뷰 정렬 옵션 (최신순, 별점 높은 순, 별점 낮은 순)
- 리뷰 필터링 (별점별)
- 리뷰에 "도움이 됐어요" 버튼 (좋아요 기능)
- 베스트 리뷰 뱃지 (좋아요 많은 순)
- 이미지 압축 (Intervention Image 라이브러리)
- 스팸 리뷰 신고 기능

## 테스트

1. 배송 완료된 박스에 대해 리뷰 작성 페이지 접속
2. 별점 선택 → 내용 입력 → 사진 업로드 → 제출
3. 박스 갤러리 페이지에서 리뷰 확인
4. 리뷰 사진 클릭 → Lightbox 모달 확인
5. 다른 사용자로 로그인 → 리뷰 작성 버튼 표시 여부 확인

```bash
php artisan tinker
>>> $shipment = \App\Models\Shipment::first();
>>> $shipment->status = 'delivered';
>>> $shipment->delivered_at = now();
>>> $shipment->save();
```

기대 결과: 리뷰 작성 페이지가 생성되고 사용자가 별점과 사진을 포함한 리뷰를 작성할 수 있습니다. 박스 갤러리 페이지에 모든 리뷰가 표시되며, 평균 별점이 계산됩니다. Alpine.js로 별점 선택 UI와 Lightbox가 구현됩니다. Policy로 권한이 체크되어 배송 완료된 박스만 리뷰 작성이 가능합니다.

9
Step 9
# Step 9: 추천인 코드 시스템 및 포인트 적립 구현

친구를 초대하면 할인 쿠폰을 받을 수 있는 추천인 코드 시스템과, 리뷰 작성 시 포인트를 적립하는 기능을 구현합니다.

## 요구사항

### 1. 포인트 시스템 추가

- `users` 테이블에 `points` 컬럼 추가 (마이그레이션):
  ```bash
  php artisan make:migration add_points_to_users_table
  ```
- `point_transactions` 테이블 생성:
  - id, user_id (FK), amount, type (earned/spent), description, created_at, updated_at
- 마이그레이션 실행

### 2. 추천인 코드 생성

- 회원가입 시 자동으로 추천인 코드 생성:
  - `User` 모델의 `creating` 이벤트 리스너 등록
  - 6자리 랜덤 코드 생성 (영문 대문자 + 숫자, 중복 방지)
  - `referral_codes` 테이블에 저장 (discount_percent: 10%, max_uses: 10)
- `User` 모델에 `referralCode` 관계 추가

### 3. 내 추천 코드 페이지 (`/my-referral`)

- `ReferralController@show` 생성
- `resources/views/referrals/show.blade.php` 생성:
  - 내 추천 코드 표시 (큰 글씨, 복사 버튼)
  - 공유 링크 생성 (`/register?ref={code}`)
  - SNS 공유 버튼 (Facebook, Twitter, KakaoTalk)
  - 추천 통계:
    - 사용된 횟수 / 최대 횟수
    - 친구가 가입하면 받을 할인 혜택 설명
  - Alpine.js로 "복사됨!" 토스트 메시지
- 라우트 추가

### 4. 추천 코드로 회원가입

- 회원가입 페이지에 추천인 코드 입력 필드 추가 (선택):
  - URL 쿼리 파라미터로 전달된 코드 자동 입력 (`?ref={code}`)
  - Alpine.js로 코드 유효성 실시간 확인 (AJAX)
- `RegisterController` 수정:
  - 추천인 코드 유효성 검사 (존재하는지, 최대 사용 횟수 초과하지 않았는지)
  - 유효하면 `referral_codes` 테이블의 `uses_count` 증가
  - 추천인에게 1,000 포인트 적립
  - 신규 가입자에게 10% 할인 쿠폰 발급 (첫 구독 시 사용 가능)
  - `point_transactions` 테이블에 기록

### 5. 포인트 적립 기능

- 리뷰 작성 시 500 포인트 적립:
  - `ReviewController@store`에서 리뷰 저장 후 포인트 추가
  - `User` 모델에 `addPoints($amount, $description)` 메서드 추가:
    ```php
    public function addPoints($amount, $description)
    {
        $this->increment('points', $amount);
        PointTransaction::create([
            'user_id' => $this->id,
            'amount' => $amount,
            'type' => 'earned',
            'description' => $description,
        ]);
    }
    ```
- 사진 포함 리뷰 작성 시 추가 200 포인트 (총 700 포인트)

### 6. 포인트 사용 기능

- 결제 페이지에서 포인트 사용 옵션 추가:
  - "보유 포인트: {points}P" 표시
  - "포인트 사용" 체크박스 (Alpine.js로 토글)
  - 사용 포인트 입력 (최대 보유 포인트까지)
  - 결제 금액에서 포인트 차감
- `SubscriptionController@showPayment` 수정:
  - 세션에 포인트 사용 정보 저장
- `SubscriptionController@success` 수정:
  - 결제 성공 시 포인트 차감
  - `point_transactions` 테이블에 기록 (type: spent)

### 7. 포인트 내역 페이지 (`/my-points`)

- `PointController@index` 생성
- `resources/views/points/index.blade.php` 생성:
  - 현재 보유 포인트 (큰 글씨)
  - 포인트 적립/사용 내역 테이블 (최신순, 페이지네이션)
  - 각 내역에 금액, 타입(적립/사용), 설명, 일시
  - 적립은 초록색, 사용은 빨간색으로 표시
- 라우트 추가

## 추가 작업

- 네비게이션 바에 포인트 잔액 표시
- 추천 코드 유효기간 설정 (expires_at)
- 포인트 만료 기능 (1년 후 자동 소멸)
- 포인트 선물 기능 (다른 사용자에게 포인트 전송)
- 포인트 랭킹 페이지 (가장 많이 적립한 사용자)

## 테스트

1. 내 추천 코드 페이지 접속 → 코드 복사
2. 로그아웃 후 `?ref={code}` 파라미터로 회원가입
3. 추천인 계정의 포인트 증가 확인
4. 리뷰 작성 → 포인트 적립 확인
5. 포인트 내역 페이지에서 트랜잭션 확인
6. 결제 시 포인트 사용 → 차감 확인

```bash
php artisan tinker
>>> $user = \App\Models\User::first();
>>> $user->addPoints(1000, '추천인 보상');
>>> $user->points;
```

기대 결과: 추천인 코드 시스템이 구축되고 회원가입 시 자동으로 코드가 생성됩니다. 친구를 초대하면 포인트가 적립되고, 리뷰 작성 시에도 포인트를 받습니다. 결제 시 포인트를 사용할 수 있으며, 포인트 내역 페이지에서 트랜잭션을 확인할 수 있습니다. Alpine.js로 인터랙티브한 UI가 구현됩니다.

10
Step 10
# Step 10: 성능 최적화, 테스트, 배포 준비

프로젝트를 최적화하고 테스트를 작성한 후 배포 준비를 완료합니다.

## 요구사항

### 1. Redis 캐싱 설정

- Redis 설치 및 `.env` 설정:
  ```
  CACHE_DRIVER=redis
  SESSION_DRIVER=redis
  REDIS_HOST=127.0.0.1
  REDIS_PASSWORD=null
  REDIS_PORT=6379
  ```
- Redis 서버 실행 확인 (`redis-cli ping`)
- 인기 플랜, 베스트 리뷰 캐싱:
  ```php
  $popularPlans = Cache::remember('popular_plans', 3600, function () {
      return SubscriptionPlan::where('is_active', true)->get();
  });
  ```
- 박스 갤러리 평균 별점 캐싱 (리뷰 작성/수정 시 캐시 무효화)

### 2. Eager Loading으로 N+1 쿼리 해결

- 구독 목록 조회 시 관계 로딩:
  ```php
  Subscription::with('user', 'plan')->get();
  ```
- 리뷰 목록 조회 시 관계 로딩:
  ```php
  Review::with('user', 'box')->latest()->paginate(20);
  ```
- Laravel Debugbar 설치로 쿼리 확인:
  ```bash
  composer require barryvdh/laravel-debugbar --dev
  ```

### 3. 이미지 최적화

- Intervention Image 설치:
  ```bash
  composer require intervention/image
  ```
- 이미지 업로드 시 리사이징:
  ```php
  $image = Image::make($request->file('image'))->fit(800, 600)->save();
  ```
- WebP 포맷 변환 (선택):
  ```php
  $image->encode('webp', 90)->save();
  ```

### 4. 테스트 작성

- Feature 테스트 생성:
  ```bash
  php artisan make:test SubscriptionTest
  php artisan make:test ReviewTest
  php artisan make:test ReferralTest
  ```
- 주요 테스트 케이스:
  - 플랜 선택 → 배송지 입력 → 결제 플로우
  - 리뷰 작성 (인증, 권한 체크)
  - 추천인 코드로 회원가입 → 포인트 적립
  - 자동 갱신 Job 실행 → 결제 처리
  - 구독 취소/일시정지
- 테스트 실행:
  ```bash
  php artisan test
  ```

### 5. 보안 강화

- CSRF 보호 확인 (모든 POST/PATCH/DELETE 폼에 `@csrf` 토큰)
- XSS 방지 (Blade `{{ }}` 자동 이스케이핑 확인)
- SQL Injection 방지 (Eloquent ORM 사용)
- Rate Limiting 설정 (`routes/web.php`):
  ```php
  Route::middleware(['throttle:60,1'])->group(function () {
      // API 라우트
  });
  ```
- `.env` 파일 `.gitignore`에 추가 확인

### 6. 에러 페이지 커스터마이징

- `resources/views/errors/404.blade.php` 생성 (페이지를 찾을 수 없음)
- `resources/views/errors/403.blade.php` 생성 (권한 없음)
- `resources/views/errors/500.blade.php` 생성 (서버 오류)
- Tailwind CSS로 스타일링

### 7. 로깅 및 모니터링

- 중요 이벤트 로깅:
  - 구독 생성/취소
  - 결제 성공/실패
  - 리뷰 작성
- Sentry 연동 (선택):
  ```bash
  composer require sentry/sentry-laravel
  php artisan sentry:publish --dsn=YOUR_DSN
  ```

### 8. README 작성

- 프로젝트 개요
- 주요 기능 목록
- 기술 스택 (Laravel 13.x, Blade, Tailwind CSS, Alpine.js, Stripe, Redis)
- 설치 방법:
  ```bash
  git clone https://github.com/yourusername/subscription-box-ecommerce-platform.git
  cd subscription-box-ecommerce-platform
  composer install
  cp .env.example .env
  php artisan key:generate
  php artisan migrate --seed
  php artisan storage:link
  npm install && npm run build
  php artisan serve
  ```
- `.env` 설정 가이드 (Stripe 키, Redis, 메일 설정)
- 스크린샷 추가
- 라이선스 정보

### 9. Git 커밋 및 푸시

- `.gitignore` 확인 (`.env`, `node_modules`, `vendor`, `storage` 등)
- 커밋 메시지 컨벤션 준수:
  ```bash
  git add .
  git commit -m "feat: 구독 플랫폼 초기 구현 완료"
  git push origin main
  ```

### 10. 배포 체크리스트

- [ ] `.env`에서 `APP_ENV=production`, `APP_DEBUG=false` 설정
- [ ] 데이터베이스 마이그레이션 실행
- [ ] `php artisan config:cache` 실행
- [ ] `php artisan route:cache` 실행
- [ ] `php artisan view:cache` 실행
- [ ] Queue Worker 설정 (Supervisor)
- [ ] Cron Job 설정 (Laravel Schedule)
- [ ] HTTPS 설정 (Let's Encrypt)
- [ ] Stripe Webhook 엔드포인트 등록

## 추가 작업

- Lighthouse (Google) 성능 테스트 → 90점 이상 목표
- 다국어 지원 (Laravel Localization)
- PWA 변환 (Service Worker)
- GitHub Actions CI/CD 파이프라인 구축

## 최종 테스트

1. 전체 플로우 테스트 (회원가입 → 플랜 선택 → 결제 → 구독 관리 → 리뷰 작성)
2. 다양한 브라우저 테스트 (Chrome, Firefox, Safari, Edge)
3. 모바일 반응형 테스트 (iOS, Android)
4. 부하 테스트 (Apache Bench 또는 Laravel Dusk)
5. 보안 취약점 스캔 (OWASP ZAP)

축하합니다! 🎉 실무 수준의 구독 박스 플랫폼이 완성되었습니다.

기대 결과: Redis 캐싱으로 성능이 최적화되고, N+1 쿼리가 해결됩니다. Feature 테스트가 작성되어 주요 기능이 검증됩니다. 보안이 강화되고 에러 페이지가 커스터마이징됩니다. README가 작성되고 Git에 커밋됩니다. 배포 준비가 완료되어 프로덕션 환경에서 실행할 수 있습니다.

제출된 작품 (0)

로그인 후 제출

아직 제출된 작품이 없습니다. 첫 번째 도전자가 되어보세요!

댓글 (0)

로그인 후 댓글을 남길 수 있습니다.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!