팝업스토어 예약 플랫폼 - 한정판 상품 실시간 예약 & 대기열 관리 - 바이브코딩 레시피
고급 PHP Laravel AI: Claude

팝업스토어 예약 플랫폼 - 한정판 상품 실시간 예약 & 대기열 관리

리포지토리명 popup-store-reservation-platform
조회수 149 2건 제출

문제 설명

팝업스토어 예약 플랫폼

트렌디한 팝업스토어를 위한 실시간 예약 시스템을 구축합니다. 한정된 재고와 시간대별 방문 슬롯을 관리하며, 실시간 대기열, 결제 연동, 예약 알림 등 실무 수준의 기능을 Laravel 13.x + Blade로 구현합니다.

필수 기능

  • 관리자 대시보드: 팝업스토어 등록(위치, 기간, 운영시간), 상품 재고 관리, 타임슬롯 설정
  • 실시간 재고 관리: Redis 기반 동시성 제어, 재고 감소 시 Optimistic Locking
  • 타임슬롯 예약 시스템: 15분/30분 단위 방문 시간 선택, 슬롯별 최대 인원 제한
  • 대기열 시스템: Laravel Queue + Redis로 대기번호 발급, 순서 도래 시 SMS/Email 알림
  • 결제 연동: 토스페이먼츠 또는 Stripe 테스트 결제, 결제 완료 시 예약 확정
  • 예약 확인 & QR코드: 예약 완료 시 QR코드 생성, 관리자 QR 스캔 체크인
  • 실시간 알림: Laravel Reverb(WebSocket) 또는 Pusher로 재고 변동, 대기열 순번 알림
  • 인증 & 권한: Laravel Breeze 기반 회원가입/로그인, 관리자/고객 역할 분리(Policy)
  • 캐싱: Redis 캐싱으로 인기 스토어/상품 목록 성능 최적화
  • 예약 취소 & 환불: 취소 정책 적용(24시간 전 전액 환불 등), 큐로 비동기 환불 처리

선택 추가 기능 (보너스)

  • 상품 리뷰 & 별점 시스템
  • 카카오톡/네이버 소셜 로그인
  • 위시리스트 & 알림 신청(재입고 시 알림)
  • 관리자 통계 대시보드(예약률, 매출, 인기 타임슬롯 차트)
  • PHPUnit 테스트(예약 동시성, 재고 차감 로직)
  • S3 스토리지 연동(상품 이미지 업로드)

기술 스택

  • Laravel 13.x + Blade 템플릿
  • Tailwind CSS + Alpine.js(간단한 인터랙션)
  • MySQL/PostgreSQL + Redis
  • Laravel Reverb 또는 Pusher(실시간 알림)
  • Laravel Queue(대기열, 알림 발송)
  • 토스페이먼츠 또는 Stripe API
  • SimpleSoftwareIO/simple-qrcode(QR코드 생성)

제약 조건

  • • Laravel 13.x 필수 사용 (구버전 금지)
  • • Blade 템플릿 엔진 사용 (React/Vue 금지)
  • • Tailwind CSS로 반응형 UI 구성
  • • Redis 필수 사용 (재고 동시성 제어, 대기열, 캐싱)
  • • Laravel Queue 사용 (알림 발송, 환불 처리)
  • • Laravel Reverb 또는 Pusher로 실시간 알림 구현
  • • 토스페이먼츠 또는 Stripe 테스트 결제 연동
  • • QR코드 생성 라이브러리 사용 (simple-qrcode 등)
  • • Optimistic Locking으로 재고 동시성 제어
  • • Policy와 Gate로 권한 관리 (관리자/고객 분리)
  • • 마이그레이션으로 DB 스키마 관리 (외래키, 인덱스 포함)
  • • Eloquent ORM 사용 (Raw Query 최소화)
  • • 환경변수(.env)로 API 키 관리
  • • 예약 취소 정책 비즈니스 로직 구현
  • • 에러 핸들링 & 트랜잭션 처리 필수

프롬프트 레시피

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

1
Step 1
mkdir popup-store-reservation-platform && cd popup-store-reservation-platform

이 폴더에서 Laravel 13.x 프로젝트를 생성하고 기본 설정을 완료합니다.

1. Laravel 13.x 설치:
```bash
composer create-project laravel/laravel . "^13.0"
```

2. .env 파일 설정:
- DB_CONNECTION=mysql (또는 pgsql)
- CACHE_DRIVER=redis
- QUEUE_CONNECTION=redis
- SESSION_DRIVER=redis
- REDIS_HOST=127.0.0.1
- REDIS_PORT=6379

