Skip to content

[BE_6] Order (domain) #204

@pjhcsols

Description

@pjhcsols

어떤 기능인가요?

결제(APPROVED) 기준으로 Order를 생성·조회·상태전이하고, 구매자/브랜드/어드민별로 권한이 다른 주문 라이프사이클 API입니다.
주문 상세는 PaymentPaymentIntentLine을 따라 주문에 묶인 모든 결제 라인아이템(상품/사이즈/색상/수량/금액)을 조회합니다.
구매확정 시 지갑 2% 적립, 환불/취소 승인 시 현금/포인트 환불, 쿠폰복구, 재고복원 옵션을 지원합니다.
MySQL용 **실행가능 샘플 데이터(data.sql 수준)**를 함께 제공하여 로컬에서 즉시 E2E 확인 가능합니다.


작업 상세 내용

도메인/엔티티

  • Order 엔티티 및 매핑

    • 컬럼: 합계 4종(원가/브랜드할인/유저추가할인/최종결제), 사용요약(쿠폰/지갑), 수령인/택배/타임스탬프
    • 인덱스: (user_number), (status, created_at)
    • 상태 열거형 OrderStatus(PAID → PREPARING → IN_TRANSIT → DELIVERED → PURCHASE_CONFIRMED …)
    • 라이프사이클 콜백: @PrePersist, @PreUpdate
    • 팩토리: Order.paid(...) (APPROVED 결제에서 최초 생성)
  • 상태 전이 가드/메서드

    • 배송지 수정 제한(배송 진행 이후 불가)
    • 브랜드 전이: toPreparingByBrand, toInTransitByBrand, toDeliveredByBrand
    • 구매확정: confirmByBuyer(DELIVERED에서만)
    • 취소신청: requestCancelByBuyer(PAID에서만)
    • 어드민 임의전이: adminSetStatus

저장소/쿼리

  • OrderRepository

    • findByUserNumberOrderByCreatedAtDesc

    • 브랜드 소유 판별형 페이지 쿼리: findPageByBrandUserNumber

      • Order ↔ Payment ↔ PaymentIntentLine ↔ Product(brandUser) 조인
    • 브랜드 취소요청 전용 페이지: findCancelRequestedByBrand

    • 어드민 필터: adminFilter(buyer,status)

    • 어드민 취소요청 페이지: adminCancelRequested

서비스 레이어

  • 생성(멱등) createFromApprovedPayment(orderId)

    • Payment(APPROVED) 필수

    • PaymentIntentLine 집계로 original, brandDiscount, couponTotal, finalPayAmount 계산

      • brandDiscount = originalTotal - lineBaseTotal (>=0)
      • finalPayAmount = paymentReadRepo.sumNetApprovedAmountByOrderId(orderId) 사용
    • walletUsed = payment.pointsToUse

    • 이미 존재하면 no-op(멱등)

  • 조회

    • 구매자/브랜드/어드민 페이지 + 상세
    • 상세에서 라인아이템 노출: Payment → PaymentIntentLine 로딩
    • 브랜드 상세는 해당 브랜드의 라인만 필터링
    • 리뷰 가능여부 계산(배송완료+14일 내, 주문당 미작성)
  • 구매자 액션

    • 배송지 수정(가드 포함)
    • 구매확정(2% 지갑 적립; WalletService.credit with UK=ORDER_CONFIRM:orderId)
    • 취소/환불/반품 신청 (상태 가드 및 전이)
  • 브랜드 액션

    • PREPARING / IN_TRANSIT(택배사/송장 필수) / DELIVERED 전이
    • 취소신청 승인 → REFUND_REQUESTED, 거절 → PAID
  • 어드민 액션

    • 환불/반품 승인(현금/지갑 환불, 전액 시 쿠폰 복구, 재고 복원 옵션)
    • 환불/반품 거절(상태 되돌림)
    • 강제 취소(전액 환불/포인트 환급/쿠폰복구/재고복원 옵션)
    • 임의 상태 전이
    • 재고 복원 시 Size.valueOf, Color.valueOf 사용 → 라인에 저장된 문자열이 Enum과 정확히 일치해야 함

컨트롤러 & 보안

  • OrderController (구매자/브랜드/어드민 분리)
  • @PreAuthorize + @AuthUser(본인 확인, 브랜드/슈퍼 권한)
  • Swagger 문서(OrderApiDocs) & 예제 페이로드

