백로그
0할 일
0진행 중
0리뷰
0완료 (30일)
3할인코드 DB 마이그레이션 + 모델 + 서비스
## 목표 할인코드(DiscountCode) 테이블 생성, 모델 정의, 할인코드 검증/적용 서비스 구현 ## 작업 내용 ### 1. DB 마이그레이션 - `discount_codes` 테이블 생성: - name (string, NOT NULL) - 할인코드 이름 - code (string, NOT NULL, UNIQUE) - 할인코드 (자동생성 가능) - discount_type (string, NOT NULL) - 'percentage' 또는 'fixed_amount' - value (integer, NOT NULL) - 할인값 (퍼센트 또는 원) - applies_to_all (boolean, default: true) - 전체 상품 적용 여부 - max_uses (integer, nullable) - 최대 사용횟수 (null이면 무제한) - current_uses (integer, default: 0) - 현재 사용횟수 - min_purchase_amount (integer, default: 0) - 최소 구매금액 - expires_at (datetime, nullable) - 만료일 - active (boolean, default: true) - 활성화 여부 - metadata (jsonb, default: {}) - timestamps - 인덱스: code (unique), active, expires_at - `discount_code_products` 조인 테이블 생성: - discount_code_id (references, FK) - product_id (references, FK) - 인덱스: [discount_code_id, product_id] (unique) - `payments` 테이블의 discount_code_id를 UUID FK로 변환하는 마이그레이션 ### 2. 모델 - `DiscountCode` 모델: - enum discount_type: { percentage: "percentage", fixed_amount: "fixed_amount" } - has_many :discount_code_products, dependent: :destroy - has_many :products, through: :discount_code_products - has_many :payments - validates :name, :code, :discount_type, :value, presence: true - validates :code, uniqueness: true - validates :value, numericality: { greater_than: 0 } - validate: percentage는 1-100 범위 - scope :available - active && (expires_at nil OR 미래) && (max_uses nil OR current_uses < max_uses) - before_create :generate_code_if_blank (SecureRandom.alphanumeric(8).upcase) - `applicable_to?(product)`: applies_to_all이거나 products에 포함 - `calculate_discount(amount)`: 할인 금액 계산 - `usable?`: 사용 가능 여부 확인 - `use!`: current_uses 증가 - `DiscountCodeProduct` 모델: - belongs_to :discount_code - belongs_to :product - `Payment` 모델 수정: - belongs_to :discount_code, optional: true ### 3. 서비스 - `DiscountCodes::ValidationService`: - initialize(code:, product: nil, amount: 0) - call: 유효성 검사 (존재, 활성, 만료, 사용횟수, 최소금액, 상품 적용) - Result(discount_code, discount_amount, error) - `DiscountCodes::ApplyService`: - initialize(discount_code:, payment:) - call: 할인코드 사용 기록 (current_uses 증가, payment.discount_code_id 설정) ### 4. Fixture - test/fixtures/discount_codes.yml (active, expired, max_used, inactive 등) - test/fixtures/discount_code_products.yml ### 5. 테스트 - test/models/discount_code_test.rb - test/models/discount_code_product_test.rb - test/services/discount_codes/validation_service_test.rb - test/services/discount_codes/apply_service_test.rb ## 완료 기준 - 모든 마이그레이션 실행 성공 - 모델 테스트 통과 - 서비스 테스트 통과 - 기존 테스트가 깨지지 않음
할인코드 어드민 CRUD + UI
## 목표 어드민 패널에 할인코드 관리 기능 구현 (CRUD + 토글) ## 작업 내용 ### 1. 라우트 (config/routes.rb) ```ruby # admin 네임스페이스 안에 추가 resources :discount_codes, except: :destroy do member do patch :toggle_active end end ``` ### 2. 컨트롤러 (app/controllers/admin/discount_codes_controller.rb) - Admin::BaseController 상속 - 기존 Admin::ProductsController 패턴 따름 - 액션: index, show, new, create, edit, update, toggle_active - Index: 검색(name, code), 필터(discount_type, active), 정렬, 페이지네이션 - PER_PAGE = 30 ### 3. 뷰 (app/views/admin/discount_codes/) - index.html.erb: 테이블 (이름, 코드, 유형, 값, 적용상품, 사용횟수/최대, 만료일, 활성화, 액션) - show.html.erb: 상세 정보 + 적용 상품 목록 + 사용 내역 - _form.html.erb: 생성/수정 공통 폼 - 이름 (text) - 코드 (text, 자동생성 안내) - 할인 유형 (select: percentage/fixed_amount) - 할인값 (number) - 전체 적용 (checkbox) - 적용 상품 (multi-select, applies_to_all이 false일 때만) - 최대 사용횟수 (number, optional) - 최소 구매금액 (number) - 만료일 (datetime) - 활성화 (checkbox) - new.html.erb, edit.html.erb: form 렌더 ### 4. 사이드바 (app/views/admin/shared/_sidebar.html.erb) - "Payments" 아래에 "Discount Codes" 메뉴 추가 - 아이콘: heroicon 'tag' 또는 유사 ### 5. 로케일 (config/locales/*.yml) - en.yml, ko.yml, zh.yml, vi.yml에 admin.discount_codes 섹션 추가 - 기존 admin.products 패턴 참고 ### 6. Tailwind CSS 스타일 - 기존 어드민 뷰 (payments, products) 스타일과 일관성 유지 - auto_submit_controller 사용 (검색/필터) ### 7. 테스트 (test/integration/admin/discount_codes_test.rb) - 접근 제어 (일반 사용자 차단) - Index (기본 조회, 검색, 필터, 정렬, 페이지네이션) - Create (성공, 실패) - Update (성공, 실패) - Toggle active - Show ## ⚠️ 의존성 - model-dev가 DiscountCode 모델과 마이그레이션을 완료해야 함 - 마이그레이션이 아직 없으면 먼저 `bin/rails db:migrate` 실행 후 작업 - 모델이 없으면 모델 파일이 생길 때까지 컨트롤러/뷰부터 작성 ## 파일 담당 범위 (이 파일들만 수정) - app/controllers/admin/discount_codes_controller.rb (신규) - app/views/admin/discount_codes/* (신규) - app/views/admin/shared/_sidebar.html.erb (수정) - config/routes.rb (수정 - admin 블록 안에 추가) - config/locales/en.yml, ko.yml, zh.yml, vi.yml (수정 - admin.discount_codes 추가) - test/integration/admin/discount_codes_test.rb (신규) ## 완료 기준 - 어드민에서 할인코드 CRUD 가능 - 사이드바에 메뉴 표시 - 4개 로케일 파일 업데이트 - 통합 테스트 통과
결제 흐름에 할인코드 통합
## 목표 결제 페이지에서 할인코드를 입력하고 검증/적용하는 기능 구현 ## 작업 내용 ### 1. 할인코드 검증 API (app/controllers/discount_codes_controller.rb 신규) ```ruby # POST /discount_codes/validate class DiscountCodesController < ApplicationController def validate result = DiscountCodes::ValidationService.call( code: params[:code], product: find_product, # payment_type으로 product 조회 amount: params[:amount].to_i ) if result.success? render json: { valid: true, discount_amount: result.discount_amount, discount_code_id: result.discount_code.id, message: "할인이 적용되었습니다" } else render json: { valid: false, message: result.error } end end end ``` ### 2. 라우트 추가 (config/routes.rb) ```ruby # admin 블록 바깥에 추가 post "discount_codes/validate", to: "discount_codes#validate" ``` ### 3. 결제 흐름 수정 **PaymentsController#checkout 수정**: - checkout_params에 `discount_code_id` 추가 - discount_code_id가 있으면 DiscountCodes::ValidationService로 재검증 - 검증 성공 시 CheckoutService에 discount_amount 전달 - 결제 완료 후 DiscountCodes::ApplyService로 사용 기록 **PaymentsController#success 수정**: - 결제 확인 후 할인코드 사용 처리 (ApplyService) ### 4. Stimulus 컨트롤러 (app/javascript/controllers/discount_code_controller.js) ```javascript // 할인코드 입력 + 검증 + UI 업데이트 import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["input", "result", "discountAmount", "discountCodeId", "totalAmount"] async validate() { const code = this.inputTarget.value.trim() if (!code) return const response = await fetch("/discount_codes/validate", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": document.querySelector("[name='csrf-token']").content }, body: JSON.stringify({ code, amount: this.originalAmount, payment_type: this.paymentType }) }) const data = await response.json() // UI 업데이트: 할인 금액 표시, hidden field 설정 } } ``` ### 5. 결제 뷰 수정 - 결제 페이지에 할인코드 입력 필드 추가 - 할인 적용 시 할인 금액과 최종 결제금액 표시 - hidden field로 discount_code_id 전달 ### 6. CustomPaymentsController 수정 - 커스텀 결제 링크에서도 할인코드 사용 가능하게 (선택사항) ### 7. 테스트 - test/controllers/discount_codes_controller_test.rb (validate 엔드포인트) - test/integration/payments_discount_test.rb (결제 + 할인코드 통합) ## ⚠️ 의존성 - model-dev가 DiscountCode 모델과 ValidationService를 완료해야 함 - 먼저 `bin/rails db:migrate` 실행하여 테이블 확인 - 모델/서비스가 없으면 컨트롤러/뷰/JS부터 작성 ## 파일 담당 범위 - app/controllers/discount_codes_controller.rb (신규) - app/javascript/controllers/discount_code_controller.js (신규) - app/controllers/payments_controller.rb (수정) - app/views/payments/ 관련 뷰 (수정) - config/routes.rb (수정 - discount_codes/validate 추가) - test/controllers/discount_codes_controller_test.rb (신규) - test/integration/payments_discount_test.rb (신규) ## 완료 기준 - 결제 페이지에서 할인코드 입력/검증 가능 - 유효한 코드 입력 시 할인 금액 표시 - 결제 완료 시 할인코드 사용 기록 - 테스트 통과