3. Redis 설치 확인 및 Laravel Breeze 설치 (Blade 스택):
```bash
composer require laravel/breeze --dev
php artisan breeze:install blade
npm install
npm run build
```

4. 데이터베이스 마이그레이션 실행:
```bash
php artisan migrate
```

5. Tailwind CSS 설정 확인 (Breeze가 자동 설정)

6. 개발 서버 실행:
```bash
php artisan serve
```

Breeze 로그인/회원가입 화면이 정상 작동하는지 확인하세요.

기대 결과: Laravel 13.x 프로젝트가 생성되고 Laravel Breeze(Blade)가 설치됩니다. Redis, Queue 설정이 완료되고 기본 인증 시스템이 작동합니다. http://localhost:8000에서 로그인/회원가입 화면을 확인할 수 있습니다.

2
Step 2
팝업스토어 예약 시스템의 핵심 데이터베이스 스키마를 설계하고 마이그레이션을 생성합니다.

필요한 테이블:

1. **popup_stores** (팝업스토어 정보)
   - id, name, description, location, start_date, end_date, opening_time, closing_time, slot_duration(분), max_visitors_per_slot, status(upcoming/active/closed), created_at, updated_at
   - 인덱스: status, start_date, end_date

2. **products** (판매 상품)
   - id, popup_store_id(FK), name, description, price, initial_stock, current_stock, image_path, is_limited_edition(boolean), created_at, updated_at
   - 인덱스: popup_store_id, is_limited_edition
   - version 컬럼 추가 (Optimistic Locking용)

3. **time_slots** (타임슬롯)
   - id, popup_store_id(FK), date, start_time, end_time, max_capacity, current_bookings, status(available/full/closed), created_at, updated_at
   - 유니크 인덱스: popup_store_id + date + start_time
   - 인덱스: date, status

4. **reservations** (예약)
   - id, user_id(FK), popup_store_id(FK), time_slot_id(FK), visitor_count, total_amount, status(pending/confirmed/cancelled/completed), payment_status(unpaid/paid/refunded), payment_id, qr_code_path, cancelled_at, created_at, updated_at
   - 인덱스: user_id, popup_store_id, time_slot_id, status, payment_status

5. **reservation_products** (예약-상품 중간 테이블)
   - id, reservation_id(FK), product_id(FK), quantity, price, created_at, updated_at
   - 인덱스: reservation_id, product_id

6. **waiting_queue** (대기열)
   - id, user_id(FK), popup_store_id(FK), queue_number, status(waiting/notified/expired), notified_at, expires_at, created_at, updated_at
   - 인덱스: popup_store_id + status, queue_number

7. **roles 테이블 추가** (관리자/고객 구분)
   - users 테이블에 role 컬럼 추가 (enum: customer, admin)

마이그레이션 생성 및 실행:
```bash
php artisan make:migration create_popup_stores_table
php artisan make:migration create_products_table
php artisan make:migration create_time_slots_table
php artisan make:migration create_reservations_table
php artisan make:migration create_reservation_products_table
php artisan make:migration create_waiting_queue_table
php artisan make:migration add_role_to_users_table
php artisan migrate
```

각 마이그레이션 파일에 위 스키마를 정확히 구현하세요. 외래키 제약조건(onDelete cascade/set null)과 인덱스를 빠짐없이 추가하세요.

기대 결과: 7개의 마이그레이션 파일이 생성되고 실행됩니다. 데이터베이스에 popup_stores, products, time_slots, reservations, reservation_products, waiting_queue 테이블과 users 테이블의 role 컬럼이 생성됩니다. 외래키와 인덱스가 정상적으로 설정됩니다.

3
Step 3
Eloquent 모델을 생성하고 관계를 정의합니다. 각 모델에 필요한 fillable, casts, 관계 메서드를 구현하세요.

1. **PopupStore 모델**:
```bash
php artisan make:model PopupStore
```
- fillable: name, description, location, start_date, end_date, opening_time, closing_time, slot_duration, max_visitors_per_slot, status
- casts: start_date, end_date => date, opening_time, closing_time => datetime
- 관계: hasMany(products), hasMany(timeSlots), hasMany(reservations)
- 스코프: scopeActive (status='active' and 현재 날짜가 start_date~end_date 사이)

2. **Product 모델**:
```bash
php artisan make:model Product
```
- fillable: popup_store_id, name, description, price, initial_stock, current_stock, image_path, is_limited_edition, version
- casts: price => decimal:2, is_limited_edition => boolean
- 관계: belongsTo(popupStore), belongsToMany(reservations through reservation_products)
- 메서드: decrementStock($quantity) - Optimistic Locking 구현

3. **TimeSlot 모델**:
```bash
php artisan make:model TimeSlot
```
- fillable: popup_store_id, date, start_time, end_time, max_capacity, current_bookings, status
- casts: date => date, start_time, end_time => datetime
- 관계: belongsTo(popupStore), hasMany(reservations)
- 메서드: isAvailable(), incrementBookings($count)

4. **Reservation 모델**:
```bash
php artisan make:model Reservation
```
- fillable: user_id, popup_store_id, time_slot_id, visitor_count, total_amount, status, payment_status, payment_id, qr_code_path, cancelled_at
- casts: total_amount => decimal:2, cancelled_at => datetime
- 관계: belongsTo(user), belongsTo(popupStore), belongsTo(timeSlot), belongsToMany(products through reservation_products)

5. **ReservationProduct 모델**:
```bash
php artisan make:model ReservationProduct
```
- fillable: reservation_id, product_id, quantity, price
- casts: price => decimal:2
- 관계: belongsTo(reservation), belongsTo(product)

6. **WaitingQueue 모델**:
```bash
php artisan make:model WaitingQueue
```
- fillable: user_id, popup_store_id, queue_number, status, notified_at, expires_at
- casts: notified_at, expires_at => datetime
- 관계: belongsTo(user), belongsTo(popupStore)

7. **User 모델** 수정:
- fillable에 role 추가
- casts: role => string
- 메서드: isAdmin() - role === 'admin'

모든 모델 파일을 생성하고 관계를 정의하세요.

기대 결과: 6개의 Eloquent 모델(PopupStore, Product, TimeSlot, Reservation, ReservationProduct, WaitingQueue)이 생성되고 User 모델이 수정됩니다. 각 모델에 fillable, casts, 관계 메서드가 정의되며, Product 모델에는 Optimistic Locking을 위한 decrementStock 메서드가 구현됩니다.

4
Step 4
관리자 대시보드를 구현합니다. 관리자만 접근 가능하도록 Policy와 Middleware를 설정하고, 팝업스토어 CRUD, 상품 관리, 타임슬롯 자동 생성 기능을 만드세요.

1. **Policy 생성**:
```bash
php artisan make:policy PopupStorePolicy --model=PopupStore
```
- viewAny, create, update, delete 메서드에서 $user->isAdmin() 체크

2. **Middleware 생성**:
```bash
php artisan make:middleware EnsureUserIsAdmin
```
- handle 메서드에서 auth()->user()->isAdmin() 체크, 아니면 403
- app/Http/Kernel.php에 등록: 'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class

3. **AdminController 생성**:
```bash
php artisan make:controller Admin/DashboardController
php artisan make:controller Admin/PopupStoreController --resource
php artisan make:controller Admin/ProductController --resource
```

4. **라우트 설정** (routes/web.php):
```php
Route::middleware(['auth', 'admin'])->prefix('admin')->name('admin.')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
    Route::resource('popup-stores', PopupStoreController::class);
    Route::resource('popup-stores.products', ProductController::class)->shallow();
    Route::post('popup-stores/{popupStore}/generate-slots', [PopupStoreController::class, 'generateSlots'])->name('popup-stores.generate-slots');
});
```

5. **Admin Dashboard View** (resources/views/admin/dashboard.blade.php):
- Tailwind CSS로 통계 카드 레이아웃 (총 스토어 수, 활성 예약 수, 오늘 매출 등)
- 최근 예약 목록 테이블

6. **PopupStore CRUD Views** (resources/views/admin/popup-stores/):
- index.blade.php: 스토어 목록 테이블 (이름, 기간, 상태, 액션)
- create.blade.php: 스토어 생성 폼 (이름, 설명, 위치, 시작/종료일, 운영시간, 슬롯 길이, 최대 방문자 수)
- edit.blade.php: 스토어 수정 폼
- show.blade.php: 스토어 상세 + 상품 목록 + 타임슬롯 자동 생성 버튼

7. **타임슬롯 자동 생성 로직** (PopupStoreController@generateSlots):
- 스토어의 start_date ~ end_date, opening_time ~ closing_time, slot_duration을 기반으로 TimeSlot 레코드 대량 생성
- 예: 2024-01-01 10:00~20:00, 30분 단위 → 20개 슬롯 생성
- DB::transaction 사용

8. **Product CRUD Views** (resources/views/admin/products/):
- index.blade.php: 상품 목록 (이름, 가격, 재고, 한정판 여부)
- create.blade.php: 상품 생성 폼 (이름, 설명, 가격, 초기 재고, 이미지 업로드, 한정판 체크박스)
- edit.blade.php: 상품 수정 폼

모든 폼에 CSRF 토큰, 유효성 검사(FormRequest), 에러 메시지 표시를 포함하세요. Tailwind CSS로 깔끔한 UI를 구성하세요.

기대 결과: 관리자 전용 대시보드가 생성됩니다. /admin/dashboard에서 통계를 확인하고, /admin/popup-stores에서 팝업스토어를 CRUD할 수 있습니다. 타임슬롯 자동 생성 기능이 작동하며, 상품 관리 페이지에서 상품을 추가/수정/삭제할 수 있습니다. 관리자가 아닌 사용자는 403 에러를 받습니다.

5
Step 5
고객용 팝업스토어 목록 및 상세 페이지를 구현합니다. 실시간 재고와 타임슬롯 예약 UI를 만드세요.

1. **StoreController 생성**:
```bash
php artisan make:controller StoreController
```
- index: 활성 팝업스토어 목록 (Redis 캐싱 5분)
- show: 스토어 상세 + 상품 목록 + 타임슬롯 달력

2. **라우트 설정** (routes/web.php):
```php
Route::get('/', [StoreController::class, 'index'])->name('home');
Route::get('/stores/{popupStore}', [StoreController::class, 'show'])->name('stores.show');
```

3. **홈 페이지 View** (resources/views/stores/index.blade.php):
- Hero 섹션 ("트렌디한 팝업스토어 예약")
- 활성 스토어 카드 그리드 (이미지, 이름, 위치, 기간, "예약하기" 버튼)
- Tailwind CSS 그리드 레이아웃, 반응형

4. **스토어 상세 페이지 View** (resources/views/stores/show.blade.php):
- 스토어 정보 (이름, 설명, 위치, 운영 기간/시간)
- 상품 목록 (카드 형식, 이미지, 이름, 가격, 재고, "장바구니 담기" 버튼)
- 타임슬롯 선택 UI:
  - 날짜 선택 (달력 또는 날짜 버튼)
  - 선택한 날짜의 타임슬롯 목록 (시간, 남은 자리, "예약" 버튼)
  - Alpine.js로 날짜 변경 시 AJAX로 타임슬롯 로드
- 방문 인원 선택 (1~10명)

5. **타임슬롯 AJAX API** (StoreController@getTimeSlots):
```php
Route::get('/stores/{popupStore}/timeslots', [StoreController::class, 'getTimeSlots'])->name('stores.timeslots');
```
- 특정 날짜의 타임슬롯을 JSON으로 반환 (id, start_time, end_time, available_capacity)
- Redis 캐싱 1분

6. **장바구니 세션 관리**:
- Alpine.js + 세션으로 장바구니 구현 (상품 ID, 수량)
- 우측 하단에 장바구니 아이콘 + 수량 뱃지

7. **재고 실시간 표시**:
- 상품 카드에 current_stock 표시 ("남은 재고: X개")
- 재고 0이면 "품절" 표시, 버튼 비활성화

Tailwind CSS로 모바일 친화적인 UI를 구성하세요.

기대 결과: 고객용 홈 페이지(/)에서 활성 팝업스토어 목록을 확인할 수 있습니다. 스토어 상세 페이지(/stores/{id})에서 상품 목록과 타임슬롯을 확인하고, 날짜를 선택하면 AJAX로 해당 날짜의 타임슬롯이 로드됩니다. 장바구니에 상품을 담을 수 있으며, 재고가 실시간으로 표시됩니다.

6
Step 6
예약 생성 로직을 구현합니다. Redis를 사용한 재고 동시성 제어, Optimistic Locking, 트랜잭션 처리를 포함하세요.

1. **ReservationController 생성**:
```bash
php artisan make:controller ReservationController
```

2. **라우트 설정**:
```php
Route::middleware('auth')->group(function () {
    Route::post('/reservations', [ReservationController::class, 'store'])->name('reservations.store');
    Route::get('/reservations/{reservation}', [ReservationController::class, 'show'])->name('reservations.show');
    Route::post('/reservations/{reservation}/cancel', [ReservationController::class, 'cancel'])->name('reservations.cancel');
});
```

3. **store 메서드 구현** (ReservationController@store):
- 입력: popup_store_id, time_slot_id, visitor_count, products (배열: product_id, quantity)
- 유효성 검사: 타임슬롯 가용성, 방문 인원 초과 체크, 상품 재고 체크
- **Redis Lock 사용** (동시 예약 방지):
  ```php
  $lock = Cache::lock('reservation:timeslot:' . $timeSlotId, 10);
  if ($lock->get()) {
      try {
          // 예약 로직
      } finally {
          $lock->release();
      }
  } else {
      return back()->with('error', '동시에 많은 예약이 발생했습니다. 다시 시도해주세요.');
  }
  ```
- **DB::transaction** 사용:
  - TimeSlot의 current_bookings 증가 (incrementBookings)
  - Reservation 생성 (status='pending', payment_status='unpaid')
  - ReservationProduct 레코드 생성 (pivot 테이블)
  - **Product 재고 차감** (Optimistic Locking):
    ```php
    $product = Product::lockForUpdate()->find($productId);
    if ($product->version !== $requestVersion) {
        throw new \Exception('재고가 변경되었습니다.');
    }
    $product->current_stock -= $quantity;
    $product->version++;
    $product->save();
    ```
  - total_amount 계산 (상품 가격 * 수량 합계)
- 예약 성공 시 결제 페이지로 리다이렉트

4. **예약 확인 페이지 View** (resources/views/reservations/show.blade.php):
- 예약 정보 (스토어명, 날짜, 시간, 방문 인원, 주문 상품, 총 금액)
- 결제 상태 표시 (미결제/결제 완료/취소)
- QR코드 표시 (결제 완료 시)
- "결제하기" 버튼 (미결제 시) → 결제 페이지로 이동
- "예약 취소" 버튼 (취소 가능 시)

5. **예약 취소 로직** (ReservationController@cancel):
- 취소 정책 체크 (예: 방문 24시간 전까지만 취소 가능)
- DB::transaction:
  - Reservation status='cancelled', cancelled_at=now()
  - TimeSlot current_bookings 감소
  - Product 재고 복구
  - 결제 완료 상태면 환불 큐 디스패치 (다음 단계에서 구현)
- 성공 메시지와 함께 마이페이지로 리다이렉트

에러 핸들링을 철저히 하고, 사용자 친화적인 에러 메시지를 표시하세요.

기대 결과: 고객이 타임슬롯과 상품을 선택하여 예약을 생성할 수 있습니다. Redis Lock과 Optimistic Locking으로 동시성 문제가 방지되며, 트랜잭션으로 데이터 일관성이 보장됩니다. 예약 확인 페이지에서 예약 정보를 확인하고, 취소 정책에 따라 예약을 취소할 수 있습니다.

7
Step 7
토스페이먼츠 결제 연동을 구현합니다. 테스트 API 키를 사용하여 결제 요청, 승인, 웹훅 처리를 만드세요.

1. **토스페이먼츠 설정**:
- .env에 추가:
  ```
  TOSS_CLIENT_KEY=test_ck_...
  TOSS_SECRET_KEY=test_sk_...
  TOSS_SUCCESS_URL=http://localhost:8000/payments/success
  TOSS_FAIL_URL=http://localhost:8000/payments/fail
  ```
- config/services.php에 추가:
  ```php
  'toss' => [
      'client_key' => env('TOSS_CLIENT_KEY'),
      'secret_key' => env('TOSS_SECRET_KEY'),
      'success_url' => env('TOSS_SUCCESS_URL'),
      'fail_url' => env('TOSS_FAIL_URL'),
  ],
  ```

2. **PaymentController 생성**:
```bash
php artisan make:controller PaymentController
```

3. **라우트 설정**:
```php
Route::middleware('auth')->group(function () {
    Route::get('/payments/{reservation}', [PaymentController::class, 'show'])->name('payments.show');
    Route::post('/payments/{reservation}/request', [PaymentController::class, 'request'])->name('payments.request');
    Route::get('/payments/success', [PaymentController::class, 'success'])->name('payments.success');
    Route::get('/payments/fail', [PaymentController::class, 'fail'])->name('payments.fail');
});
Route::post('/payments/webhook', [PaymentController::class, 'webhook'])->name('payments.webhook'); // CSRF 제외
```

4. **결제 페이지 View** (resources/views/payments/show.blade.php):
- 예약 정보 요약 (스토어, 날짜, 시간, 상품, 총 금액)
- 토스페이먼츠 결제 버튼 (JavaScript SDK 사용)
- 결제 위젯 임베드:
  ```html
  <script src="https://js.tosspayments.com/v1/payment-widget"></script>
  <script>
    const clientKey = '{{ config('services.toss.client_key') }}';
    const paymentWidget = PaymentWidget(clientKey, PaymentWidget.ANONYMOUS);
    // 결제 UI 렌더링
  </script>
  ```

5. **결제 요청 로직** (PaymentController@request):
- 토스페이먼츠 API 호출 (결제 준비)
- Reservation에 payment_id 저장
- 토스 결제창으로 리다이렉트