예외/로깅/성능

  • 예외 정책: BasiliumCustomException(ErrorCode) / AccessDeniedException / IllegalStateException → 의미있는 메시지
  • 로깅: 생성/신청/승인 로그
  • 인덱싱: (status, created_at) 최신순 조회 최적화

데이터/샘플(SQL)

  • 카테고리/유저(노말=1, 브랜드=1, 슈퍼=1 포함)/포인트/월렛/상품(1~9)/옵션/사진/재질 예시

  • 상품공개할인(ProductDiscount)/사용자추가할인(UserDiscount) 예시

  • 브랜드쿠폰캠페인/개인지갑 발급 예시

  • 결제/결제라인 2건 예시(상품1,2)

  • 주문(Order) 2건 예시(결제와 1:1 매칭)

    • 20250903-200500-UUID-TEST-1 (user=1, 상품2, 53,000원, PAID)
    • 20250903-200000-UUID-EXAMPLE-2 (user=2, 상품1, 원가 53,000→브랜드할인 34,000, 최종 19,000, DELIVERED)
  • 브랜드 소유 판별Order 단독이 아니라 반드시 Payment + PaymentIntentLine과 함께(Repo 쿼리, Service의 assertBrandOwns)

호환성/검증 포인트

  • 브랜드 주문 목록/상세 동작 확인: 조인 경로 유효(주문↔결제↔라인↔상품↔브랜드)
  • 상세 라인아이템 노출: 결제 라인 전부 노출(브랜드 상세는 본인 라인만)
  • 금액 정합: original = Σ(productPrice*qty), brandDiscount = original - Σ(lineBase), couponTotal = Σ(couponDiscount), finalPayAmount = 승인합계
  • 멱등성: createFromApprovedPayment 중복 호출 시 재생성 방지
  • Enum 일치: 라인의 size, color는 재고복원 시 Size/Color enum과 일치해야 함(예: M/L, BLACK/WHITE)
  • 권한 가드: 구매자 본인/브랜드 소유/어드민

권장 보완(TODO)

  • DB FK 추가: orders.order_idpayment.order_id(1:1) 참조 무결성
    payment_intent_line.payment_idpayment.id, …product_idproduct.product_id
  • 트랜잭션 경계 테스트: 환불 승인 시 Payment 업데이트, 월렛 적립/환불, 쿠폰 복구, 재고 복원 원자성 E2E 테스트
  • 통합/격리 테스트(Testcontainers): 생성/조회/전이/권한/예외/금액집계
  • 동시성 제어: 동일 주문에 대한 다중 전이/적립 경쟁 조건 테스트(낙관락/UK 기반 멱등 보강)
  • 검증 강화: 배송지 필드 포맷, 전화번호/우편번호 정규화, 수취인 길이 제한 등 Bean Validation
  • 관측성: 주요 전이/실패 지표(metrics) & 감사 로그(audit) 추가
  • 에러코드 문서화: ErrorCode별 HTTP 매핑/가이드
  • 부분환불 시 UI/정책 정의: 라인단위 환불/부분 수량 환불 UX 결정
  • 다브랜드 주문 UI 정책: 브랜드 상세에서 라인 필터링 OK → 알림/정산 정책 연계

참고할만한 자료(선택)

  • 핵심 조인 경로(브랜드 주문 조회)
    Order oPayment p (on p.orderId = o.orderId)PaymentIntentLine l (on l.payment = p)Product prod (on l.product)BrandUser
    → 브랜드 필터: prod.brandUser.userNumber = :brandUserNumber

  • 상세 아이템 로딩 코드 요약

    • paymentRepo.findByOrderId(orderId).map(p -> lineRepo.findAllByPayment_Id(p.getId()))
    • 브랜드 상세: product.brandUser.userNumber == brandUserFilter 필터
  • 샘플 주문 INSERT(요약)

    • PAID: 20250903-200500-UUID-TEST-1 / user=1 / 53,000 / 할인0 / 송장없음
    • DELIVERED: 20250903-200000-UUID-EXAMPLE-2 / user=2 / original 53,000 / brandDiscount 34,000 / final 19,000 / CJ 송장 포함
  • 주의

    • 브랜드 판별은 orders만으로 불가. 반드시 payment/payment_intent_line과 함께 봐야 함(이미 쿼리/서비스 반영).
    • 라인 size/colorEnum 상수 문자열과 일치해야 재고복원 성공.
    • 주문을 수동 INSERT할 때는 반드시 동일한 payment.order_id가 존재해야 브랜드 화면/상세가 정상 동작.

Metadata

Metadata

Assignees

Labels

feature새로운 기능 추가

Projects

Status

In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions