44import com .loopers .domain .coupon .CouponService ;
55import com .loopers .domain .order .Order ;
66import com .loopers .domain .order .OrderService ;
7- import com .loopers .domain .point .PointService ;
87import com .loopers .domain .product .Product ;
98import com .loopers .domain .product .ProductService ;
109import com .loopers .domain .user .User ;
1110import com .loopers .domain .user .UserService ;
1211import com .loopers .support .error .CoreException ;
1312import com .loopers .support .error .ErrorType ;
1413import lombok .RequiredArgsConstructor ;
15- import org . springframework . orm . ObjectOptimisticLockingFailureException ;
14+ import lombok . extern . slf4j . Slf4j ;
1615import org .springframework .stereotype .Component ;
1716import org .springframework .transaction .annotation .Transactional ;
1817
2120import java .util .function .Function ;
2221import java .util .stream .Collectors ;
2322
23+ @ Slf4j
2424@ Component
2525@ RequiredArgsConstructor
26- @ Transactional (readOnly = true )
2726public class OrderFacade {
2827
29- private final OrderService orderService ;
3028 private final UserService userService ;
3129 private final ProductService productService ;
32- private final PointService pointService ;
3330 private final CouponService couponService ;
31+ private final OrderService orderService ;
3432
3533 @ Transactional
36- public OrderInfo placeOrder (OrderPlaceCommand command ) {
37- User user = userService .getUserByUserId (command .userId ());
34+ public OrderInfo createOrder (OrderPlaceCommand command ) {
35+ log .info ("주문 생성 시작: userBusinessId={}, items={}" ,
36+ command .userId (), command .items ().size ());
3837
39- List <Long > productIds = command .items ().stream ()
40- .map (OrderPlaceCommand .OrderItemCommand ::productId )
41- .sorted ()
42- .toList ();
38+ // 1. 사용자 조회
39+ User user = userService .getUserByUserId (command .userId ());
4340
41+ // 2. 상품 조회 (데드락 방지를 위한 정렬 + 비관적 락)
42+ List <Long > productIds = extractAndSortProductIds (command .items ());
4443 List <Product > products = productService .getProductsByIdsWithPessimisticLock (productIds );
45- Map <Long , Product > productMap = products .stream ()
46- .collect (Collectors .toMap (Product ::getId , Function .identity ()));
44+ Map <Long , Product > productMap = toProductMap (products );
4745
46+ // 3. 재고 검증 및 차감 (도메인 로직은 Product가 처리)
4847 validateAndDecreaseStock (command .items (), productMap );
4948
50- Order order = Order .create (user );
49+ // 4. 주문 생성 (도메인 서비스 위임)
50+ List <OrderService .OrderItemRequest > itemRequests = command .items ().stream ()
51+ .map (item -> OrderService .OrderItemRequest .of (item .productId (), item .quantity ()))
52+ .toList ();
53+ Order order = orderService .createOrderWithItems (user , itemRequests , productMap );
5154
52- for (OrderPlaceCommand .OrderItemCommand item : command .items ()) {
53- Product product = productMap .get (item .productId ());
54- order .addOrderItem (product , item .quantity ());
55- }
55+ // 5. 쿠폰 적용 (선택)
56+ Long discountAmount = applyCouponIfExists (command .couponId (), user , order );
57+
58+ // 6. 주문 저장
59+ Order savedOrder = orderService .save (order );
60+
61+ log .info ("주문 생성 완료: orderId={}, totalAmount={}, discountAmount={}" ,
62+ savedOrder .getId (), savedOrder .getTotalAmountValue (), discountAmount );
5663
57- Long totalAmount = order .getTotalAmountValue ();
58- Long discountAmount = 0L ;
59- Long couponId = command .couponId ();
64+ return OrderInfo .from (savedOrder , discountAmount );
65+ }
6066
61- if ( couponId != null ) {
62- Coupon coupon = couponService . getCouponWithOptimisticLock ( couponId );
63- couponService . validateCouponUsable ( coupon , user );
67+ @ Transactional
68+ public void cancelOrder ( Long orderId , Long couponId ) {
69+ log . info ( "주문 취소 시작: orderId={}" , orderId );
6470
65- discountAmount = coupon .calculateDiscount (totalAmount );
66- coupon .use ();
67- couponService .save (coupon );
71+ Order order = orderService .getOrderById (orderId );
72+
73+ // 1. 재고 복구
74+ List <Long > productIds = order .getOrderItems ().stream ()
75+ .map (item -> item .getProductId ())
76+ .sorted ()
77+ .toList ();
78+ List <Product > products = productService .getProductsByIdsWithPessimisticLock (productIds );
79+ Map <Long , Product > productMap = toProductMap (products );
80+
81+ orderService .restoreStock (order , productMap );
82+
83+ // 2. 쿠폰 복구
84+ Long couponIdToRestore = couponId != null ? couponId : order .getCouponId ();
85+ if (couponIdToRestore != null ) {
86+ restoreCoupon (couponIdToRestore );
6887 }
69- long finalAmount = totalAmount - discountAmount ;
70- pointService .usePointWithLock (user .getUserIdValue (), finalAmount );
7188
89+ // 3. 주문 취소 상태 변경
90+ order .markAsCancelled ();
91+ orderService .save (order );
92+
93+ log .info ("주문 취소 완료: orderId={}" , orderId );
94+ }
95+
96+ @ Transactional
97+ public void completeOrder (Long orderId ) {
98+ log .info ("주문 완료 처리: orderId={}" , orderId );
99+
100+ Order order = orderService .getOrderById (orderId );
72101 order .completePayment ();
73- Order savedOrder = orderService .save (order );
102+ orderService .save (order );
103+
104+ log .info ("주문 완료: orderId={}" , orderId );
105+ }
106+
107+ @ Transactional (readOnly = true )
108+ public List <OrderInfo > getMyOrders (String userId ) {
109+ User user = userService .getUserByUserId (userId );
110+ List <Order > orders = orderService .getOrdersByUser (user );
111+
112+ return orders .stream ()
113+ .map (order -> OrderInfo .from (order , 0L ))
114+ .toList ();
115+ }
116+
117+ @ Transactional (readOnly = true )
118+ public OrderInfo getOrderDetail (Long orderId , String userId ) {
119+ User user = userService .getUserByUserId (userId );
120+ Order order = orderService .getOrderByIdAndUser (orderId , user );
74121
75- return OrderInfo .from (savedOrder );
122+ return OrderInfo .from (order , 0L );
76123 }
77124
78- private void validateAndDecreaseStock (List <OrderPlaceCommand .OrderItemCommand > items , Map <Long , Product > productMap ) {
125+ private void validateAndDecreaseStock (
126+ List <OrderPlaceCommand .OrderItemCommand > items ,
127+ Map <Long , Product > productMap
128+ ) {
79129 for (OrderPlaceCommand .OrderItemCommand item : items ) {
80130 Product product = productMap .get (item .productId ());
81131
82132 if (product == null ) {
83- throw new CoreException (ErrorType .NOT_FOUND , "상품을 찾을 수 없습니다." );
133+ throw new CoreException (ErrorType .NOT_FOUND ,
134+ "상품을 찾을 수 없습니다: " + item .productId ());
84135 }
85136
86137 if (!product .isStockAvailable (item .quantity ())) {
@@ -92,19 +143,55 @@ private void validateAndDecreaseStock(List<OrderPlaceCommand.OrderItemCommand> i
92143 }
93144 }
94145
95- public List <OrderInfo > getMyOrders (String userId ) {
96- User user = userService .getUserByUserId (userId );
97- List <Order > orders = orderService .getOrdersByUser (user );
146+ /**
147+ * 쿠폰 적용
148+ */
149+ private Long applyCouponIfExists (Long couponId , User user , Order order ) {
150+ if (couponId == null ) {
151+ return 0L ;
152+ }
98153
99- return orders .stream ()
100- .map (OrderInfo ::from )
101- .toList ();
154+ Coupon coupon = couponService .getCouponWithOptimisticLock (couponId );
155+ couponService .validateCouponUsable (coupon , user );
156+
157+ Long discountAmount = coupon .calculateDiscount (order .getTotalAmountValue ());
158+ coupon .use ();
159+ couponService .save (coupon );
160+
161+ order .applyCoupon (couponId );
162+
163+ log .info ("쿠폰 적용 완료: couponId={}, discountAmount={}" , couponId , discountAmount );
164+
165+ return discountAmount ;
102166 }
103167
104- public OrderInfo getOrderDetail (Long orderId , String userId ) {
105- User user = userService .getUserByUserId (userId );
106- Order order = orderService .getOrderByIdAndUser (orderId , user );
168+ /**
169+ * 쿠폰 복구
170+ */
171+ private void restoreCoupon (Long couponId ) {
172+ Coupon coupon = couponService .getCouponWithOptimisticLock (couponId );
173+ coupon .restore ();
174+ couponService .save (coupon );
175+ log .debug ("쿠폰 복구: couponId={}" , couponId );
176+ }
107177
108- return OrderInfo .from (order );
178+ /**
179+ * 상품 ID 추출 및 정렬
180+ */
181+ private List <Long > extractAndSortProductIds (
182+ List <OrderPlaceCommand .OrderItemCommand > items
183+ ) {
184+ return items .stream ()
185+ .map (OrderPlaceCommand .OrderItemCommand ::productId )
186+ .sorted ()
187+ .toList ();
188+ }
189+
190+ /**
191+ * 상품 리스트를 Map으로 변환
192+ */
193+ private Map <Long , Product > toProductMap (List <Product > products ) {
194+ return products .stream ()
195+ .collect (Collectors .toMap (Product ::getId , Function .identity ()));
109196 }
110197}
0 commit comments