6. **결제 성공 콜백** (PaymentController@success):
- 토스페이먼츠 API로 결제 승인 (paymentKey, orderId, amount)
- HTTP 클라이언트로 승인 API 호출:
  ```php
  $response = Http::withBasicAuth(config('services.toss.secret_key'), '')
      ->post('https://api.tosspayments.com/v1/payments/confirm', [
          'paymentKey' => $paymentKey,
          'orderId' => $orderId,
          'amount' => $amount,
      ]);
  ```
- 승인 성공 시:
  - Reservation payment_status='paid'
  - QR코드 생성 (SimpleSoftwareIO/simple-qrcode):
    ```php
    $qrCode = QrCode::format('png')->size(300)->generate($reservation->id);
    Storage::disk('public')->put('qrcodes/' . $reservation->id . '.png', $qrCode);
    $reservation->qr_code_path = 'qrcodes/' . $reservation->id . '.png';
    $reservation->save();
    ```
  - 예약 확인 이메일 발송 (큐 사용)
- 예약 확인 페이지로 리다이렉트

7. **결제 실패 콜백** (PaymentController@fail):
- 실패 사유 표시
- 예약 페이지로 리다이렉트

8. **웹훅 처리** (PaymentController@webhook):
- 토스페이먼츠 웹훅 검증 (시그니처 체크)
- 결제 상태 변경 처리 (취소, 환불 등)

QR코드 라이브러리 설치:
```bash
composer require simplesoftwareio/simple-qrcode
```

.env에 TOSS_CLIENT_KEY, TOSS_SECRET_KEY를 테스트 키로 설정하세요.

기대 결과: 결제 페이지(/payments/{reservation})에서 토스페이먼츠 결제 위젯이 표시됩니다. 테스트 카드로 결제하면 승인 API가 호출되고, 예약 상태가 'paid'로 변경되며 QR코드가 생성됩니다. 예약 확인 페이지에서 QR코드를 확인할 수 있고, 예약 확인 이메일이 발송됩니다.

8
Step 8
대기열 시스템을 구현합니다. Redis로 대기번호를 관리하고, Laravel Queue로 순번 도래 시 알림을 발송하세요.

1. **대기열 등록 로직**:
- 타임슬롯이 만석일 때 "대기열 등록" 버튼 표시 (stores/show.blade.php)
- WaitingQueueController 생성:
  ```bash
  php artisan make:controller WaitingQueueController
  ```
- 라우트:
  ```php
  Route::post('/waiting-queue', [WaitingQueueController::class, 'store'])->name('waiting-queue.store');
  ```
- store 메서드:
  - Redis로 대기번호 발급 (INCR 명령):
    ```php
    $queueNumber = Redis::incr('waiting_queue:' . $popupStoreId . ':counter');
    ```
  - WaitingQueue 레코드 생성 (user_id, popup_store_id, queue_number, status='waiting')
  - "대기번호 X번으로 등록되었습니다" 메시지

2. **대기열 관리 Job 생성**:
```bash
php artisan make:job ProcessWaitingQueue
```
- 매 분마다 실행 (스케줄러 또는 큐 워커)
- 로직:
  - 각 팝업스토어별로 취소된 예약 확인
  - 취소로 자리가 생기면 대기열 첫 번째 사용자에게 알림
  - WaitingQueue status='notified', notified_at=now(), expires_at=now()+30분
  - 알림 발송 (이메일/SMS)

3. **알림 발송**:
- Notification 생성:
  ```bash
  php artisan make:notification WaitingQueueNotification
  ```
- 이메일 채널 사용 (toMail 메서드):
  - "예약 가능한 자리가 생겼습니다! 30분 내에 예약하세요."
  - 예약 링크 포함
- Job에서 디스패치:
  ```php
  $user->notify(new WaitingQueueNotification($waitingQueue));
  ```

4. **대기열에서 예약 전환**:
- 알림 받은 사용자가 30분 내에 예약하면 대기열 레코드 삭제
- 30분 경과 시 status='expired'로 변경하고 다음 대기자에게 알림

5. **마이페이지에 대기열 상태 표시**:
- MyPageController 생성:
  ```bash
  php artisan make:controller MyPageController
  ```
- 라우트:
  ```php
  Route::get('/mypage', [MyPageController::class, 'index'])->name('mypage');
  ```
- View (resources/views/mypage/index.blade.php):
  - 내 예약 목록 (예약 번호, 스토어, 날짜, 시간, 상태, QR코드 링크)
  - 내 대기열 목록 (스토어, 대기번호, 상태)

6. **스케줄러 설정** (app/Console/Kernel.php):
```php
protected function schedule(Schedule $schedule)
{
    $schedule->job(new ProcessWaitingQueue)->everyMinute();
}
```

큐 워커 실행:
```bash
php artisan queue:work
```

스케줄러 실행 (개발 환경):
```bash
php artisan schedule:work
```

기대 결과: 타임슬롯이 만석일 때 대기열에 등록할 수 있습니다. Redis로 대기번호가 발급되고, 예약 취소 시 대기열 첫 번째 사용자에게 이메일 알림이 발송됩니다. 마이페이지에서 내 예약과 대기열 상태를 확인할 수 있습니다. 스케줄러와 큐 워커가 정상 작동합니다.

9
Step 9
Laravel Reverb를 사용하여 실시간 알림 기능을 구현합니다. 재고 변동, 대기열 순번 알림을 WebSocket으로 전송하세요.

1. **Laravel Reverb 설치**:
```bash
php artisan install:broadcasting
```
- .env 설정:
  ```
  BROADCAST_DRIVER=reverb
  REVERB_APP_ID=my-app-id
  REVERB_APP_KEY=my-app-key
  REVERB_APP_SECRET=my-app-secret
  REVERB_HOST=localhost
  REVERB_PORT=8080
  REVERB_SCHEME=http
  ```

2. **Reverb 서버 실행**:
```bash
php artisan reverb:start
```

3. **이벤트 생성**:
```bash
php artisan make:event StockUpdated
php artisan make:event WaitingQueueUpdated
```

4. **StockUpdated 이벤트**:
- implements ShouldBroadcast
- public $product;
- broadcastOn(): new Channel('popup-store.' . $this->product->popup_store_id)
- broadcastWith(): ['product_id' => $this->product->id, 'current_stock' => $this->product->current_stock]

5. **WaitingQueueUpdated 이벤트**:
- implements ShouldBroadcast
- public $waitingQueue;
- broadcastOn(): new PrivateChannel('user.' . $this->waitingQueue->user_id)
- broadcastWith(): ['queue_number' => $this->waitingQueue->queue_number, 'status' => $this->waitingQueue->status]

6. **이벤트 디스패치**:
- Product 재고 변경 시 (decrementStock 메서드):
  ```php
  broadcast(new StockUpdated($this));
  ```
- WaitingQueue 상태 변경 시 (ProcessWaitingQueue Job):
  ```php
  broadcast(new WaitingQueueUpdated($waitingQueue));
  ```

7. **프론트엔드에서 WebSocket 수신** (resources/views/stores/show.blade.php):
- Laravel Echo 설정:
  ```html
  <script src="https://cdn.jsdelivr.net/npm/laravel-echo@1.15.3/dist/echo.iife.js"></script>
  <script>
    window.Echo = new Echo({
      broadcaster: 'reverb',
      key: '{{ config('broadcasting.connections.reverb.key') }}',
      wsHost: '{{ config('broadcasting.connections.reverb.host') }}',
      wsPort: {{ config('broadcasting.connections.reverb.port') }},
      forceTLS: false,
      enabledTransports: ['ws', 'wss'],
    });

    Echo.channel('popup-store.{{ $popupStore->id }}')
      .listen('StockUpdated', (e) => {
        // Alpine.js로 상품 재고 업데이트
        console.log('재고 변경:', e);
      });
  </script>
  ```

8. **마이페이지에서 대기열 알림 수신** (resources/views/mypage/index.blade.php):
- Private Channel 인증 (routes/channels.php):
  ```php
  Broadcast::channel('user.{userId}', function ($user, $userId) {
      return (int) $user->id === (int) $userId;
  });
  ```
- Echo private channel:
  ```javascript
  Echo.private('user.{{ auth()->id() }}')
    .listen('WaitingQueueUpdated', (e) => {
      // 알림 토스트 표시
      alert('대기 순번이 도래했습니다! 예약하세요.');
    });
  ```

9. **알림 토스트 UI** (Alpine.js + Tailwind):
- 우측 상단에 알림 토스트 컴포넌트
- WebSocket 이벤트 수신 시 토스트 표시

npm 패키지 설치:
```bash
npm install --save-dev laravel-echo pusher-js
npm run build
```

Reverb 서버가 실행 중인지 확인하세요.

기대 결과: Laravel Reverb 서버가 실행되고, 상품 재고 변경 시 스토어 상세 페이지에서 실시간으로 재고가 업데이트됩니다. 대기열 순번이 도래하면 마이페이지에서 실시간 알림 토스트가 표시됩니다. WebSocket 연결이 정상적으로 작동하며, 이벤트가 브로드캐스트됩니다.

10
Step 10
환불 처리, 관리자 통계 대시보드, QR코드 체크인 기능을 구현하고 전체 시스템을 테스트합니다.

1. **환불 처리 Job 생성**:
```bash
php artisan make:job ProcessRefund
```
- 토스페이먼츠 취소 API 호출:
  ```php
  $response = Http::withBasicAuth(config('services.toss.secret_key'), '')
      ->post('https://api.tosspayments.com/v1/payments/' . $paymentKey . '/cancel', [
          'cancelReason' => '고객 요청',
      ]);
  ```
- 성공 시 Reservation payment_status='refunded'
- 실패 시 재시도 로직 (최대 3회)

2. **예약 취소 시 환불 큐 디스패치** (ReservationController@cancel):
```php
if ($reservation->payment_status === 'paid') {
    ProcessRefund::dispatch($reservation);
}
```

3. **관리자 통계 대시보드** (Admin/DashboardController@index):
- 집계 쿼리:
  - 총 예약 수, 오늘 예약 수, 총 매출, 오늘 매출
  - 인기 스토어 TOP 5 (예약 수 기준)
  - 인기 타임슬롯 (요일/시간대별 예약 분포)
- Chart.js로 시각화:
  - 일별 예약 수 라인 차트
  - 스토어별 매출 바 차트
- View (resources/views/admin/dashboard.blade.php):
  - Tailwind CSS 그리드로 통계 카드 배치
  - Chart.js 차트 임베드

4. **QR코드 체크인 기능** (Admin/CheckinController):
```bash
php artisan make:controller Admin/CheckinController
```
- 라우트:
  ```php
  Route::post('/admin/checkin', [CheckinController::class, 'scan'])->name('admin.checkin.scan');
  ```
- scan 메서드:
  - QR코드에서 reservation_id 추출
  - Reservation 조회 (status='confirmed', payment_status='paid')
  - 유효성 체크 (날짜/시간 일치, 이미 체크인했는지)
  - 체크인 완료 시 status='completed'
- View (resources/views/admin/checkin.blade.php):
  - QR코드 스캔 UI (카메라 또는 수동 입력)
  - 체크인 결과 표시 (성공/실패)

5. **테스트 시나리오 실행**:
- 관리자 로그인 → 팝업스토어 생성 → 타임슬롯 자동 생성 → 상품 등록
- 고객 로그인 → 스토어 상세 → 타임슬롯 선택 → 상품 장바구니 담기 → 예약 생성
- 결제 페이지 → 토스페이먼츠 테스트 결제 → QR코드 확인
- 예약 취소 → 환불 큐 확인
- 대기열 등록 → 알림 수신 (스케줄러 실행)
- 관리자 대시보드 → 통계 확인
- QR코드 체크인 → 예약 완료 확인

6. **에러 핸들링 & 로깅**:
- try-catch 블록으로 모든 외부 API 호출 감싸기
- Log::error()로 에러 로깅
- 사용자 친화적인 에러 메시지 표시

7. **성능 최적화**:
- Redis 캐싱 확인 (스토어 목록, 타임슬롯)
- DB 쿼리 최적화 (N+1 문제 해결, eager loading)
- 인덱스 확인

8. **README.md 작성**:
- 프로젝트 소개
- 설치 방법 (composer install, npm install, .env 설정, 마이그레이션)
- 실행 방법 (php artisan serve, queue:work, reverb:start, schedule:work)
- 테스트 계정 정보 (관리자/고객)
- 기능 목록
- 기술 스택

모든 기능이 정상 작동하는지 확인하고, 코드 품질을 점검하세요.

기대 결과: 환불 처리가 큐로 비동기 실행되며, 관리자 대시보드에서 통계 차트를 확인할 수 있습니다. QR코드 체크인 기능이 작동하여 예약을 완료 처리할 수 있습니다. 전체 시스템이 안정적으로 작동하며, 실시간 알림, 결제, 대기열 등 모든 기능이 통합되어 실무 수준의 팝업스토어 예약 플랫폼이 완성됩니다. README.md에 설치 및 실행 방법이 명확히 기술되어 있습니다.

제출된 작품 (2)

로그인 후 제출

코어 수정 없이 붙여 사용하는 그누보드7 팝업스토어 예약 모듈로, 이벤트/회차/예약/취소/QR 체크인까지 이어지는 전체 운영 흐름을 모듈 단위로 제공합니다.

조회수 448 댓글수 0 추천수 2

팝업스토어 예약 플랫폼

리자
리자
· Claude Code

한정판 상품을 위한 실시간 예약 시스템과 대기열 관리 기능을 제공하는 팝업스토어 예약 플랫폼

조회수 214 댓글수 0 추천수 1

댓글 (0)

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