diff --git a/apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java
index 4186c20fa..528901a29 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/brand/BrandFacade.java
@@ -15,7 +15,7 @@ public class BrandFacade {
 
   private final BrandService brandService;
 
-  public BrandResult viewBrand(Long brandId) {
+  public BrandResult retrieveBrand(Long brandId) {
     Brand brand = brandService.getById(brandId)
         .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "브랜드를 찾을 수 없습니다."));
     return BrandResult.from(brand);
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/like/LikeFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/like/LikeFacade.java
index 9d2244263..5cc913b2e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/like/LikeFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/like/LikeFacade.java
@@ -1,11 +1,14 @@
 package com.loopers.application.like;
 
 import com.loopers.domain.product.ProductService;
+import com.loopers.domain.productlike.LikeSortType;
+import com.loopers.domain.productlike.LikedProduct;
 import com.loopers.domain.productlike.ProductLikeService;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;
-import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -17,19 +20,19 @@ public class LikeFacade {
   private final ProductService productService;
 
   @Transactional
-  public void likeProduct(Long userId, Long productId) {
+  public void registerProductLike(Long userId, Long productId) {
     productService.getById(productId)
         .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "상품을 찾을 수 없습니다."));
-    try {
-      productLikeService.createLike(userId, productId);
-      productService.increaseLikeCount(productId);
-    } catch (DataIntegrityViolationException e) {
 
+    boolean result = productLikeService.createLike(userId, productId);
+    if (!result) {
+      return;
     }
+    productService.increaseLikeCount(productId);
   }
 
   @Transactional
-  public void unlikeProduct(Long userId, Long productId) {
+  public void cancelProductLike(Long userId, Long productId) {
     productService.getById(productId)
         .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "상품을 찾을 수 없습니다."));
 
@@ -39,4 +42,10 @@ public void unlikeProduct(Long userId, Long productId) {
       productService.decreaseLikeCount(productId);
     }
   }
+
+  @Transactional(readOnly = true)
+  public Page<LikedProduct> retrieveLikedProducts(Long userId, LikeSortType sortType,
+      Pageable pageable) {
+    return productLikeService.findLikedProducts(userId, sortType, pageable);
+  }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java
index e8553ee12..5ec86c0b0 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java
@@ -2,14 +2,20 @@
 
 import com.loopers.domain.order.Order;
 import com.loopers.domain.order.OrderCreateService;
+import com.loopers.domain.order.OrderListDto;
 import com.loopers.domain.order.OrderPreparation;
 import com.loopers.domain.order.OrderService;
 import com.loopers.domain.order.orderitem.OrderItemCommand;
 import com.loopers.domain.point.PointService;
 import com.loopers.domain.product.Product;
 import com.loopers.domain.product.ProductService;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.error.ErrorType;
+import java.time.Clock;
 import java.util.Objects;
 import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -27,14 +33,14 @@ public class OrderFacade {
   private final OrderCreateService orderCreateService;
   private final ProductService productService;
   private final PointService pointService;
+  private final Clock clock;
+
 
   @Transactional
-  public Order createOrder(Long userId, List<OrderItemCommand> commands, LocalDateTime orderedAt) {
-    Objects.requireNonNull(userId, "유저아이디는 null 일수 없습니다.");
-    Objects.requireNonNull(commands, "상품 주문 정보는 null일 수 없습니다.");
-    Objects.requireNonNull(orderedAt, "주문 요청 시간은 null일 수 없습니다");
+  public Order createOrder(Long userId, List<OrderItemCommand> commands) {
+    LocalDateTime orderedAt = LocalDateTime.now(clock);
 
-    Map<Long, Product> productById = getProductMapWithLock(commands);
+    Map<Long, Product> productById = getProductByIdWithLocks(commands);
 
     OrderPreparation result = orderCreateService.prepareOrder(commands, productById);
 
@@ -45,7 +51,24 @@ public Order createOrder(Long userId, List<OrderItemCommand> commands, LocalDate
     return orderService.create(userId, result.orderItems().getItems(), orderedAt);
   }
 
-  private Map<Long, Product> getProductMapWithLock(List<OrderItemCommand> commands) {
+  @Transactional(readOnly = true)
+  public Page<OrderListDto> retrieveOrders(Long userId, Pageable pageable) {
+    return orderService.findOrders(userId, pageable);
+  }
+
+  @Transactional(readOnly = true)
+  public Order retrieveOrderDetail(Long userId, Long orderId) {
+    Order order = orderService.getWithItemsById(orderId)
+        .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "주문을 찾을 수 없습니다."));
+
+    if (!Objects.equals(order.getUserId(), userId)) {
+      throw new CoreException(ErrorType.FORBIDDEN, "본인의 주문만 조회할 수 있습니다.");
+    }
+
+    return order;
+  }
+
+  private Map<Long, Product> getProductByIdWithLocks(List<OrderItemCommand> commands) {
     List<Long> productIds = commands.stream()
         .map(OrderItemCommand::productId)
         .distinct()
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
index 3f6bd6940..3e755bf32 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/point/PointFacade.java
@@ -12,6 +12,7 @@ public class PointFacade {
 
     private final PointService pointService;
 
+    @Transactional(readOnly = true)
     public Optional<PointResult> getPoint(Long userId) {
       return pointService.findByUserId(userId)
           .map(PointResult::from);
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
index eaf4c9846..1548efc5f 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/product/ProductFacade.java
@@ -15,6 +15,7 @@
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 @Component
 @RequiredArgsConstructor
@@ -24,6 +25,7 @@ public class ProductFacade {
   private final BrandService brandService;
   private final ProductLikeService productLikeService;
 
+  @Transactional(readOnly = true)
   public Page<ProductDetail> searchProductDetails(Long brandId, Long userId, Pageable pageable) {
     Page<Product> productPage = productService.findProducts(brandId, pageable);
     List<Product> products = productPage.getContent();
@@ -62,7 +64,8 @@ private Page<ProductDetail> getProductDetails(Page<Product> productPage, Map<Lon
     ));
   }
 
-  public ProductDetail viewProductDetail(Long productId, Long userId) {
+  @Transactional(readOnly = true)
+  public ProductDetail retrieveProductDetail(Long productId, Long userId) {
     Product product = productService.getById(productId)
         .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "상품을 찾을 수 없습니다."));
     Brand brand = brandService.getById(product.getBrandId())
diff --git a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
index 49eb178f7..07bb5be71 100644
--- a/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
+++ b/apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
@@ -7,6 +7,7 @@
 import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDate;
 
@@ -15,12 +16,14 @@
 public class UserFacade {
     private final UserService userService;
 
+    @Transactional
     public UserResult register(String loginId, String email, LocalDate birth, Gender gender) {
         User user = userService.registerUser(loginId, email, birth, gender);
         return UserResult.from(user);
     }
 
-    public UserResult lookupUserProfile(String loginId) {
+    @Transactional(readOnly = true)
+    public UserResult retrieveUserProfile(String loginId) {
         User user = userService.findById(loginId);
         if (user == null) {
             throw new CoreException(ErrorType.NOT_FOUND, "사용자를 찾을 수 없습니다.");
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/brand/Brands.java b/apps/commerce-api/src/main/java/com/loopers/domain/brand/Brands.java
deleted file mode 100644
index 747b85a19..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/domain/brand/Brands.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.loopers.domain.brand;
-
-import com.loopers.support.error.CoreException;
-import com.loopers.support.error.ErrorType;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-public class Brands {
-
-  private final List<Brand> values;
-
-  private Brands(List<Brand> values) {
-    this.values = values;
-  }
-
-  public static Brands from(List<Brand> brands) {
-    Objects.requireNonNull(brands, "brands는 null일 수 없습니다.");
-    return new Brands(List.copyOf(brands));
-  }
-
-  public Brand getBrandById(Long brandId) {
-    return values.stream()
-        .filter(brand -> brand.isSameId(brandId))
-        .findFirst()
-        .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "브랜드를 찾을 수 없습니다."));
-  }
-
-  public List<Brand> toList() {
-    return new ArrayList<>(values);
-  }
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderCreateService.java b/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderCreateService.java
index 3d4b1dff9..23560ee16 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderCreateService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderCreateService.java
@@ -7,6 +7,7 @@
 import com.loopers.domain.product.Product;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
+import java.util.Objects;
 import org.springframework.stereotype.Component;
 
 import java.util.List;
@@ -18,6 +19,8 @@ public class OrderCreateService {
 
   public OrderPreparation prepareOrder(List<OrderItemCommand> commands,
                                           Map<Long, Product> productById) {
+
+    Objects.requireNonNull(commands, "상품 주문 정보는 null일 수 없습니다.");
     OrderItems orderItems = createOrderItems(commands, productById);
 
     orderItems.validateStock(productById);
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java b/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java
index 089566399..d6b162161 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/order/OrderService.java
@@ -4,11 +4,14 @@
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 
 @Component
@@ -22,9 +25,17 @@ public Optional<Order> getById(Long orderId) {
   }
 
   public Optional<Order> getWithItemsById(Long orderId) {
+    Objects.requireNonNull(orderId, "주문 ID는 null일 수 없습니다.");
+
     return orderRepository.findWithItemsById(orderId);
   }
 
+  public Page<OrderListDto> findOrders(Long userId, Pageable pageable) {
+    Objects.requireNonNull(userId, "사용자 ID는 null일 수 없습니다.");
+    Objects.requireNonNull(pageable, "Pageable은 null일 수 없습니다.");
+    return orderRepository.findOrderList(userId, pageable);
+  }
+
   public Order create(Long userId, List<OrderItem> orderItems, LocalDateTime orderedAt) {
     Long totalAmount = calculateTotalAmount(orderItems);
 
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/Point.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/Point.java
index d387d096d..9dccb5e53 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/point/Point.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/Point.java
@@ -59,17 +59,6 @@ public void deduct(Long deductAmount) {
     this.amount = this.amount.subtract(deductAmount);
   }
 
-  public boolean hasEnoughBalance(Long requiredAmount) {
-    if (requiredAmount == null || requiredAmount < 0) {
-      return false;
-    }
-    return this.amount.getValue() >= requiredAmount;
-  }
-
-  public boolean isNotEnough(Long requiredAmount) {
-    return !hasEnoughBalance(requiredAmount);
-  }
-
   public Long getAmountValue() {
     return amount.getValue();
   }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
index 84629c6a7..31bb73f4e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/point/PointService.java
@@ -2,6 +2,7 @@
 
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
+import java.util.Objects;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Component;
@@ -28,6 +29,7 @@ public Point charge(Long userId, Long chargeAmount) {
 
     @Transactional
     public Point deduct(Long userId, Long deductAmount) {
+      Objects.requireNonNull(userId, "유저아이디는 null 일수 없습니다.");
         Point point = pointRepository.findByUserIdWithLock(userId)
             .orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND,
                 "포인트 정보를 찾을 수 없습니다."));
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java
index e9469c32d..6d6ee3422 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/product/ProductRepository.java
@@ -16,6 +16,8 @@ public interface ProductRepository {
 
   List<Product> findAllByIdWithLock(List<Long> ids);
 
+  List<Product> findByIdIn(List<Long> ids);
+
   Product save(Product product);
 
   Product saveAndFlush(Product product);
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/product/Products.java b/apps/commerce-api/src/main/java/com/loopers/domain/product/Products.java
deleted file mode 100644
index 9cda7addc..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/domain/product/Products.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.loopers.domain.product;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-public class Products {
-
-  private final List<Product> values;
-
-  private Products(List<Product> values) {
-    this.values = values;
-  }
-
-  public static Products from(List<Product> products) {
-    return new Products(new ArrayList<>(products));
-  }
-
-  public List<Long> getBrandIds() {
-    return values.stream()
-        .map(Product::getBrandId)
-        .distinct()
-        .toList();
-  }
-
-  public List<Long> getProductIds() {
-    return values.stream()
-        .map(Product::getId)
-        .toList();
-  }
-
-  public Optional<Product> findById(Long productId) {
-    return values.stream()
-        .filter(p -> p.isSameId(productId))
-        .findFirst();
-  }
-
-  public List<Product> toList() {
-    return List.copyOf(values);
-  }
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikeQueryRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikeQueryRepository.java
new file mode 100644
index 000000000..72af878ae
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikeQueryRepository.java
@@ -0,0 +1,15 @@
+package com.loopers.domain.productlike;
+
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+
+public interface LikeQueryRepository {
+
+  Page<LikedProduct> findByUserIdOrderByLatest(Long userId, Pageable pageable);
+
+  Page<LikedProduct> findByUserIdOrderByProductName(Long userId, Pageable pageable);
+
+  Page<LikedProduct> findByUserIdOrderByPriceAsc(Long userId, Pageable pageable);
+
+  Page<LikedProduct> findByUserIdOrderByPriceDesc(Long userId, Pageable pageable);
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikeSortType.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikeSortType.java
new file mode 100644
index 000000000..7758af0fd
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikeSortType.java
@@ -0,0 +1,37 @@
+package com.loopers.domain.productlike;
+
+import java.util.Arrays;
+
+public enum LikeSortType {
+  LATEST("latest", "likedAt: DESC"),
+  PRODUCT_NAME("product_name", "productName: ASC"),
+  PRICE_ASC("price_asc", "price: ASC"),
+  PRICE_DESC("price_desc", "price: DESC");
+
+  private final String parameterValue;
+  private final String sortDescription;
+
+  LikeSortType(String parameterValue, String sortDescription) {
+    this.parameterValue = parameterValue;
+    this.sortDescription = sortDescription;
+  }
+
+  public String parameterValue() {
+    return parameterValue;
+  }
+
+  public String sortDescription() {
+    return sortDescription;
+  }
+
+  public static LikeSortType from(String parameterValue) {
+    if (parameterValue == null) {
+      return LATEST;
+    }
+
+    return Arrays.stream(values())
+        .filter(type -> type.parameterValue.equalsIgnoreCase(parameterValue))
+        .findFirst()
+        .orElse(LATEST);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikedProduct.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikedProduct.java
new file mode 100644
index 000000000..47df89303
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/LikedProduct.java
@@ -0,0 +1,33 @@
+package com.loopers.domain.productlike;
+
+import com.loopers.domain.brand.Brand;
+import com.loopers.domain.product.Product;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+public record LikedProduct(
+    Long productId,
+    String productName,
+    Long price,
+    Long likeCount,
+    Long brandId,
+    String brandName,
+    LocalDateTime likedAt
+) {
+
+  public static LikedProduct of(ProductLike productLike, Product product, Brand brand) {
+    Objects.requireNonNull(productLike, "ProductLike는 null일 수 없습니다.");
+    Objects.requireNonNull(product, "Product는 null일 수 없습니다.");
+    Objects.requireNonNull(brand, "Brand는 null일 수 없습니다.");
+
+    return new LikedProduct(
+        product.getId(),
+        product.getName(),
+        product.getPriceValue(),
+        product.getLikeCount(),
+        brand.getId(),
+        brand.getName(),
+        productLike.getLikedAt()
+    );
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLike.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLike.java
index b872127c3..d49ffd566 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLike.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLike.java
@@ -4,7 +4,6 @@
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
 import jakarta.persistence.*;
-import java.util.Objects;
 import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
@@ -53,10 +52,6 @@ public static ProductLike of(Long userId, Long productId, LocalDateTime likedAt)
     return new ProductLike(userId, productId, likedAt);
   }
 
-  public boolean isLikedBy(Long userId) {
-    return Objects.equals(this.userId, userId);
-  }
-
   private void validateUserId(Long userId) {
     if (userId == null) {
       throw new CoreException(ErrorType.INVALID_PRODUCT_LIKE_USER_EMPTY);
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeRepository.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeRepository.java
index 93751bae3..d366afdaa 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeRepository.java
@@ -10,7 +10,7 @@ public interface ProductLikeRepository {
 
   ProductLike save(ProductLike productLike);
 
-  ProductLike saveAndFlush(ProductLike productLike);
+  boolean saveAndFlush(ProductLike productLike);
 
   int deleteByUserIdAndProductId(Long userId, Long productId);
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeService.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeService.java
index cfbc244d0..d151be3c2 100644
--- a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeService.java
+++ b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikeService.java
@@ -4,9 +4,12 @@
 import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
 import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 
 @Component
@@ -14,6 +17,7 @@
 public class ProductLikeService {
 
   private final ProductLikeRepository productLikeRepository;
+  private final LikeQueryRepository likeQueryRepository;
   private final Clock clock;
 
   public boolean isLiked(Long userId, Long productId) {
@@ -23,27 +27,46 @@ public boolean isLiked(Long userId, Long productId) {
     return productLikeRepository.existsByUserIdAndProductId(userId, productId);
   }
 
-
+  //userId는 null일수 있음 (비회원)
   public Map<Long, Boolean> findLikeStatusByProductId(Long userId, List<Long> productIds) {
-    if (userId == null || productIds == null || productIds.isEmpty()) {
-      return Map.of();
-    }
+    Objects.requireNonNull(productIds, "productIds는 null일 수 없습니다.");
 
-    List<ProductLike> likes = productLikeRepository.findByUserIdAndProductIdIn(userId, productIds);
-    ProductLikes productLikes = ProductLikes.from(likes);
+    List<Long> distinctProductIds = productIds.stream()
+        .distinct()
+        .toList();
 
-    return productLikes.toLikeStatusByProductId(productIds);
-  }
+    List<ProductLike> likes = productLikeRepository.findByUserIdAndProductIdIn(userId, distinctProductIds);
 
+    Map<Long, Boolean> likedByProductId = likes.stream()
+        .collect(Collectors.toMap(ProductLike::getProductId, like -> true));
 
-  @Transactional(propagation = Propagation.REQUIRES_NEW)
-  public void createLike(Long userId, Long productId) {
-      ProductLike like = ProductLike.of(userId, productId, LocalDateTime.now(clock));
-      productLikeRepository.saveAndFlush(like);
+    return distinctProductIds.stream()
+        .collect(Collectors.toMap(
+            productId -> productId,
+            productId -> likedByProductId.getOrDefault(productId, false)
+        ));
+  }
+
+  @Transactional
+  public boolean createLike(Long userId, Long productId) {
+    ProductLike like = ProductLike.of(userId, productId, LocalDateTime.now(clock));
+    return productLikeRepository.saveAndFlush(like);
   }
 
   @Transactional
   public int deleteLike(Long userId, Long productId) {
     return productLikeRepository.deleteByUserIdAndProductId(userId, productId);
   }
+
+  public Page<LikedProduct> findLikedProducts(Long userId, LikeSortType sortType,
+      Pageable pageable) {
+    Objects.requireNonNull(sortType, "sortType은 null일 수 없습니다.");
+
+    return switch (sortType) {
+      case LATEST -> likeQueryRepository.findByUserIdOrderByLatest(userId, pageable);
+      case PRODUCT_NAME -> likeQueryRepository.findByUserIdOrderByProductName(userId, pageable);
+      case PRICE_ASC -> likeQueryRepository.findByUserIdOrderByPriceAsc(userId, pageable);
+      case PRICE_DESC -> likeQueryRepository.findByUserIdOrderByPriceDesc(userId, pageable);
+    };
+  }
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikes.java b/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikes.java
deleted file mode 100644
index a30a48e83..000000000
--- a/apps/commerce-api/src/main/java/com/loopers/domain/productlike/ProductLikes.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.loopers.domain.productlike;
-
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-public class ProductLikes {
-
-  private final List<ProductLike> values;
-
-  private ProductLikes(List<ProductLike> productLikes) {
-    this.values = List.copyOf(productLikes);
-  }
-
-  public static ProductLikes from(List<ProductLike> productLikes) {
-    if (productLikes == null) {
-      throw new IllegalArgumentException("productLikes cannot be null");
-    }
-    return new ProductLikes(productLikes);
-  }
-
-  public static ProductLikes empty() {
-    return new ProductLikes(List.of());
-  }
-
-  // 중복 productId가 있으면 IllegalStateException 발생 (데이터 무결성 오류)
-  public Map<Long, Boolean> toLikedByProductId() {
-    return values.stream()
-        .collect(Collectors.toMap(ProductLike::getProductId, like -> true));
-  }
-
-  public Map<Long, Boolean> toLikeStatusByProductId(List<Long> allProductIds) {
-    if (allProductIds == null || allProductIds.isEmpty()) {
-      return Map.of();
-    }
-
-    Map<Long, Boolean> likedByProductId = toLikedByProductId();
-
-    // allProductIds에 중복이 있으면 IllegalStateException 발생
-    return allProductIds.stream()
-        .collect(Collectors.toMap(
-            productId -> productId,
-            productId -> likedByProductId.getOrDefault(productId, false)
-        ));
-  }
-}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java
index 8a9d1fb6b..2c8cc4093 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderJpaRepository.java
@@ -22,6 +22,7 @@ public interface OrderJpaRepository extends JpaRepository<Order, Long> {
 
   /**
    * 사용자별 주문 목록 조회 (DTO 프로젝션, SIZE()로 개수만 조회)
+   * 정렬은 Pageable로 제어
    */
   @Query("SELECT new com.loopers.domain.order.OrderListDto(" +
       "o.id, o.userId, o.status, o.totalAmount.value, o.orderedAt, SIZE(o.items)) " +
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java
index 0984ab6a1..88f6f9865 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/order/OrderRepositoryImpl.java
@@ -5,7 +5,9 @@
 import com.loopers.domain.order.OrderRepository;
 import lombok.RequiredArgsConstructor;
 import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
+import org.springframework.data.domain.Sort;
 import org.springframework.stereotype.Repository;
 
 import java.util.Optional;
@@ -14,6 +16,8 @@
 @RequiredArgsConstructor
 public class OrderRepositoryImpl implements OrderRepository {
 
+  private static final Sort DEFAULT_ORDER_SORT = Sort.by(Sort.Direction.DESC, "id");
+
   private final OrderJpaRepository jpaRepository;
 
   @Override
@@ -28,6 +32,13 @@ public Optional<Order> findWithItemsById(Long id) {
 
   @Override
   public Page<OrderListDto> findOrderList(Long userId, Pageable pageable) {
+    if (pageable.getSort().isUnsorted()) {
+      pageable = PageRequest.of(
+          pageable.getPageNumber(),
+          pageable.getPageSize(),
+          DEFAULT_ORDER_SORT
+      );
+    }
     return jpaRepository.findOrderListByUserId(userId, pageable);
   }
 
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java
index 72321701e..7de0aa1be 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductJpaRepository.java
@@ -20,6 +20,9 @@ public interface ProductJpaRepository extends JpaRepository<Product, Long> {
   @Query("SELECT p FROM Product p WHERE p.id IN :ids ORDER BY p.id ASC")
   List<Product> findAllByIdWithLock(@Param("ids") List<Long> ids);
 
+  @Query("SELECT p FROM Product p WHERE p.id IN :ids ORDER BY p.id ASC")
+  List<Product> findByIdIn(@Param("ids") List<Long> ids);
+
   @Modifying(clearAutomatically = true, flushAutomatically = true)
   @Query("UPDATE Product p SET p.likeCount = p.likeCount + 1 WHERE p.id = :productId")
   int incrementLikeCount(@Param("productId") Long productId);
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java
index c188f163e..3c12245ee 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/product/ProductRepositoryImpl.java
@@ -36,6 +36,11 @@ public List<Product> findAllByIdWithLock(List<Long> ids) {
     return jpaRepository.findAllByIdWithLock(ids);
   }
 
+  @Override
+  public List<Product> findByIdIn(List<Long> ids) {
+    return jpaRepository.findByIdIn(ids);
+  }
+
   @Override
   public Product save(Product product) {
     return jpaRepository.save(product);
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/LikeQueryJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/LikeQueryJpaRepository.java
new file mode 100644
index 000000000..ccb0449e7
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/LikeQueryJpaRepository.java
@@ -0,0 +1,85 @@
+package com.loopers.infrastructure.productlike;
+
+import com.loopers.domain.productlike.LikedProduct;
+import com.loopers.domain.productlike.ProductLike;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+
+public interface LikeQueryJpaRepository extends JpaRepository<ProductLike, Long> {
+
+  @Query("""
+      SELECT new com.loopers.domain.productlike.LikedProduct(
+        p.id,
+        p.name,
+        p.price.value,
+        p.likeCount,
+        b.id,
+        b.name,
+        pl.likedAt
+      )
+      FROM ProductLike pl
+      LEFT JOIN Product p ON p.id = pl.productId
+      LEFT JOIN Brand b ON b.id = p.brandId
+      WHERE pl.userId = :userId
+      ORDER BY pl.likedAt DESC
+      """)
+  Page<LikedProduct> findByUserIdOrderByLatest(@Param("userId") Long userId, Pageable pageable);
+
+  @Query("""
+      SELECT new com.loopers.domain.productlike.LikedProduct(
+        p.id,
+        p.name,
+        p.price.value,
+        p.likeCount,
+        b.id,
+        b.name,
+        pl.likedAt
+      )
+      FROM ProductLike pl
+      LEFT JOIN Product p ON p.id = pl.productId
+      LEFT JOIN Brand b ON b.id = p.brandId
+      WHERE pl.userId = :userId
+      ORDER BY p.name ASC
+      """)
+  Page<LikedProduct> findByUserIdOrderByProductName(@Param("userId") Long userId,
+      Pageable pageable);
+
+  @Query("""
+      SELECT new com.loopers.domain.productlike.LikedProduct(
+        p.id,
+        p.name,
+        p.price.value,
+        p.likeCount,
+        b.id,
+        b.name,
+        pl.likedAt
+      )
+      FROM ProductLike pl
+      LEFT JOIN Product p ON p.id = pl.productId
+      LEFT JOIN Brand b ON b.id = p.brandId
+      WHERE pl.userId = :userId
+      ORDER BY p.price.value ASC
+      """)
+  Page<LikedProduct> findByUserIdOrderByPriceAsc(@Param("userId") Long userId, Pageable pageable);
+
+  @Query("""
+      SELECT new com.loopers.domain.productlike.LikedProduct(
+        p.id,
+        p.name,
+        p.price.value,
+        p.likeCount,
+        b.id,
+        b.name,
+        pl.likedAt
+      )
+      FROM ProductLike pl
+      LEFT JOIN Product p ON p.id = pl.productId
+      LEFT JOIN Brand b ON b.id = p.brandId
+      WHERE pl.userId = :userId
+      ORDER BY p.price.value DESC
+      """)
+  Page<LikedProduct> findByUserIdOrderByPriceDesc(@Param("userId") Long userId, Pageable pageable);
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/LikeQueryRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/LikeQueryRepositoryImpl.java
new file mode 100644
index 000000000..de2178a90
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/LikeQueryRepositoryImpl.java
@@ -0,0 +1,35 @@
+package com.loopers.infrastructure.productlike;
+
+import com.loopers.domain.productlike.LikeQueryRepository;
+import com.loopers.domain.productlike.LikedProduct;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.Pageable;
+import org.springframework.stereotype.Repository;
+
+@Repository
+@RequiredArgsConstructor
+public class LikeQueryRepositoryImpl implements LikeQueryRepository {
+
+  private final LikeQueryJpaRepository jpaRepository;
+
+  @Override
+  public Page<LikedProduct> findByUserIdOrderByLatest(Long userId, Pageable pageable) {
+    return jpaRepository.findByUserIdOrderByLatest(userId, pageable);
+  }
+
+  @Override
+  public Page<LikedProduct> findByUserIdOrderByProductName(Long userId, Pageable pageable) {
+    return jpaRepository.findByUserIdOrderByProductName(userId, pageable);
+  }
+
+  @Override
+  public Page<LikedProduct> findByUserIdOrderByPriceAsc(Long userId, Pageable pageable) {
+    return jpaRepository.findByUserIdOrderByPriceAsc(userId, pageable);
+  }
+
+  @Override
+  public Page<LikedProduct> findByUserIdOrderByPriceDesc(Long userId, Pageable pageable) {
+    return jpaRepository.findByUserIdOrderByPriceDesc(userId, pageable);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeJpaRepository.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeJpaRepository.java
index 3fa89d307..7cbf5dfe0 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeJpaRepository.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeJpaRepository.java
@@ -6,15 +6,25 @@
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 public interface ProductLikeJpaRepository extends JpaRepository<ProductLike, Long> {
 
   boolean existsByUserIdAndProductId(Long userId, Long productId);
 
+  @Modifying
+  @Query(nativeQuery = true,
+      value = "INSERT IGNORE INTO product_like (ref_user_id, ref_product_id, liked_at, created_at, updated_at) " +
+              "VALUES (:userId, :productId, :likedAt, NOW(), NOW())")
+  int insertIgnore(@Param("userId") Long userId,
+                   @Param("productId") Long productId,
+                   @Param("likedAt") LocalDateTime likedAt);
+
   List<ProductLike> findByUserIdAndProductIdIn(Long userId, List<Long> productIds);
 
   @Modifying
   @Query("DELETE FROM ProductLike pl WHERE pl.userId = :userId AND pl.productId = :productId")
   int deleteByUserIdAndProductId(@Param("userId") Long userId, @Param("productId") Long productId);
+
 }
diff --git a/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeRepositoryImpl.java b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeRepositoryImpl.java
index d68fe84b8..4dc69ea7e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeRepositoryImpl.java
+++ b/apps/commerce-api/src/main/java/com/loopers/infrastructure/productlike/ProductLikeRepositoryImpl.java
@@ -2,11 +2,10 @@
 
 import com.loopers.domain.productlike.ProductLike;
 import com.loopers.domain.productlike.ProductLikeRepository;
+import java.util.List;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Repository;
 
-import java.util.List;
-
 @Repository
 @RequiredArgsConstructor
 public class ProductLikeRepositoryImpl implements ProductLikeRepository {
@@ -31,12 +30,17 @@ public List<ProductLike> findByUserIdAndProductIdIn(Long userId, List<Long> prod
 
   @Override
   public ProductLike save(ProductLike productLike) {
-    return jpaRepository.save(productLike);
+    return jpaRepository.saveAndFlush(productLike);
   }
 
   @Override
-  public ProductLike saveAndFlush(ProductLike productLike) {
-    return jpaRepository.saveAndFlush(productLike);
+  public boolean saveAndFlush(ProductLike productLike) {
+    int result = jpaRepository.insertIgnore(
+        productLike.getUserId(),
+        productLike.getProductId(),
+        productLike.getLikedAt()
+    );
+    return result > 0;
   }
 
   @Override
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandApiSpec.java
new file mode 100644
index 000000000..cd8e51699
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandApiSpec.java
@@ -0,0 +1,21 @@
+package com.loopers.interfaces.api.brand;
+
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.brand.BrandDto.BrandViewResponse;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PathVariable;
+
+@Tag(name = "Brand API", description = "브랜드 API 입니다.")
+public interface BrandApiSpec {
+
+  @Operation(
+      summary = "브랜드 조회",
+      description = "브랜드 ID로 브랜드 정보를 조회합니다."
+  )
+  ApiResponse<BrandViewResponse> retrieveBrand(
+      @Parameter(description = "브랜드 ID", required = true)
+      @PathVariable("brandId") Long brandId
+  );
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandController.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandController.java
new file mode 100644
index 000000000..c4e429d45
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandController.java
@@ -0,0 +1,26 @@
+package com.loopers.interfaces.api.brand;
+
+import com.loopers.application.brand.BrandFacade;
+import com.loopers.application.brand.BrandResult;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.brand.BrandDto.BrandViewResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/v1/brands")
+public class BrandController implements BrandApiSpec {
+
+  private final BrandFacade brandFacade;
+
+  @Override
+  @GetMapping("/{brandId}")
+  public ApiResponse<BrandViewResponse> retrieveBrand(@PathVariable("brandId") Long brandId) {
+    BrandResult result = brandFacade.retrieveBrand(brandId);
+    return ApiResponse.success(BrandViewResponse.from(result));
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandDto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandDto.java
new file mode 100644
index 000000000..6dd86effe
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/brand/BrandDto.java
@@ -0,0 +1,27 @@
+package com.loopers.interfaces.api.brand;
+
+import com.loopers.application.brand.BrandResult;
+
+import java.util.Objects;
+
+public class BrandDto {
+
+  private BrandDto() {
+  }
+
+  public record BrandViewResponse(
+      Long brandId,
+      String name,
+      String description
+  ) {
+
+    public static BrandViewResponse from(BrandResult result) {
+      Objects.requireNonNull(result, "result는 null일 수 없습니다.");
+      return new BrandViewResponse(
+          result.brandId(),
+          result.name(),
+          result.description()
+      );
+    }
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeApiSpec.java
new file mode 100644
index 000000000..4ed1e07f6
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeApiSpec.java
@@ -0,0 +1,44 @@
+package com.loopers.interfaces.api.like;
+
+import com.loopers.domain.productlike.LikeSortType;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.like.LikeDto.LikedProductListResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Tag(name = "Like API", description = "상품 좋아요 API 입니다.")
+public interface LikeApiSpec {
+
+  @Operation(summary = "상품 좋아요 등록", description = "상품에 좋아요를 등록합니다.")
+  ApiResponse<Object> registerProductLike(
+      @Parameter(description = "사용자 ID", required = true)
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @Parameter(description = "상품 ID", required = true)
+      @PathVariable("productId") Long productId
+  );
+
+  @Operation(summary = "상품 좋아요 취소", description = "상품 좋아요를 취소합니다.")
+  ApiResponse<Object> cancelProductLike(
+      @Parameter(description = "사용자 ID", required = true)
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @Parameter(description = "상품 ID", required = true)
+      @PathVariable("productId") Long productId
+  );
+
+  @Operation(summary = "좋아요 상품 목록 조회", description = "사용자가 좋아요한 상품 목록을 페이지 단위로 조회합니다.")
+  ApiResponse<LikedProductListResponse> retrieveLikedProducts(
+      @Parameter(description = "사용자 ID", required = true)
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @Parameter(description = "정렬 (latest, product_name, price_asc, price_desc)")
+      @RequestParam(defaultValue = "latest") LikeSortType sort,
+      @Parameter(description = "페이지 번호 (0부터 시작)")
+      @RequestParam(defaultValue = "0") int page,
+      @Parameter(description = "페이지 크기")
+      @RequestParam(defaultValue = "20") int size
+  );
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeController.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeController.java
new file mode 100644
index 000000000..dc2de0347
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeController.java
@@ -0,0 +1,62 @@
+package com.loopers.interfaces.api.like;
+
+import com.loopers.application.like.LikeFacade;
+import com.loopers.domain.productlike.LikeSortType;
+import com.loopers.domain.productlike.LikedProduct;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.like.LikeDto.LikedProductListResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/api/v1/like/products")
+@RequiredArgsConstructor
+public class LikeController implements LikeApiSpec {
+
+  private final LikeFacade likeFacade;
+
+  @Override
+  @PostMapping("/{productId}")
+  public ApiResponse<Object> registerProductLike(
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @PathVariable("productId") Long productId
+  ) {
+    likeFacade.registerProductLike(userId, productId);
+    return ApiResponse.success();
+  }
+
+  @Override
+  @DeleteMapping("/{productId}")
+  public ApiResponse<Object> cancelProductLike(
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @PathVariable("productId") Long productId
+  ) {
+    likeFacade.cancelProductLike(userId, productId);
+    return ApiResponse.success();
+  }
+
+  @Override
+  @GetMapping
+  public ApiResponse<LikedProductListResponse> retrieveLikedProducts(
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @RequestParam(defaultValue = "latest") LikeSortType sort,
+      @RequestParam(defaultValue = "0") int page,
+      @RequestParam(defaultValue = "20") int size
+  ) {
+    Pageable pageable = PageRequest.of(page, size);
+    Page<LikedProduct> likedProducts = likeFacade.retrieveLikedProducts(userId, sort, pageable);
+    LikedProductListResponse response = LikedProductListResponse.from(likedProducts, sort);
+    return ApiResponse.success(response);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeDto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeDto.java
new file mode 100644
index 000000000..e5a3213f9
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeDto.java
@@ -0,0 +1,65 @@
+package com.loopers.interfaces.api.like;
+
+import com.loopers.domain.productlike.LikeSortType;
+import com.loopers.domain.productlike.LikedProduct;
+import com.loopers.interfaces.api.support.PageInfo;
+import java.util.List;
+import java.util.Objects;
+import org.springframework.data.domain.Page;
+
+public class LikeDto {
+
+  private LikeDto() {
+  }
+
+  public record LikedProductListResponse(
+      List<LikedProductResponse> products,
+      PageInfo pageInfo
+  ) {
+
+    public static LikedProductListResponse from(Page<LikedProduct> page) {
+      Objects.requireNonNull(page, "page는 null일 수 없습니다.");
+      List<LikedProductResponse> products = page.getContent().stream()
+          .map(LikedProductResponse::from)
+          .toList();
+
+      return new LikedProductListResponse(products, PageInfo.from(page));
+    }
+
+    public static LikedProductListResponse from(Page<LikedProduct> page, LikeSortType sortType) {
+      Objects.requireNonNull(page, "page는 null일 수 없습니다.");
+      Objects.requireNonNull(sortType, "sortType은 null일 수 없습니다.");
+      List<LikedProductResponse> products = page.getContent().stream()
+          .map(LikedProductResponse::from)
+          .toList();
+
+      return new LikedProductListResponse(products, PageInfo.from(page, sortType.sortDescription()));
+    }
+  }
+
+  public record LikedProductResponse(
+      Long productId,
+      String productName,
+      Long price,
+      Long likeCount,
+      BrandInfo brand
+  ) {
+
+    public static LikedProductResponse from(LikedProduct likedProduct) {
+      Objects.requireNonNull(likedProduct, "likedProduct는 null일 수 없습니다.");
+      return new LikedProductResponse(
+          likedProduct.productId(),
+          likedProduct.productName(),
+          likedProduct.price(),
+          likedProduct.likeCount(),
+          new BrandInfo(likedProduct.brandId(), likedProduct.brandName())
+      );
+    }
+  }
+
+  public record BrandInfo(
+      Long brandId,
+      String name
+  ) {
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeSortTypeConverter.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeSortTypeConverter.java
new file mode 100644
index 000000000..611bd8844
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/like/LikeSortTypeConverter.java
@@ -0,0 +1,14 @@
+package com.loopers.interfaces.api.like;
+
+import com.loopers.domain.productlike.LikeSortType;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class LikeSortTypeConverter implements Converter<String, LikeSortType> {
+
+  @Override
+  public LikeSortType convert(String source) {
+    return LikeSortType.from(source);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderApiSpec.java
new file mode 100644
index 000000000..317b89cf6
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderApiSpec.java
@@ -0,0 +1,59 @@
+package com.loopers.interfaces.api.order;
+
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.order.OrderDto.OrderCreateRequest;
+import com.loopers.interfaces.api.order.OrderDto.OrderDetailResponse;
+import com.loopers.interfaces.api.order.OrderDto.OrderListResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.parameters.RequestBody;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Tag(name = "Order API", description = "주문 API입니다.")
+public interface OrderApiSpec {
+
+  @Operation(
+      summary = "주문 생성",
+      description = "여러 상품을 한 번에 주문하고 포인트로 결제합니다."
+  )
+  ResponseEntity<ApiResponse<OrderDetailResponse>> createOrder(
+      @Parameter(description = "사용자 ID", required = true)
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+
+      @RequestBody(description = "주문 생성 요청", required = true)
+      OrderCreateRequest request
+  );
+
+  @Operation(
+      summary = "주문 목록 조회",
+      description = "사용자의 주문 목록을 조회합니다. 페이지네이션을 지원합니다."
+  )
+  ApiResponse<OrderListResponse> retrieveOrders(
+      @Parameter(description = "사용자 ID", required = true)
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+
+      @Parameter(description = "페이지 번호 (0부터 시작)", required = false)
+      @RequestParam(defaultValue = "0") int page,
+
+      @Parameter(description = "페이지당 주문 수", required = false)
+      @RequestParam(defaultValue = "20") int size
+  );
+
+  @Operation(
+      summary = "주문 상세 조회",
+      description = "주문 ID로 주문 상세 정보를 조회합니다. 본인의 주문만 조회할 수 있습니다."
+  )
+  ApiResponse<OrderDetailResponse> retrieveOrderDetail(
+      @Parameter(description = "사용자 ID", required = true)
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+
+      @Parameter(description = "주문 ID", required = true)
+      @PathVariable("orderId") Long orderId
+  );
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderController.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderController.java
new file mode 100644
index 000000000..3f0faff45
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderController.java
@@ -0,0 +1,80 @@
+package com.loopers.interfaces.api.order;
+
+import com.loopers.application.order.OrderFacade;
+import com.loopers.domain.order.Order;
+import com.loopers.domain.order.OrderListDto;
+import com.loopers.domain.order.orderitem.OrderItemCommand;
+import com.loopers.domain.quantity.Quantity;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.order.OrderDto.OrderCreateRequest;
+import com.loopers.interfaces.api.order.OrderDto.OrderDetailResponse;
+import com.loopers.interfaces.api.order.OrderDto.OrderListResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import jakarta.validation.Valid;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/v1/orders")
+public class OrderController implements OrderApiSpec {
+
+  private final OrderFacade orderFacade;
+
+  @Override
+  @PostMapping
+  public ResponseEntity<ApiResponse<OrderDetailResponse>> createOrder(
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @Valid @RequestBody OrderCreateRequest request
+  ) {
+    List<OrderItemCommand> commands = request.items().stream()
+        .map(item -> OrderItemCommand.of(item.productId(), Quantity.of(item.quantity())))
+        .toList();
+
+    Order order = orderFacade.createOrder(userId, commands);
+    OrderDetailResponse response = OrderDetailResponse.from(order);
+
+    return ResponseEntity.status(HttpStatus.CREATED)
+        .body(ApiResponse.success(response));
+  }
+
+  @Override
+  @GetMapping
+  public ApiResponse<OrderListResponse> retrieveOrders(
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @RequestParam(defaultValue = "0") int page,
+      @RequestParam(defaultValue = "20") int size
+  ) {
+    Pageable pageable = PageRequest.of(page, size);
+
+    Page<OrderListDto> orderPage = orderFacade.retrieveOrders(userId, pageable);
+    OrderListResponse response = OrderListResponse.from(orderPage);
+
+    return ApiResponse.success(response);
+  }
+
+  @Override
+  @GetMapping("/{orderId}")
+  public ApiResponse<OrderDetailResponse> retrieveOrderDetail(
+      @RequestHeader(ApiHeaders.USER_ID) Long userId,
+      @PathVariable("orderId") Long orderId
+  ) {
+    Order order = orderFacade.retrieveOrderDetail(userId, orderId);
+    OrderDetailResponse response = OrderDetailResponse.from(order);
+
+    return ApiResponse.success(response);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderDto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderDto.java
new file mode 100644
index 000000000..70e4bf5df
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/order/OrderDto.java
@@ -0,0 +1,115 @@
+package com.loopers.interfaces.api.order;
+
+import com.loopers.domain.order.Order;
+import com.loopers.domain.order.OrderListDto;
+import com.loopers.domain.order.OrderStatus;
+import com.loopers.domain.order.orderitem.OrderItem;
+import com.loopers.interfaces.api.support.PageInfo;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Objects;
+import org.springframework.data.domain.Page;
+
+public class OrderDto {
+
+  private OrderDto() {
+  }
+
+  public record OrderCreateRequest(
+      @NotNull(message = "주문 항목은 null일 수 없습니다.")
+      @NotEmpty(message = "주문 항목은 비어있을 수 없습니다.")
+      List<OrderItemRequest> items
+  ) {
+  }
+
+  public record OrderItemRequest(
+      @Positive(message = "상품 ID는 양수여야 합니다.")
+      Long productId,
+
+      @Positive(message = "수량은 양수여야 합니다.")
+      Long quantity
+  ) {
+  }
+
+  public record OrderListResponse(
+      List<OrderListItemResponse> orders,
+      PageInfo pageInfo
+  ) {
+
+    public static OrderListResponse from(Page<OrderListDto> page) {
+      Objects.requireNonNull(page, "page는 null일 수 없습니다.");
+      List<OrderListItemResponse> orders = page.getContent().stream()
+          .map(OrderListItemResponse::from)
+          .toList();
+
+      PageInfo pageInfo = PageInfo.from(page);
+
+      return new OrderListResponse(orders, pageInfo);
+    }
+  }
+
+  public record OrderListItemResponse(
+      Long orderId,
+      LocalDateTime orderedAt,
+      Long totalAmount,
+      OrderStatus status,
+      Integer itemCount
+  ) {
+
+    public static OrderListItemResponse from(OrderListDto dto) {
+      Objects.requireNonNull(dto, "dto는 null일 수 없습니다.");
+      return new OrderListItemResponse(
+          dto.id(),
+          dto.orderedAt(),
+          dto.totalAmount(),
+          dto.status(),
+          dto.itemCount()
+      );
+    }
+  }
+
+  public record OrderDetailResponse(
+      Long orderId,
+      LocalDateTime orderedAt,
+      OrderStatus status,
+      List<OrderItemResponse> items,
+      Long totalAmount
+  ) {
+
+    public static OrderDetailResponse from(Order order) {
+      Objects.requireNonNull(order, "order는 null일 수 없습니다.");
+      List<OrderItemResponse> items = order.getItems().stream()
+          .map(OrderItemResponse::from)
+          .toList();
+
+      return new OrderDetailResponse(
+          order.getId(),
+          order.getOrderedAt(),
+          order.getStatus(),
+          items,
+          order.getTotalAmountValue()
+      );
+    }
+  }
+
+  public record OrderItemResponse(
+      Long productId,
+      String productName,
+      Long quantity,
+      Long price
+  ) {
+
+    public static OrderItemResponse from(OrderItem item) {
+      Objects.requireNonNull(item, "item은 null일 수 없습니다.");
+      return new OrderItemResponse(
+          item.getProductId(),
+          item.getProductName(),
+          item.getQuantityValue(),
+          item.getOrderPriceValue()
+      );
+    }
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointDto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointDto.java
index 15bac5a54..aaa12108e 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointDto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/point/PointDto.java
@@ -3,14 +3,20 @@
 import com.loopers.application.point.PointResult;
 import jakarta.validation.constraints.Positive;
 
+import java.util.Objects;
+
 public class PointDto {
 
+  private PointDto() {
+  }
+
   public record PointResponse(
       Long userId,
       Long balance
   ) {
 
     public static PointResponse from(PointResult pointResult) {
+      Objects.requireNonNull(pointResult, "pointResult는 null일 수 없습니다.");
       return new PointResponse(
           pointResult.userId(),
           pointResult.amount()
@@ -29,6 +35,7 @@ public record ChargeResponse(
   ) {
 
     public static ChargeResponse from(PointResult pointResult) {
+      Objects.requireNonNull(pointResult, "pointResult는 null일 수 없습니다.");
       return new ChargeResponse(
           pointResult.userId(),
           pointResult.amount()
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductApiSpec.java
new file mode 100644
index 000000000..38b551495
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductApiSpec.java
@@ -0,0 +1,49 @@
+package com.loopers.interfaces.api.product;
+
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductListResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@Tag(name = "Product API", description = "상품 API 입니다.")
+public interface ProductApiSpec {
+
+  @Operation(
+      summary = "상품 목록 조회",
+      description = "상품 목록을 조회합니다. 브랜드 필터링, 정렬, 페이지네이션을 지원합니다."
+  )
+  ApiResponse<ProductListResponse> searchProductDetails(
+      @Parameter(description = "사용자 ID (선택, 비회원 가능)", required = false)
+      @RequestHeader(value = ApiHeaders.USER_ID, required = false) Long userId,
+
+      @Parameter(description = "브랜드 ID (선택, 특정 브랜드 필터링)", required = false)
+      @RequestParam(required = false) Long brandId,
+
+      @Parameter(description = "정렬 기준 (latest, price_asc, likes_desc)", required = false)
+      @RequestParam(defaultValue = "latest") ProductSortType sort,
+
+      @Parameter(description = "페이지 번호 (0부터 시작)", required = false)
+      @RequestParam(defaultValue = "0") int page,
+
+      @Parameter(description = "페이지당 상품 수", required = false)
+      @RequestParam(defaultValue = "20") int size
+  );
+
+  @Operation(
+      summary = "상품 상세 조회",
+      description = "상품 ID로 상품 상세 정보를 조회합니다."
+  )
+  ApiResponse<ProductResponse> retrieveProductDetail(
+      @Parameter(description = "사용자 ID (선택, 비회원 가능)", required = false)
+      @RequestHeader(value = ApiHeaders.USER_ID, required = false) Long userId,
+
+      @Parameter(description = "상품 ID", required = true)
+      @PathVariable("productId") Long productId
+  );
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductController.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductController.java
new file mode 100644
index 000000000..aaf4963dc
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductController.java
@@ -0,0 +1,56 @@
+package com.loopers.interfaces.api.product;
+
+import com.loopers.application.product.ProductDetail;
+import com.loopers.application.product.ProductFacade;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductListResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/api/v1/products")
+public class ProductController implements ProductApiSpec {
+
+  private final ProductFacade productFacade;
+
+  @Override
+  @GetMapping
+  public ApiResponse<ProductListResponse> searchProductDetails(
+      @RequestHeader(value = ApiHeaders.USER_ID, required = false) Long userId,
+      @RequestParam(required = false) Long brandId,
+      @RequestParam(defaultValue = "latest") ProductSortType sort,
+      @RequestParam(defaultValue = "0") int page,
+      @RequestParam(defaultValue = "20") int size
+  ) {
+    Pageable pageable = PageRequest.of(page, size, sort.toSort());
+
+    Page<ProductDetail> productPage = productFacade.searchProductDetails(brandId, userId,
+        pageable);
+    ProductListResponse response = ProductListResponse.from(productPage);
+
+    return ApiResponse.success(response);
+  }
+
+  @Override
+  @GetMapping("/{productId}")
+  public ApiResponse<ProductResponse> retrieveProductDetail(
+      @RequestHeader(value = ApiHeaders.USER_ID, required = false) Long userId,
+      @PathVariable("productId") Long productId
+  ) {
+    ProductDetail detail = productFacade.retrieveProductDetail(productId, userId);
+    ProductResponse response = ProductResponse.from(detail);
+
+    return ApiResponse.success(response);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductDto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductDto.java
new file mode 100644
index 000000000..c5b953bb5
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductDto.java
@@ -0,0 +1,85 @@
+package com.loopers.interfaces.api.product;
+
+import com.loopers.application.product.ProductDetail;
+import com.loopers.interfaces.api.support.PageInfo;
+import java.util.List;
+import java.util.Objects;
+import org.springframework.data.domain.Page;
+
+public class ProductDto {
+
+  private ProductDto() {
+  }
+
+  public record ProductListResponse(
+      List<ProductListItemResponse> products,
+      PageInfo pageInfo
+  ) {
+
+    public static ProductListResponse from(Page<ProductDetail> page) {
+      Objects.requireNonNull(page, "page는 null일 수 없습니다.");
+      List<ProductListItemResponse> products = page.getContent().stream()
+          .map(ProductListItemResponse::from)
+          .toList();
+
+      PageInfo pageInfo = PageInfo.from(page);
+
+      return new ProductListResponse(products, pageInfo);
+    }
+  }
+
+  public record ProductListItemResponse(
+      Long productId,
+      String name,
+      Long price,
+      BrandInfo brand,
+      Long likeCount,
+      boolean isLiked
+  ) {
+
+    public static ProductListItemResponse from(ProductDetail detail) {
+      Objects.requireNonNull(detail, "detail은 null일 수 없습니다.");
+      return new ProductListItemResponse(
+          detail.productId(),
+          detail.productName(),
+          detail.price(),
+          new BrandInfo(detail.brandId(), detail.brandName()),
+          detail.likeCount(),
+          detail.liked()
+      );
+    }
+  }
+
+  public record ProductResponse(
+      Long productId,
+      String name,
+      Long price,
+      String description,
+      Long stock,
+      BrandInfo brand,
+      Long likeCount,
+      boolean isLiked
+  ) {
+
+    public static ProductResponse from(ProductDetail detail) {
+      Objects.requireNonNull(detail, "detail은 null일 수 없습니다.");
+      return new ProductResponse(
+          detail.productId(),
+          detail.productName(),
+          detail.price(),
+          detail.description(),
+          detail.stock(),
+          new BrandInfo(detail.brandId(), detail.brandName()),
+          detail.likeCount(),
+          detail.liked()
+      );
+    }
+  }
+
+  public record BrandInfo(
+      Long brandId,
+      String name
+  ) {
+
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductSortType.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductSortType.java
new file mode 100644
index 000000000..226ffec0d
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductSortType.java
@@ -0,0 +1,35 @@
+package com.loopers.interfaces.api.product;
+
+import java.util.Arrays;
+import org.springframework.data.domain.Sort;
+
+public enum ProductSortType {
+  LATEST("latest", Sort.Direction.DESC, "id"),
+  PRICE_ASC("price_asc", Sort.Direction.ASC, "price.value"),
+  LIKES_DESC("likes_desc", Sort.Direction.DESC, "likeCount");
+
+  private final String parameterValue;
+  private final Sort.Direction direction;
+  private final String property;
+
+  ProductSortType(String parameterValue, Sort.Direction direction, String property) {
+    this.parameterValue = parameterValue;
+    this.direction = direction;
+    this.property = property;
+  }
+
+  public Sort toSort() {
+    return Sort.by(direction, property);
+  }
+
+  public static ProductSortType from(String parameterValue) {
+    if (parameterValue == null) {
+      return LATEST;
+    }
+
+    return Arrays.stream(values())
+        .filter(type -> type.parameterValue.equalsIgnoreCase(parameterValue))
+        .findFirst()
+        .orElse(LATEST);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductSortTypeConverter.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductSortTypeConverter.java
new file mode 100644
index 000000000..5aa643874
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/product/ProductSortTypeConverter.java
@@ -0,0 +1,13 @@
+package com.loopers.interfaces.api.product;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ProductSortTypeConverter implements Converter<String, ProductSortType> {
+
+  @Override
+  public ProductSortType convert(String source) {
+    return ProductSortType.from(source);
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/support/PageInfo.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/support/PageInfo.java
new file mode 100644
index 000000000..0cd6be15a
--- /dev/null
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/support/PageInfo.java
@@ -0,0 +1,36 @@
+package com.loopers.interfaces.api.support;
+
+import java.util.Objects;
+import org.springframework.data.domain.Page;
+
+public record PageInfo(
+    long totalElements,
+    int currentPage,
+    int totalPages,
+    int pageSize,
+    String sort
+) {
+
+  public static PageInfo from(Page<?> page) {
+    Objects.requireNonNull(page, "page는 null일 수 없습니다.");
+    return new PageInfo(
+        page.getTotalElements(),
+        page.getNumber(),
+        page.getTotalPages(),
+        page.getSize(),
+        page.getSort().toString()
+    );
+  }
+
+  public static PageInfo from(Page<?> page, String sortDescription) {
+    Objects.requireNonNull(page, "page는 null일 수 없습니다.");
+    Objects.requireNonNull(sortDescription, "sortDescription은 null일 수 없습니다.");
+    return new PageInfo(
+        page.getTotalElements(),
+        page.getNumber(),
+        page.getTotalPages(),
+        page.getSize(),
+        sortDescription
+    );
+  }
+}
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserApiSpec.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserApiSpec.java
index 34ab3524b..8080c4103 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserApiSpec.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserApiSpec.java
@@ -21,7 +21,7 @@ ApiResponse<UserDto.UserResponse> register(
         summary = "사용자 조회",
         description = "로그인 ID로 사용자 정보를 조회합니다."
     )
-    ApiResponse<UserDto.UserResponse> getUser(
+    ApiResponse<UserDto.UserResponse> retrieveUserProfile(
         @Schema(description = "로그인 ID")
         String loginId
     );
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserController.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserController.java
index bcfadf9b7..84c1302e8 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserController.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserController.java
@@ -37,8 +37,8 @@ public ApiResponse<UserDto.UserResponse> register(
 
     @GetMapping("/{loginId}")
     @Override
-    public ApiResponse<UserDto.UserResponse> getUser(@PathVariable String loginId) {
-        UserResult userResult = userFacade.lookupUserProfile(loginId);
+    public ApiResponse<UserDto.UserResponse> retrieveUserProfile(@PathVariable("loginId") String loginId) {
+        UserResult userResult = userFacade.retrieveUserProfile(loginId);
         UserDto.UserResponse response = UserDto.UserResponse.from(userResult);
         return ApiResponse.success(response);
     }
diff --git a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserDto.java b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserDto.java
index be1c2e3e6..b39ff12ad 100644
--- a/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserDto.java
+++ b/apps/commerce-api/src/main/java/com/loopers/interfaces/api/user/UserDto.java
@@ -9,8 +9,13 @@
 import jakarta.validation.constraints.Past;
 import jakarta.validation.constraints.Pattern;
 import java.time.LocalDate;
+import java.util.Objects;
 
 public class UserDto {
+
+    private UserDto() {
+    }
+
     private static final String VALID_LOGIN_ID_PATTERN = "^[A-Za-z0-9]{1,10}$";
     private static final String VALID_EMAIL_PATTERN = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
     private static final String YYYY_MM_DD = "yyyy-MM-dd";
@@ -41,6 +46,7 @@ public record UserResponse(
         Gender gender
     ) {
         public static UserResponse from(UserResult result) {
+            Objects.requireNonNull(result, "result는 null일 수 없습니다.");
             return new UserResponse(
                 result.loginId(),
                 result.email(),
diff --git a/apps/commerce-api/src/main/java/com/loopers/support/error/ErrorType.java b/apps/commerce-api/src/main/java/com/loopers/support/error/ErrorType.java
index 2668d6a01..6a3cc3f37 100644
--- a/apps/commerce-api/src/main/java/com/loopers/support/error/ErrorType.java
+++ b/apps/commerce-api/src/main/java/com/loopers/support/error/ErrorType.java
@@ -11,6 +11,7 @@ public enum ErrorType {
     INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "일시적인 오류가 발생했습니다."),
     BAD_REQUEST(HttpStatus.BAD_REQUEST, HttpStatus.BAD_REQUEST.getReasonPhrase(), "잘못된 요청입니다."),
     NOT_FOUND(HttpStatus.NOT_FOUND, HttpStatus.NOT_FOUND.getReasonPhrase(), "존재하지 않는 요청입니다."),
+    FORBIDDEN(HttpStatus.FORBIDDEN, HttpStatus.FORBIDDEN.getReasonPhrase(), "권한이 없습니다."),
     CONFLICT(HttpStatus.CONFLICT, HttpStatus.CONFLICT.getReasonPhrase(), "이미 존재하는 리소스입니다."),
 
     /** Money 도메인 에러 */
diff --git a/apps/commerce-api/src/test/java/com/loopers/application/brand/BrandFacadeTest.java b/apps/commerce-api/src/test/java/com/loopers/application/brand/BrandFacadeTest.java
index 2fc4d710f..cbf778ed0 100644
--- a/apps/commerce-api/src/test/java/com/loopers/application/brand/BrandFacadeTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/application/brand/BrandFacadeTest.java
@@ -27,8 +27,8 @@ class BrandFacadeTest {
   private BrandService brandService;
 
   @Test
-  @DisplayName("브랜드 ID로 브랜드 조회 성공")
-  void viewBrand_success() {
+  @DisplayName("브랜드 ID로 브랜드를 조회한다")
+  void retrieveBrand_success() {
     // given
     Long brandId = 1L;
     Brand brand = Brand.of("나이키", "스포츠 브랜드");
@@ -36,7 +36,7 @@ void viewBrand_success() {
     given(brandService.getById(brandId)).willReturn(Optional.of(brand));
 
     // when
-    BrandResult result = brandFacade.viewBrand(brandId);
+    BrandResult result = brandFacade.retrieveBrand(brandId);
 
     // then
     assertThat(result)
@@ -45,15 +45,15 @@ void viewBrand_success() {
   }
 
   @Test
-  @DisplayName("존재하지 않는 브랜드 ID 조회 시 CoreException 발생")
-  void viewBrand_notFound() {
+  @DisplayName("존재하지 않는 브랜드 ID를 조회하면 CoreException이 발생한다")
+  void retrieveBrand_notFound() {
     // given
     Long brandId = 999L;
 
     given(brandService.getById(brandId)).willReturn(Optional.empty());
 
     // when & then
-    assertThatThrownBy(() -> brandFacade.viewBrand(brandId))
+    assertThatThrownBy(() -> brandFacade.retrieveBrand(brandId))
         .isInstanceOf(CoreException.class)
         .hasMessageContaining("브랜드를 찾을 수 없습니다.");
   }
diff --git a/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeIntegrationTest.java
index 124e81a84..92a785ea1 100644
--- a/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeIntegrationTest.java
@@ -1,31 +1,32 @@
 package com.loopers.application.like;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.tuple;
+
 import com.loopers.domain.brand.Brand;
 import com.loopers.domain.brand.BrandRepository;
 import com.loopers.domain.money.Money;
 import com.loopers.domain.product.Product;
 import com.loopers.domain.product.ProductRepository;
+import com.loopers.domain.productlike.LikeSortType;
+import com.loopers.domain.productlike.LikedProduct;
 import com.loopers.domain.productlike.ProductLike;
 import com.loopers.domain.productlike.ProductLikeRepository;
 import com.loopers.domain.stock.Stock;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
-import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
+import com.loopers.support.test.IntegrationTestSupport;
+import java.time.LocalDateTime;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import java.time.LocalDateTime;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
 
-@SpringBootTest
 @DisplayName("LikeFacade 통합 테스트")
-class LikeFacadeIntegrationTest {
+class LikeFacadeIntegrationTest extends IntegrationTestSupport {
 
   private static final LocalDateTime LIKED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
 
@@ -41,14 +42,6 @@ class LikeFacadeIntegrationTest {
   @Autowired
   private ProductLikeRepository productLikeRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @Nested
   @DisplayName("좋아요 등록 시")
   class LikeProduct {
@@ -63,7 +56,7 @@ void likesProduct_andIncreasesLikeCount() {
       Long userId = 1L;
       Long productId = product.getId();
 
-      likeFacade.likeProduct(userId, productId);
+      likeFacade.registerProductLike(userId, productId);
 
       assertThat(productLikeRepository.existsByUserIdAndProductId(userId, productId)).isTrue();
       Product updatedProduct = productRepository.findById(productId).orElseThrow();
@@ -71,7 +64,7 @@ void likesProduct_andIncreasesLikeCount() {
     }
 
     @Test
-    @DisplayName("같은 사용자가 두 번 좋아요 시도해도 1개만 증가 (멱등성)")
+    @DisplayName("같은 사용자가 두 번 좋아요 시도해도 1개만 증가한다. (멱등성)")
     void ensuresIdempotency_whenDuplicateLikes() {
       Brand brand = brandRepository.save(Brand.of("브랜드A", "설명A"));
       Product product = productRepository.save(
@@ -80,8 +73,8 @@ void ensuresIdempotency_whenDuplicateLikes() {
       Long userId = 1L;
       Long productId = product.getId();
 
-      likeFacade.likeProduct(userId, productId);
-      likeFacade.likeProduct(userId, productId);
+      likeFacade.registerProductLike(userId, productId);
+      likeFacade.registerProductLike(userId, productId);
 
       assertThat(productLikeRepository.existsByUserIdAndProductId(userId, productId)).isTrue();
       Product updatedProduct = productRepository.findById(productId).orElseThrow();
@@ -89,12 +82,12 @@ void ensuresIdempotency_whenDuplicateLikes() {
     }
 
     @Test
-    @DisplayName("존재하지 않는 상품 좋아요 시 예외 발생")
+    @DisplayName("존재하지 않는 상품 좋아요 시 예외 발생한다")
     void throwsException_whenProductNotFound() {
       Long userId = 1L;
       Long productId = 999999L;
 
-      assertThatThrownBy(() -> likeFacade.likeProduct(userId, productId))
+      assertThatThrownBy(() -> likeFacade.registerProductLike(userId, productId))
           .isInstanceOf(CoreException.class)
           .hasFieldOrPropertyWithValue("errorType", ErrorType.NOT_FOUND)
           .hasMessageContaining("상품을 찾을 수 없습니다.");
@@ -119,7 +112,7 @@ void unlikesProduct_andDecreasesLikeCount() {
       product.increaseLikeCount();
       productRepository.save(product);
 
-      likeFacade.unlikeProduct(userId, productId);
+      likeFacade.cancelProductLike(userId, productId);
 
       assertThat(productLikeRepository.existsByUserIdAndProductId(userId, productId)).isFalse();
       Product updatedProduct = productRepository.findById(productId).orElseThrow();
@@ -127,7 +120,7 @@ void unlikesProduct_andDecreasesLikeCount() {
     }
 
     @Test
-    @DisplayName("같은 사용자가 두 번 좋아요 취소 시도해도 1개만 감소 (멱등성)")
+    @DisplayName("같은 사용자가 두 번 좋아요 취소 시도해도 1개만 감소한다 (멱등성)")
     void ensuresIdempotency_whenDuplicateUnlikes() {
       Brand brand = brandRepository.save(Brand.of("브랜드A", "설명A"));
       Product product = productRepository.save(
@@ -140,8 +133,8 @@ void ensuresIdempotency_whenDuplicateUnlikes() {
       product.increaseLikeCount();
       productRepository.save(product);
 
-      likeFacade.unlikeProduct(userId, productId);
-      likeFacade.unlikeProduct(userId, productId);
+      likeFacade.cancelProductLike(userId, productId);
+      likeFacade.cancelProductLike(userId, productId);
 
       assertThat(productLikeRepository.existsByUserIdAndProductId(userId, productId)).isFalse();
       Product updatedProduct = productRepository.findById(productId).orElseThrow();
@@ -149,15 +142,176 @@ void ensuresIdempotency_whenDuplicateUnlikes() {
     }
 
     @Test
-    @DisplayName("존재하지 않는 상품 좋아요 취소 시 예외 발생")
+    @DisplayName("존재하지 않는 상품 좋아요 취소 시 예외가 발생한다")
     void throwsException_whenProductNotFound() {
       Long userId = 1L;
       Long productId = 999999L;
 
-      assertThatThrownBy(() -> likeFacade.unlikeProduct(userId, productId))
+      assertThatThrownBy(() -> likeFacade.cancelProductLike(userId, productId))
           .isInstanceOf(CoreException.class)
           .hasFieldOrPropertyWithValue("errorType", ErrorType.NOT_FOUND)
           .hasMessageContaining("상품을 찾을 수 없습니다.");
     }
   }
+
+  @Nested
+  @DisplayName("좋아요 목록 조회 시")
+  class RetrieveLikedProducts {
+
+    @Test
+    @DisplayName("LATEST 정렬 - 최근 좋아요 순으로 조회한다")
+    void retrievesLikedProducts_sortedByLatest() {
+      Brand brand = brandRepository.save(Brand.of("브랜드A", "설명A"));
+      Product product1 = productRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명1", Stock.of(100L), brand.getId()));
+      Product product2 = productRepository.save(
+          Product.of("상품2", Money.of(20000L), "설명2", Stock.of(50L), brand.getId()));
+
+      Long userId = 1L;
+      productLikeRepository.save(
+          ProductLike.of(userId, product1.getId(), LIKED_AT_2025_10_30));
+      productLikeRepository.save(ProductLike.of(userId, product2.getId(),
+          LIKED_AT_2025_10_30.plusSeconds(10)));
+
+      Page<LikedProduct> result = likeFacade.retrieveLikedProducts(userId, LikeSortType.LATEST,
+          PageRequest.of(0, 10));
+
+      assertThat(result.getContent())
+          .hasSize(2)
+          .extracting("productName", "likedAt")
+          .containsExactly(
+              tuple("상품2", LIKED_AT_2025_10_30.plusSeconds(10)),
+              tuple("상품1", LIKED_AT_2025_10_30)
+          );
+    }
+
+    @Test
+    @DisplayName("PRODUCT_NAME 정렬 - 상품명 오름차순으로 조회한다")
+    void retrievesLikedProducts_sortedByProductName() {
+      Brand brand = brandRepository.save(Brand.of("브랜드A", "설명A"));
+      Product charlie = productRepository.save(
+          Product.of("Charlie", Money.of(30000L), "설명C", Stock.of(30L), brand.getId()));
+      Product alpha = productRepository.save(
+          Product.of("Alpha", Money.of(10000L), "설명A", Stock.of(100L), brand.getId()));
+      Product beta = productRepository.save(
+          Product.of("Beta", Money.of(20000L), "설명B", Stock.of(50L), brand.getId()));
+
+      Long userId = 1L;
+      productLikeRepository.save(
+          ProductLike.of(userId, charlie.getId(), LIKED_AT_2025_10_30));
+      productLikeRepository.save(
+          ProductLike.of(userId, alpha.getId(), LIKED_AT_2025_10_30.plusSeconds(1)));
+      productLikeRepository.save(
+          ProductLike.of(userId, beta.getId(), LIKED_AT_2025_10_30.plusSeconds(2)));
+
+      Page<LikedProduct> result = likeFacade.retrieveLikedProducts(userId,
+          LikeSortType.PRODUCT_NAME, PageRequest.of(0, 10));
+
+      assertThat(result.getContent())
+          .hasSize(3)
+          .extracting("productName")
+          .containsExactly("Alpha", "Beta", "Charlie");
+    }
+
+    @Test
+    @DisplayName("PRICE_ASC 정렬 - 가격 오름차순으로 조회한다")
+    void retrievesLikedProducts_sortedByPriceAsc() {
+      Brand brand = brandRepository.save(Brand.of("브랜드A", "설명A"));
+      Product expensive = productRepository.save(
+          Product.of("비싼상품", Money.of(50000L), "설명", Stock.of(10L), brand.getId()));
+      Product cheap = productRepository.save(
+          Product.of("저렴한상품", Money.of(10000L), "설명", Stock.of(100L), brand.getId()));
+      Product medium = productRepository.save(
+          Product.of("중간상품", Money.of(30000L), "설명", Stock.of(50L), brand.getId()));
+
+      Long userId = 1L;
+      productLikeRepository.save(
+          ProductLike.of(userId, expensive.getId(), LIKED_AT_2025_10_30));
+      productLikeRepository.save(
+          ProductLike.of(userId, cheap.getId(), LIKED_AT_2025_10_30.plusSeconds(1)));
+      productLikeRepository.save(
+          ProductLike.of(userId, medium.getId(), LIKED_AT_2025_10_30.plusSeconds(2)));
+
+      Page<LikedProduct> result = likeFacade.retrieveLikedProducts(userId,
+          LikeSortType.PRICE_ASC, PageRequest.of(0, 10));
+
+      assertThat(result.getContent())
+          .hasSize(3)
+          .extracting("productName", "price")
+          .containsExactly(
+              tuple("저렴한상품", 10000L),
+              tuple("중간상품", 30000L),
+              tuple("비싼상품", 50000L)
+          );
+    }
+
+    @Test
+    @DisplayName("PRICE_DESC 정렬 - 가격 내림차순으로 조회한다")
+    void retrievesLikedProducts_sortedByPriceDesc() {
+      Brand brand = brandRepository.save(Brand.of("브랜드A", "설명A"));
+      Product expensive = productRepository.save(
+          Product.of("비싼상품", Money.of(50000L), "설명", Stock.of(10L), brand.getId()));
+      Product cheap = productRepository.save(
+          Product.of("저렴한상품", Money.of(10000L), "설명", Stock.of(100L), brand.getId()));
+      Product medium = productRepository.save(
+          Product.of("중간상품", Money.of(30000L), "설명", Stock.of(50L), brand.getId()));
+
+      Long userId = 1L;
+      productLikeRepository.save(
+          ProductLike.of(userId, expensive.getId(), LIKED_AT_2025_10_30));
+      productLikeRepository.save(
+          ProductLike.of(userId, cheap.getId(), LIKED_AT_2025_10_30.plusSeconds(1)));
+      productLikeRepository.save(
+          ProductLike.of(userId, medium.getId(), LIKED_AT_2025_10_30.plusSeconds(2)));
+
+      Page<LikedProduct> result = likeFacade.retrieveLikedProducts(userId,
+          LikeSortType.PRICE_DESC, PageRequest.of(0, 10));
+
+      assertThat(result.getContent())
+          .hasSize(3)
+          .extracting("productName", "price")
+          .containsExactly(
+              tuple("비싼상품", 50000L),
+              tuple("중간상품", 30000L),
+              tuple("저렴한상품", 10000L)
+          );
+    }
+
+    @Test
+    @DisplayName("좋아요한 상품이 없으면 빈 페이지를 반환한다")
+    void returnsEmptyPage_whenNoLikedProducts() {
+      Long userId = 1L;
+
+      Page<LikedProduct> result = likeFacade.retrieveLikedProducts(userId, LikeSortType.LATEST,
+          PageRequest.of(0, 10));
+
+      assertThat(result.getContent()).isEmpty();
+      assertThat(result.getTotalElements()).isZero();
+    }
+
+    @Test
+    @DisplayName("브랜드 정보가 포함된다")
+    void includesBrandInformation() {
+      Brand brandA = brandRepository.save(Brand.of("브랜드A", "설명A"));
+      Brand brandB = brandRepository.save(Brand.of("브랜드B", "설명B"));
+      Product product1 = productRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명1", Stock.of(100L), brandA.getId()));
+      Product product2 = productRepository.save(
+          Product.of("상품2", Money.of(20000L), "설명2", Stock.of(50L), brandB.getId()));
+
+      Long userId = 1L;
+      productLikeRepository.save(
+          ProductLike.of(userId, product1.getId(), LIKED_AT_2025_10_30));
+      productLikeRepository.save(
+          ProductLike.of(userId, product2.getId(), LIKED_AT_2025_10_30.plusSeconds(1)));
+
+      Page<LikedProduct> result = likeFacade.retrieveLikedProducts(userId, LikeSortType.LATEST,
+          PageRequest.of(0, 10));
+
+      assertThat(result.getContent())
+          .hasSize(2)
+          .extracting("brandName")
+          .containsExactly("브랜드B", "브랜드A");
+    }
+  }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeRaceConditionTest.java b/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeRaceConditionTest.java
new file mode 100644
index 000000000..aa170dbc4
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/application/like/LikeFacadeRaceConditionTest.java
@@ -0,0 +1,140 @@
+package com.loopers.application.like;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.loopers.domain.money.Money;
+import com.loopers.domain.product.Product;
+import com.loopers.domain.product.ProductRepository;
+import com.loopers.domain.productlike.ProductLike;
+import com.loopers.domain.productlike.ProductLikeRepository;
+import com.loopers.domain.stock.Stock;
+import com.loopers.support.test.IntegrationTestSupport;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@DisplayName("LikeFacade 동시성 테스트")
+public class LikeFacadeRaceConditionTest extends IntegrationTestSupport {
+
+  private static final Logger log = LoggerFactory.getLogger(LikeFacadeRaceConditionTest.class);
+
+  @Autowired
+  private LikeFacade likeFacade;
+
+  @Autowired
+  private ProductRepository productRepository;
+
+  @Autowired
+  private ProductLikeRepository productLikeRepository;
+
+  @Nested
+  @DisplayName("좋아요 추가 동시성")
+  class ConcurrentLike {
+
+    @Test
+    @DisplayName("10명이 동시에 같은 상품에 좋아요 시 카운트가 정확히 10으로 증가한다")
+    void countsCorrectly() {
+      // given
+      Product product = productRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명1", Stock.of(100L), 1L)
+      );
+
+      // when
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+      for (int i = 1; i <= 10; i++) {
+        Long userId = (long) i;
+        futures.add(asyncExecute(() -> likeFacade.registerProductLike(userId, product.getId())));
+      }
+
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
+
+      // then
+      Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
+      long likeCount = updatedProduct.getLikeCount();
+      assertThat(likeCount).isEqualTo(10L);
+    }
+
+    @Test
+    @DisplayName("같은 유저가 동시에 5번 좋아요 시 중복 방지되어 1개만 등록된다")
+    void preventsDuplicate() {
+      // given
+      Product product = productRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명1", Stock.of(100L), 1L)
+      );
+      Long userId = 1L;
+
+      // when
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+      for (int i = 0; i < 5; i++) {
+        futures.add(asyncExecute(() -> likeFacade.registerProductLike(userId, product.getId())));
+      }
+
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
+
+      // then
+      Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
+      long likeCount = updatedProduct.getLikeCount();
+      assertThat(likeCount).isEqualTo(1L);
+
+      List<ProductLike> likes = productLikeRepository.findByUserIdAndProductIdIn(userId, List.of(product.getId()));
+      assertThat(likes).hasSize(1);
+    }
+
+  }
+
+  @Nested
+  @DisplayName("좋아요 취소 동시성")
+  class ConcurrentUnlike {
+
+    @Test
+    @DisplayName("10명이 동시에 좋아요 취소 시 카운트가 정확히 0으로 감소한다")
+    void countsCorrectly() {
+      // given
+      Product product = productRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명1", Stock.of(100L), 1L)
+      );
+
+      for (int i = 1; i <= 10; i++) {
+        Long userId = (long) i;
+        likeFacade.registerProductLike(userId, product.getId());
+      }
+
+      // when
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+      for (int i = 1; i <= 10; i++) {
+        Long userId = (long) i;
+        futures.add(asyncExecute(() -> likeFacade.cancelProductLike(userId, product.getId())));
+      }
+
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
+
+      // then
+      Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
+      long likeCount = updatedProduct.getLikeCount();
+      assertThat(likeCount).isZero();
+    }
+  }
+
+  private CompletableFuture<Void> asyncExecute(Runnable task) {
+    return CompletableFuture.runAsync(() -> {
+      try {
+        task.run();
+      } catch (Exception e) {
+        log.debug("동시성 테스트 중 예외 발생 (예상됨): {}", e.getMessage());
+      }
+    });
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeIntegrationTest.java
index 1717b87d4..886bd84ae 100644
--- a/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeIntegrationTest.java
@@ -1,7 +1,6 @@
 package com.loopers.application.order;
 
-import com.loopers.domain.brand.Brand;
-import com.loopers.domain.brand.BrandRepository;
+import com.loopers.support.test.IntegrationTestSupport;
 import com.loopers.domain.money.Money;
 import com.loopers.domain.order.Order;
 import com.loopers.domain.order.orderitem.OrderItemCommand;
@@ -22,8 +21,6 @@
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.transaction.annotation.Transactional;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -31,44 +28,33 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertAll;
 
-@SpringBootTest
-@Transactional
 @DisplayName("OrderFacade 통합 테스트")
-class OrderFacadeIntegrationTest {
+class OrderFacadeIntegrationTest extends IntegrationTestSupport {
 
+  private static final LocalDate BIRTH_DATE_1990_01_01 = LocalDate.of(1990, 1, 1);
   @Autowired
   private OrderFacade orderFacade;
-
   @Autowired
   private ProductRepository productRepository;
-
-  @Autowired
-  private BrandRepository brandRepository;
-
   @Autowired
   private UserRepository userRepository;
-
   @Autowired
   private PointRepository pointRepository;
 
   private User user;
-  private Brand brand;
   private Product product1;
   private Product product2;
 
-  private static final LocalDateTime ORDERED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
-  private static final LocalDate BIRTH_DATE_1990_01_01 = LocalDate.of(1990, 1, 1);
-
   @BeforeEach
   void setUp() {
     user = userRepository.save(User.of("testuser", "test@example.com", BIRTH_DATE_1990_01_01, Gender.MALE, LocalDate.of(2025, 10, 30)));
-    brand = brandRepository.save(Brand.of("테스트브랜드"));
     product1 = productRepository.save(
-        Product.of("상품1", Money.of(10000L), "설명1", Stock.of(10L), brand.getId())
+        Product.of("상품1", Money.of(10000L), "설명1", Stock.of(10L), 1L)
     );
     product2 = productRepository.save(
-        Product.of("상품2", Money.of(30000L), "설명2", Stock.of(5L), brand.getId())
+        Product.of("상품2", Money.of(30000L), "설명2", Stock.of(5L), 1L)
     );
     pointRepository.save(Point.of(user.getId(), 100000L));
   }
@@ -78,35 +64,44 @@ void setUp() {
   class CreateOrder {
 
     @Test
-    @DisplayName("유효한 주문 요청이면 주문이 생성되고 재고와 포인트가 차감된다")
-    void createOrder_success() {
+    @DisplayName("재고가 0인 상품을 주문하면 예외가 발생한다")
+    void createOrder_zeroStock() {
       // given
-      List<OrderItemCommand> commands = List.of(
-          OrderItemCommand.of(product1.getId(), Quantity.of(2L)),
-          OrderItemCommand.of(product2.getId(), Quantity.of(1L))
+      Product zeroStockProduct = productRepository.save(
+          Product.of("재고0상품", Money.of(10000L), "설명", Stock.of(0L), 1L)
       );
 
-      // when
-      Order order = orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30);
+      List<OrderItemCommand> commands = List.of(
+          OrderItemCommand.of(zeroStockProduct.getId(), Quantity.of(1L))
+      );
 
-      // then
-      assertThat(order.getTotalAmountValue()).isEqualTo(50000L);
-      assertThat(order.getStatus()).isEqualTo(OrderStatus.PENDING);
+      // when & then
+      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands))
+          .isInstanceOf(CoreException.class)
+          .hasFieldOrPropertyWithValue("errorType", ErrorType.INSUFFICIENT_STOCK);
+    }
 
-      // 재고 차감 확인 (실제 DB 조회)
-      Product updatedProduct1 = productRepository.findById(product1.getId()).orElseThrow();
-      assertThat(updatedProduct1.getStockValue()).isEqualTo(8L);
+    @Test
+    @DisplayName("포인트가 부족하면 예외가 발생한다")
+    void createOrder_insufficientPoint() {
+      // given
+      List<OrderItemCommand> commands = List.of(
+          OrderItemCommand.of(product1.getId(), Quantity.of(1L))
+      );
 
-      Product updatedProduct2 = productRepository.findById(product2.getId()).orElseThrow();
-      assertThat(updatedProduct2.getStockValue()).isEqualTo(4L);
+      Point point = pointRepository.findByUserId(user.getId()).orElseThrow();
+      point.deduct(95000L);
+      pointRepository.save(point);
 
-      // 포인트 차감 확인
-      Point updatedPoint = pointRepository.findByUserId(user.getId()).orElseThrow();
-      assertThat(updatedPoint.getAmountValue()).isEqualTo(50000L);
+      // when & then
+      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands))
+          .isInstanceOf(CoreException.class)
+          .hasFieldOrPropertyWithValue("errorType", ErrorType.INSUFFICIENT_POINT_BALANCE);
     }
 
+
     @Test
-    @DisplayName("재고가 부족하면 예외가 발생하고 트랜잭션이 롤백된다")
+    @DisplayName("재고가 부족하면 예외가 발생한다")
     void createOrder_insufficientStock() {
       // given
       List<OrderItemCommand> commands = List.of(
@@ -114,82 +109,85 @@ void createOrder_insufficientStock() {
       );
 
       // when & then
-      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30))
+      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands))
           .isInstanceOf(CoreException.class)
           .hasFieldOrPropertyWithValue("errorType", ErrorType.INSUFFICIENT_STOCK)
           .hasMessageContaining("재고가 부족합니다");
-
-      // 롤백 확인 - 재고 변경 안 됨
-      Product unchangedProduct = productRepository.findById(product1.getId()).orElseThrow();
-      assertThat(unchangedProduct.getStockValue()).isEqualTo(10L);
-
-      // 포인트도 변경 안 됨
-      Point unchangedPoint = pointRepository.findByUserId(user.getId()).orElseThrow();
-      assertThat(unchangedPoint.getAmountValue()).isEqualTo(100000L);
     }
 
     @Test
-    @DisplayName("재고가 0인 상품을 주문하면 예외가 발생한다")
-    void createOrder_zeroStock() {
+    @DisplayName("재고가 부족하면 트랜잭션이 롤백된다")
+    void createOrder_rollbackWhenStockInsufficient() {
       // given
-      Product zeroStockProduct = productRepository.save(
-          Product.of("재고0상품", Money.of(10000L), "설명", Stock.of(0L), brand.getId())
-      );
-
       List<OrderItemCommand> commands = List.of(
-          OrderItemCommand.of(zeroStockProduct.getId(), Quantity.of(1L))
+          OrderItemCommand.of(product1.getId(), Quantity.of(100L))
       );
 
       // when & then
-      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30))
+      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands))
           .isInstanceOf(CoreException.class)
           .hasFieldOrPropertyWithValue("errorType", ErrorType.INSUFFICIENT_STOCK);
+
+      Product unchangedProduct = productRepository.findById(product1.getId()).orElseThrow();
+      Point unchangedPoint = pointRepository.findByUserId(user.getId()).orElseThrow();
+
+      assertThat(unchangedProduct.getStockValue()).isEqualTo(10L);
+      assertThat(unchangedPoint.getAmountValue()).isEqualTo(100000L);
     }
 
     @Test
-    @DisplayName("포인트가 부족하면 예외가 발생하고 트랜잭션이 롤백된다")
-    void createOrder_insufficientPoint() {
+    @DisplayName("포인트가 부족하면 트랜잭션이 롤백된다")
+    void createOrder_rollbackWhenPointInsufficient() {
       // given
       List<OrderItemCommand> commands = List.of(
           OrderItemCommand.of(product1.getId(), Quantity.of(1L))
       );
 
-      // 포인트를 5000으로 설정 (부족)
       Point point = pointRepository.findByUserId(user.getId()).orElseThrow();
       point.deduct(95000L);
+      pointRepository.save(point);
 
       // when & then
-      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30))
+      assertThatThrownBy(() -> orderFacade.createOrder(user.getId(), commands))
           .isInstanceOf(CoreException.class)
           .hasFieldOrPropertyWithValue("errorType", ErrorType.INSUFFICIENT_POINT_BALANCE);
 
-      // 재고 변경 안 됨 (포인트 차감 전 실패)
       Product unchangedProduct = productRepository.findById(product1.getId()).orElseThrow();
-      assertThat(unchangedProduct.getStockValue()).isEqualTo(10L);
-
-      // 포인트는 차감 전 상태 유지
       Point unchangedPoint = pointRepository.findByUserId(user.getId()).orElseThrow();
+
+      assertThat(unchangedProduct.getStockValue()).isEqualTo(10L);
       assertThat(unchangedPoint.getAmountValue()).isEqualTo(5000L);
     }
 
+
     @Test
-    @DisplayName("주문 생성 시 총액이 정확하게 계산되고 포인트가 차감된다")
-    void createOrder_totalAmountCalculation() {
+    @DisplayName("유효한 주문 요청이면 주문이 생성되고 재고와 포인트가 차감된다")
+    void createOrder_success() {
       // given
       List<OrderItemCommand> commands = List.of(
-          OrderItemCommand.of(product1.getId(), Quantity.of(3L)),
-          OrderItemCommand.of(product2.getId(), Quantity.of(2L))
+          OrderItemCommand.of(product1.getId(), Quantity.of(2L)),
+          OrderItemCommand.of(product2.getId(), Quantity.of(1L))
       );
 
       // when
-      Order order = orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30);
+      Order order = orderFacade.createOrder(user.getId(), commands);
 
       // then
-      assertThat(order.getTotalAmountValue()).isEqualTo(90000L);
-
-      // 포인트 차감 확인
+      Product updatedProduct1 = productRepository.findById(product1.getId()).orElseThrow();
+      Product updatedProduct2 = productRepository.findById(product2.getId()).orElseThrow();
       Point updatedPoint = pointRepository.findByUserId(user.getId()).orElseThrow();
-      assertThat(updatedPoint.getAmountValue()).isEqualTo(10000L);
+
+      assertAll(
+          () -> assertThat(order)
+              .extracting("totalAmountValue", "status")
+              .containsExactly(50000L, OrderStatus.PENDING),
+          () -> assertThat(updatedProduct1.getStockValue()).isEqualTo(8L),
+          () -> assertThat(updatedProduct2.getStockValue()).isEqualTo(4L),
+          () -> assertThat(updatedPoint.getAmountValue()).isEqualTo(50000L)
+      );
+
+
     }
+
   }
 }
diff --git a/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeRaceConditionTest.java b/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeRaceConditionTest.java
index 4a00fe1b1..6b9843b64 100644
--- a/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeRaceConditionTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/application/order/OrderFacadeRaceConditionTest.java
@@ -1,7 +1,7 @@
 package com.loopers.application.order;
 
-import com.loopers.domain.brand.Brand;
-import com.loopers.domain.brand.BrandRepository;
+import static org.assertj.core.api.Assertions.assertThat;
+
 import com.loopers.domain.money.Money;
 import com.loopers.domain.order.orderitem.OrderItemCommand;
 import com.loopers.domain.point.Point;
@@ -13,26 +13,25 @@
 import com.loopers.domain.user.Gender;
 import com.loopers.domain.user.User;
 import com.loopers.domain.user.UserRepository;
-import com.loopers.support.error.CoreException;
-import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-
+import com.loopers.support.test.IntegrationTestSupport;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 
-import static org.assertj.core.api.Assertions.assertThat;
-
-@SpringBootTest
 @DisplayName("OrderFacade 동시성 테스트")
-class OrderFacadeRaceConditionTest {
+class OrderFacadeRaceConditionTest extends IntegrationTestSupport {
+
+  private static final Logger log = LoggerFactory.getLogger(OrderFacadeRaceConditionTest.class);
 
   @Autowired
   private OrderFacade orderFacade;
@@ -40,195 +39,234 @@ class OrderFacadeRaceConditionTest {
   @Autowired
   private ProductRepository productRepository;
 
-  @Autowired
-  private BrandRepository brandRepository;
-
   @Autowired
   private UserRepository userRepository;
 
   @Autowired
   private PointRepository pointRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
   private static final LocalDateTime ORDERED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
   private static final LocalDate BIRTH_DATE_1990_01_01 = LocalDate.of(1990, 1, 1);
+  private static final LocalDate JOINED_AT_2025_10_30 = LocalDate.of(2025, 10, 30);
+
+  private static final long POINT_BALANCE_50_000 = 50000L;
+  private static final long BRAND_ID = 1L;
+
+  @DisplayName("재고 동시성 테스트")
+  @Nested
+  class StockConcurrency {
+
+    @Test
+    @DisplayName("재고 1개 상품에 2명이 동시 주문 시 1개만 성공하고 재고가 0이 된다")
+    void concurrentOrders_onlyOneSucceeds() {
+      // given
+      Product product = productRepository.save(
+          Product.of("재고1개상품", Money.of(10000L), "재고 1개 상품", Stock.of(1L), BRAND_ID)
+      );
+
+      User user1 = createUserWithPoint("user1", "user1@test.com", Gender.MALE, POINT_BALANCE_50_000);
+      User user2 = createUserWithPoint("user2", "user2@test.com", Gender.FEMALE, POINT_BALANCE_50_000);
+
+      List<OrderItemCommand> commands = List.of(
+          OrderItemCommand.of(product.getId(), Quantity.of(1L))
+      );
+
+      // when
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+      futures.add(asyncExecute(() -> orderFacade.createOrder(user1.getId(), commands)));
+      futures.add(asyncExecute(() -> orderFacade.createOrder(user2.getId(), commands)));
+
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
+
+      // then
+      Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
+      long remainingStock = updatedProduct.getStockValue();
+      assertThat(remainingStock).isZero();
+    }
 
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
-  @Test
-  @DisplayName("동시에 2개 주문 시 하나만 성공하고 재고가 음수가 되지 않음")
-  void concurrentOrders_onlyOneSucceeds() {
-    Brand brand = brandRepository.save(Brand.of("테스트브랜드"));
-    Product product = productRepository.save(
-        Product.of("재고1개상품", Money.of(10000L), "재고 1개 상품", Stock.of(1L), brand.getId())
-    );
-
-    int random = (int) (System.nanoTime() % 10000);
-    User user1 = userRepository.save(User.of("u1" + random, "u1" + random + "@t.com", BIRTH_DATE_1990_01_01, Gender.MALE, LocalDate.of(2025, 10, 30)));
-    User user2 = userRepository.save(User.of("u2" + random, "u2" + random + "@t.com", BIRTH_DATE_1990_01_01, Gender.FEMALE, LocalDate.of(2025, 10, 30)));
-    pointRepository.save(Point.of(user1.getId(), 50000L));
-    pointRepository.save(Point.of(user2.getId(), 50000L));
-
-    List<OrderItemCommand> commands = List.of(
-        OrderItemCommand.of(product.getId(), Quantity.of(1L))
-    );
+    @Test
+    @DisplayName("재고 5개 상품에 10명이 동시 주문 시 5개만 성공하고 재고가 0이 된다")
+    void concurrentOrders_multipleThreads() {
+      // given
+      Product product = productRepository.save(
+          Product.of("재고5개상품", Money.of(5000L), "재고 5개 상품", Stock.of(5L), BRAND_ID)
+      );
+
+      List<OrderItemCommand> commands = List.of(
+          OrderItemCommand.of(product.getId(), Quantity.of(1L))
+      );
+
+      // when
+      AtomicInteger userIdCounter = new AtomicInteger(10);
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+
+      for (int i = 0; i < 10; i++) {
+        futures.add(asyncExecute(() -> {
+          int userId = userIdCounter.getAndIncrement();
+          String loginId = "user" + userId;
+          User user = createUserWithPoint(loginId, loginId + "@test.com", Gender.MALE, POINT_BALANCE_50_000);
+          orderFacade.createOrder(user.getId(), commands);
+        }));
+      }
 
-    List<CompletableFuture<Void>> futures = new ArrayList<>();
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
 
-    futures.add(CompletableFuture.runAsync(() -> {
-      try {
-        orderFacade.createOrder(user1.getId(), commands, ORDERED_AT_2025_10_30);
-      } catch (CoreException ignored) {
-        // 재고 부족 시 예외 발생 예상
-      }
-    }));
+      // then
+      Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
+      long remainingStock = updatedProduct.getStockValue();
+      assertThat(remainingStock).isZero();
+    }
 
-    futures.add(CompletableFuture.runAsync(() -> {
-      try {
-        orderFacade.createOrder(user2.getId(), commands, ORDERED_AT_2025_10_30);
-      } catch (CoreException ignored) {
-        // 재고 부족 시 예외 발생 예상
+    @Test
+    @DisplayName("재고 3개 상품에 5명이 동시 주문 시 재고가 음수가 되지 않는다")
+    void concurrentOrders_stockNeverNegative() {
+      // given
+      Product product = productRepository.save(
+          Product.of("재고3개상품", Money.of(3000L), "재고 3개 상품", Stock.of(3L), BRAND_ID)
+      );
+
+      List<OrderItemCommand> commands = List.of(
+          OrderItemCommand.of(product.getId(), Quantity.of(1L))
+      );
+
+      // when
+      AtomicInteger userIdCounter = new AtomicInteger(20);
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+
+      for (int i = 0; i < 5; i++) {
+        futures.add(asyncExecute(() -> {
+          int userId = userIdCounter.getAndIncrement();
+          String loginId = "user" + userId;
+          User user = createUserWithPoint(loginId, loginId + "@test.com", Gender.MALE, POINT_BALANCE_50_000);
+          orderFacade.createOrder(user.getId(), commands);
+        }));
       }
-    }));
 
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
 
-    Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
-    assertThat(updatedProduct).extracting("stockValue").isEqualTo(0L);
+      // then
+      Product finalProduct = productRepository.findById(product.getId()).orElseThrow();
+      long remainingStock = finalProduct.getStockValue();
+      assertThat(remainingStock).isGreaterThanOrEqualTo(0L);
+    }
   }
 
-  @Test
-  @DisplayName("동시에 10개 주문 시 재고 5개 상품은 5개만 성공")
-  void concurrentOrders_multipleThreads() {
-    Brand brand = brandRepository.save(Brand.of("테스트브랜드"));
-    Product product = productRepository.save(
-        Product.of("재고5개상품", Money.of(5000L), "재고 5개 상품", Stock.of(5L), brand.getId())
-    );
-
-    int threadCount = 10;
-
-    List<OrderItemCommand> commands = List.of(
-        OrderItemCommand.of(product.getId(), Quantity.of(1L))
-    );
-
-    AtomicInteger counter = new AtomicInteger(0);
-    List<CompletableFuture<Void>> futures = new ArrayList<>();
-
-    for (int i = 0; i < threadCount; i++) {
-      futures.add(CompletableFuture.runAsync(() -> {
-        try {
-          int id = counter.getAndIncrement();
-          String loginId = "u" + id;
-          User user = userRepository.save(User.of(loginId, loginId + "@t.com", BIRTH_DATE_1990_01_01, Gender.MALE, LocalDate.of(2025, 10, 30)));
-          pointRepository.save(Point.of(user.getId(), 50000L));
-          orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30);
-        } catch (CoreException ignored) {
-          // 재고 부족 시 예외 발생 예상
-        }
-      }));
+  @DisplayName("포인트 동시성 테스트")
+  @Nested
+  class PointConcurrency {
+
+    @Test
+    @DisplayName("동일한 유저가 서로 다른 상품 2개를 동시 주문 시 포인트가 정확히 차감된다")
+    void concurrentOrders_sameUser_differentProducts() {
+      // given
+      Product product1 = productRepository.save(
+          Product.of("상품1", Money.of(10000L), "상품 1", Stock.of(10L), BRAND_ID)
+      );
+      Product product2 = productRepository.save(
+          Product.of("상품2", Money.of(20000L), "상품 2", Stock.of(10L), BRAND_ID)
+      );
+
+      User user = createUserWithPoint("user30", "user30@test.com", Gender.MALE, POINT_BALANCE_50_000);
+
+      List<OrderItemCommand> commands1 = List.of(
+          OrderItemCommand.of(product1.getId(), Quantity.of(1L))
+      );
+      List<OrderItemCommand> commands2 = List.of(
+          OrderItemCommand.of(product2.getId(), Quantity.of(1L))
+      );
+
+      // when
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+      futures.add(asyncExecute(() -> orderFacade.createOrder(user.getId(), commands1)));
+      futures.add(asyncExecute(() -> orderFacade.createOrder(user.getId(), commands2)));
+
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
+
+      // then
+      Point finalPoint = pointRepository.findByUserId(user.getId()).orElseThrow();
+      long remainingBalance = finalPoint.getAmountValue();
+      assertThat(remainingBalance).isEqualTo(20000L);
     }
-
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-
-    Product updatedProduct = productRepository.findById(product.getId()).orElseThrow();
-    assertThat(updatedProduct).extracting("stockValue").isEqualTo(0L);
   }
 
-  @Test
-  @DisplayName("동시 주문 시 재고가 음수가 되지 않음")
-  void concurrentOrders_stockNeverNegative() {
-    Brand brand = brandRepository.save(Brand.of("테스트브랜드"));
-    Product product = productRepository.save(
-        Product.of("재고3개상품", Money.of(3000L), "재고 3개 상품", Stock.of(3L), brand.getId())
-    );
-
-    int threadCount = 5;
-
-    List<OrderItemCommand> commands = List.of(
-        OrderItemCommand.of(product.getId(), Quantity.of(1L))
-    );
-
-    AtomicInteger counter = new AtomicInteger(100);
-    List<CompletableFuture<Void>> futures = new ArrayList<>();
-
-    for (int i = 0; i < threadCount; i++) {
-      futures.add(CompletableFuture.runAsync(() -> {
-        try {
-          int id = counter.getAndIncrement();
-          String loginId = "u" + id;
-          User user = userRepository.save(User.of(loginId, loginId + "@t.com", BIRTH_DATE_1990_01_01, Gender.MALE, LocalDate.of(2025, 10, 30)));
-          pointRepository.save(Point.of(user.getId(), 50000L));
-          orderFacade.createOrder(user.getId(), commands, ORDERED_AT_2025_10_30);
-        } catch (CoreException ignored) {
-          // 재고 부족 시 예외 발생 예상
-        }
-      }));
+  @DisplayName("데드락 방지 테스트")
+  @Nested
+  class DeadlockPrevention {
+
+    @Test
+    @DisplayName("여러 상품 역순 동시 주문 시 데드락이 발생하지 않는다 (ORDER BY 검증)")
+    void concurrentOrders_multipleProducts_noDeadlock() {
+      // given
+      Product product1 = productRepository.save(
+          Product.of("상품1", Money.of(1000L), "상품 1", Stock.of(10L), BRAND_ID)
+      );
+      Product product2 = productRepository.save(
+          Product.of("상품2", Money.of(2000L), "상품 2", Stock.of(10L), BRAND_ID)
+      );
+      Product product3 = productRepository.save(
+          Product.of("상품3", Money.of(3000L), "상품 3", Stock.of(10L), BRAND_ID)
+      );
+
+      User user1 = createUserWithPoint("user40", "user40@test.com", Gender.MALE, POINT_BALANCE_50_000);
+      User user2 = createUserWithPoint("user41", "user41@test.com", Gender.FEMALE, POINT_BALANCE_50_000);
+
+      List<OrderItemCommand> commands1 = List.of(
+          OrderItemCommand.of(product1.getId(), Quantity.of(1L)),
+          OrderItemCommand.of(product2.getId(), Quantity.of(1L)),
+          OrderItemCommand.of(product3.getId(), Quantity.of(1L))
+      );
+
+      List<OrderItemCommand> commands2 = List.of(
+          OrderItemCommand.of(product3.getId(), Quantity.of(1L)),
+          OrderItemCommand.of(product2.getId(), Quantity.of(1L)),
+          OrderItemCommand.of(product1.getId(), Quantity.of(1L))
+      );
+
+      // when
+      List<CompletableFuture<Void>> futures = new ArrayList<>();
+      futures.add(asyncExecute(() -> orderFacade.createOrder(user1.getId(), commands1)));
+      futures.add(asyncExecute(() -> orderFacade.createOrder(user2.getId(), commands2)));
+
+      CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
+          .orTimeout(10, TimeUnit.SECONDS)
+          .join();
+
+      // then
+      List<Product> finalProducts = List.of(
+          productRepository.findById(product1.getId()).orElseThrow(),
+          productRepository.findById(product2.getId()).orElseThrow(),
+          productRepository.findById(product3.getId()).orElseThrow()
+      );
+
+      assertThat(finalProducts)
+          .extracting("stockValue")
+          .containsExactly(8L, 8L, 8L);
     }
-
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
-
-    Product finalProduct = productRepository.findById(product.getId()).orElseThrow();
-    assertThat(finalProduct).extracting("stockValue").matches(value -> (Long) value >= 0L);
   }
 
-  @Test
-  @DisplayName("여러 상품 역순 동시 주문 시 데드락이 발생하지 않음 (ORDER BY 검증)")
-  void concurrentOrders_multipleProducts_noDeadlock() {
-    Brand brand = brandRepository.save(Brand.of("테스트브랜드"));
-    Product product1 = productRepository.save(
-        Product.of("상품1", Money.of(1000L), "상품 1", Stock.of(10L), brand.getId())
-    );
-    Product product2 = productRepository.save(
-        Product.of("상품2", Money.of(2000L), "상품 2", Stock.of(10L), brand.getId())
-    );
-    Product product3 = productRepository.save(
-        Product.of("상품3", Money.of(3000L), "상품 3", Stock.of(10L), brand.getId())
-    );
-
-    int random1 = (int) (System.nanoTime() % 10000);
-    int random2 = random1 + 1;
-    User user1 = userRepository.save(User.of("u1" + random1, "u1" + random1 + "@t.com", BIRTH_DATE_1990_01_01, Gender.MALE, LocalDate.of(2025, 10, 30)));
-    User user2 = userRepository.save(User.of("u2" + random2, "u2" + random2 + "@t.com", BIRTH_DATE_1990_01_01, Gender.FEMALE, LocalDate.of(2025, 10, 30)));
-    pointRepository.save(Point.of(user1.getId(), 100000L));
-    pointRepository.save(Point.of(user2.getId(), 100000L));
-
-    List<OrderItemCommand> commands1 = List.of(
-        OrderItemCommand.of(product1.getId(), Quantity.of(1L)),
-        OrderItemCommand.of(product2.getId(), Quantity.of(1L)),
-        OrderItemCommand.of(product3.getId(), Quantity.of(1L))
-    );
-
-    List<OrderItemCommand> commands2 = List.of(
-        OrderItemCommand.of(product3.getId(), Quantity.of(1L)),
-        OrderItemCommand.of(product2.getId(), Quantity.of(1L)),
-        OrderItemCommand.of(product1.getId(), Quantity.of(1L))
-    );
-
-    List<CompletableFuture<Void>> futures = new ArrayList<>();
-
-    futures.add(CompletableFuture.runAsync(() -> {
-      orderFacade.createOrder(user1.getId(), commands1, ORDERED_AT_2025_10_30);
-    }));
-
-    futures.add(CompletableFuture.runAsync(() -> {
-      orderFacade.createOrder(user2.getId(), commands2, ORDERED_AT_2025_10_30);
-    }));
-
-    CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
+  private CompletableFuture<Void> asyncExecute(Runnable task) {
+    return CompletableFuture.runAsync(() -> {
+      try {
+        task.run();
+      } catch (Exception e) {
+        log.debug("동시성 테스트 중 예외 발생 (예상됨): {}", e.getMessage());
+      }
+    });
+  }
 
-    List<Product> finalProducts = List.of(
-        productRepository.findById(product1.getId()).orElseThrow(),
-        productRepository.findById(product2.getId()).orElseThrow(),
-        productRepository.findById(product3.getId()).orElseThrow()
+  private User createUserWithPoint(String loginId, String email, Gender gender, Long pointBalance) {
+    User user = userRepository.save(
+        User.of(loginId, email, BIRTH_DATE_1990_01_01, gender, JOINED_AT_2025_10_30)
     );
-
-    assertThat(finalProducts)
-        .extracting("stockValue")
-        .containsExactly(8L, 8L, 8L);
+    pointRepository.save(Point.of(user.getId(), pointBalance));
+    return user;
   }
-}
\ No newline at end of file
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/application/product/ProductFacadeIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/application/product/ProductFacadeIntegrationTest.java
index 8a0c744ec..6bc83b2e2 100644
--- a/apps/commerce-api/src/test/java/com/loopers/application/product/ProductFacadeIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/application/product/ProductFacadeIntegrationTest.java
@@ -1,5 +1,6 @@
 package com.loopers.application.product;
 
+import com.loopers.support.test.IntegrationTestSupport;
 import com.loopers.application.like.LikeFacade;
 import com.loopers.domain.brand.Brand;
 import com.loopers.domain.brand.BrandRepository;
@@ -11,13 +12,10 @@
 import com.loopers.domain.stock.Stock;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
-import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.domain.Pageable;
@@ -30,8 +28,7 @@
 import static org.assertj.core.api.Assertions.tuple;
 import static org.junit.jupiter.api.Assertions.assertAll;
 
-@SpringBootTest
-class ProductFacadeIntegrationTest {
+class ProductFacadeIntegrationTest extends IntegrationTestSupport {
 
   private static final LocalDateTime LIKED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
 
@@ -50,14 +47,6 @@ class ProductFacadeIntegrationTest {
   @Autowired
   private ProductLikeRepository productLikeRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @Nested
   @DisplayName("상품 목록 조회 시")
   class GetProducts {
@@ -246,7 +235,7 @@ void showsCorrectLikeCount_whenMultipleUsersLike() {
       );
 
       for (long userId = 1L; userId <= 3L; userId++) {
-        likeFacade.likeProduct(userId, product.getId());
+        likeFacade.registerProductLike(userId, product.getId());
       }
 
       Pageable pageable = PageRequest.of(0, 20);
@@ -324,7 +313,7 @@ void returnsProductWithBrand() {
           Product.of("상품1", Money.of(10000L), "상세설명", Stock.of(100L), brand.getId())
       );
 
-      ProductDetail result = productFacade.viewProductDetail(product.getId(), null);
+      ProductDetail result = productFacade.retrieveProductDetail(product.getId(), null);
 
       assertThat(result)
           .extracting("productName", "price", "description", "stock", "brandName")
@@ -344,7 +333,7 @@ void returnsIsLikedTrue_whenUserLikesProduct() {
           ProductLike.of(userId, product.getId(), LIKED_AT_2025_10_30)
       );
 
-      ProductDetail result = productFacade.viewProductDetail(product.getId(), userId);
+      ProductDetail result = productFacade.retrieveProductDetail(product.getId(), userId);
 
       assertThat(result.liked()).isTrue();
     }
@@ -359,7 +348,7 @@ void returnsIsLikedFalse_whenUserDoesNotLikeProduct() {
 
       Long userId = 1L;
 
-      ProductDetail result = productFacade.viewProductDetail(product.getId(), userId);
+      ProductDetail result = productFacade.retrieveProductDetail(product.getId(), userId);
 
       assertThat(result.liked()).isFalse();
     }
@@ -370,7 +359,7 @@ void throwsNotFoundException_whenProductDoesNotExist() {
       Long nonExistentProductId = 999L;
 
       assertThatThrownBy(() ->
-          productFacade.viewProductDetail(nonExistentProductId, null)
+          productFacade.retrieveProductDetail(nonExistentProductId, null)
       )
           .isInstanceOf(CoreException.class)
           .extracting("errorType", "message")
@@ -387,7 +376,7 @@ void throwsNotFoundException_whenBrandDoesNotExist() {
       Long productId = product.getId();
 
       assertThatThrownBy(() ->
-          productFacade.viewProductDetail(productId, null)
+          productFacade.retrieveProductDetail(productId, null)
       )
           .isInstanceOf(CoreException.class)
           .extracting("errorType", "message")
@@ -441,15 +430,15 @@ void sortsByLikesDescending() {
       );
 
       for (long userId = 1L; userId <= 5L; userId++) {
-        likeFacade.likeProduct(userId, product1.getId());
+        likeFacade.registerProductLike(userId, product1.getId());
       }
 
       for (long userId = 1L; userId <= 10L; userId++) {
-        likeFacade.likeProduct(userId, product2.getId());
+        likeFacade.registerProductLike(userId, product2.getId());
       }
 
       for (long userId = 1L; userId <= 2L; userId++) {
-        likeFacade.likeProduct(userId, product3.getId());
+        likeFacade.registerProductLike(userId, product3.getId());
       }
 
       Pageable pageable = PageRequest.of(0, 20, Sort.by("likeCount").descending());
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandServiceIntegrationTest.java
index 762446305..c9063dd8e 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandServiceIntegrationTest.java
@@ -2,26 +2,20 @@
 
 import static org.assertj.core.api.Assertions.assertThat;
 
-import com.loopers.utils.DatabaseCleanUp;
+import com.loopers.support.test.IntegrationTestSupport;
 import java.util.List;
 import java.util.Optional;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
 
-@SpringBootTest
-class BrandServiceIntegrationTest {
+class BrandServiceIntegrationTest extends IntegrationTestSupport {
 
   @Autowired
   private BrandRepository brandRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
   private BrandService brandService;
 
   @BeforeEach
@@ -29,11 +23,6 @@ void setUp() {
     brandService = new BrandService(brandRepository);
   }
 
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @Nested
   @DisplayName("브랜드 단건 조회 시")
   class GetById {
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandsTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandsTest.java
deleted file mode 100644
index cbc6a529d..000000000
--- a/apps/commerce-api/src/test/java/com/loopers/domain/brand/BrandsTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.loopers.domain.brand;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-@DisplayName("Brands 일급컬렉션 테스트")
-class BrandsTest {
-
-  @Test
-  @DisplayName("Brand 리스트로 일급컬렉션을 생성한다")
-  void from() {
-    Brand brand1 = Brand.of("브랜드1", "설명1");
-    Brand brand2 = Brand.of("브랜드2", "설명2");
-    List<Brand> brandList = List.of(brand1, brand2);
-
-    Brands brands = Brands.from(brandList);
-
-    assertThat(brands.toList()).hasSize(2);
-  }
-
-  @Test
-  @DisplayName("내부 리스트를 불변 복사본으로 반환한다")
-  void toList() {
-    Brand brand = Brand.of("브랜드1", "설명1");
-    Brands brands = Brands.from(List.of(brand));
-
-    List<Brand> list1 = brands.toList();
-    List<Brand> list2 = brands.toList();
-
-    assertThat(list1)
-        .isNotSameAs(list2)
-        .isEqualTo(list2);
-  }
-}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderCreateServiceTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderCreateServiceTest.java
index e3d9692ed..c7e55b45e 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderCreateServiceTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderCreateServiceTest.java
@@ -1,5 +1,8 @@
 package com.loopers.domain.order;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
 import com.loopers.domain.brand.Brand;
 import com.loopers.domain.brand.BrandRepository;
 import com.loopers.domain.money.Money;
@@ -10,24 +13,17 @@
 import com.loopers.domain.stock.Stock;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
+import com.loopers.support.test.IntegrationTestSupport;
+import java.util.List;
+import java.util.Map;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.List;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-@SpringBootTest
-@Transactional
 @DisplayName("OrderCreateService 통합 테스트")
-class OrderCreateServiceTest {
+class OrderCreateServiceTest extends IntegrationTestSupport {
 
   @Autowired
   private OrderCreateService orderCreateService;
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderRepositoryIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderRepositoryIntegrationTest.java
index ed9a31f84..00b52008c 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderRepositoryIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderRepositoryIntegrationTest.java
@@ -1,44 +1,30 @@
 package com.loopers.domain.order;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import com.loopers.support.test.IntegrationTestSupport;
 import com.loopers.domain.order.orderitem.OrderItem;
 import com.loopers.domain.order.orderitem.OrderPrice;
 import com.loopers.domain.quantity.Quantity;
-import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
+import java.time.LocalDateTime;
+import java.util.Optional;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.data.domain.Page;
 import org.springframework.data.domain.PageRequest;
-import org.springframework.transaction.annotation.Transactional;
 
-import java.time.LocalDateTime;
-import java.util.Optional;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.tuple;
-import static org.junit.jupiter.api.Assertions.assertAll;
-
-@SpringBootTest
-@Transactional
 @DisplayName("OrderRepository 통합 테스트")
-class OrderRepositoryIntegrationTest {
+class OrderRepositoryIntegrationTest extends IntegrationTestSupport {
 
   private static final LocalDateTime ORDERED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
 
   @Autowired
   private OrderRepository orderRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @DisplayName("주문 목록 조회 (@Query + DTO)")
   @Nested
   class FindOrderList {
@@ -53,11 +39,11 @@ void shouldReturnDtoPage() {
       Order order1 = Order.of(userId, OrderStatus.COMPLETED, 30000L, orderedAt);
       order1.addItem(OrderItem.of(100L, "상품1", Quantity.of(1L), OrderPrice.of(10000L)));
       order1.addItem(OrderItem.of(200L, "상품2", Quantity.of(2L), OrderPrice.of(10000L)));
-      orderRepository.save(order1);
+      Order savedOrder1 = orderRepository.save(order1);
 
       Order order2 = Order.of(userId, OrderStatus.PENDING, 50000L, orderedAt.plusDays(1));
       order2.addItem(OrderItem.of(300L, "상품3", Quantity.of(5L), OrderPrice.of(10000L)));
-      orderRepository.save(order2);
+      Order savedOrder2 = orderRepository.save(order2);
 
       PageRequest pageRequest = PageRequest.of(0, 10);
 
@@ -67,10 +53,10 @@ void shouldReturnDtoPage() {
       // then
       assertThat(result.getContent())
           .hasSize(2)
-          .extracting("userId", "status", "itemCount", "totalAmount")
+          .extracting("id", "userId", "status", "itemCount", "totalAmount")
           .containsExactly(
-              tuple(userId, OrderStatus.COMPLETED, 2, 30000L),
-              tuple(userId, OrderStatus.PENDING, 1, 50000L)
+              tuple(savedOrder2.getId(), userId, OrderStatus.PENDING, 1, 50000L),
+              tuple(savedOrder1.getId(), userId, OrderStatus.COMPLETED, 2, 30000L)
           );
     }
 
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceIntegrationTest.java
index b087a7fbb..d5bf6738e 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceIntegrationTest.java
@@ -1,42 +1,30 @@
 package com.loopers.domain.order;
 
+import com.loopers.support.test.IntegrationTestSupport;
 import com.loopers.domain.order.orderitem.OrderItem;
 import com.loopers.domain.order.orderitem.OrderPrice;
 import com.loopers.domain.quantity.Quantity;
-import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
 
 import java.time.LocalDateTime;
 import java.util.List;
 
 import com.loopers.support.error.CoreException;
-import com.loopers.support.error.ErrorType;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-@SpringBootTest
 @DisplayName("OrderService 통합 테스트")
-class OrderServiceIntegrationTest {
+class OrderServiceIntegrationTest extends IntegrationTestSupport {
 
   @Autowired
   private OrderService orderService;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
   private static final LocalDateTime ORDERED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
 
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @Nested
   @DisplayName("주문 생성 및 조회")
   class CreateAndGetOrder {
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java
index 5257226a8..8e261bb09 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/order/OrderServiceTest.java
@@ -1,8 +1,17 @@
 package com.loopers.domain.order;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.BDDMockito.then;
+import static org.mockito.Mockito.times;
+
 import com.loopers.domain.order.orderitem.OrderItem;
 import com.loopers.domain.order.orderitem.OrderPrice;
 import com.loopers.domain.quantity.Quantity;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Optional;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
@@ -13,17 +22,6 @@
 import org.mockito.Mock;
 import org.mockito.junit.jupiter.MockitoExtension;
 
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Optional;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.BDDMockito.given;
-import static org.mockito.BDDMockito.then;
-import static org.mockito.Mockito.times;
-
 @ExtendWith(MockitoExtension.class)
 @DisplayName("OrderService 단위 테스트")
 class OrderServiceTest {
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
index 5e04cd434..f53c71b23 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/point/PointServiceIntegrationTest.java
@@ -1,17 +1,15 @@
 package com.loopers.domain.point;
 
+import com.loopers.support.test.IntegrationTestSupport;
 import com.loopers.domain.user.Gender;
 import com.loopers.domain.user.User;
 import com.loopers.domain.user.UserRepository;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
-import com.loopers.utils.DatabaseCleanUp;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
 
 import java.time.Clock;
 import java.time.Instant;
@@ -21,8 +19,7 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
-@SpringBootTest
-class PointServiceIntegrationTest {
+class PointServiceIntegrationTest extends IntegrationTestSupport {
 
   private static final Clock TEST_CLOCK = Clock.fixed(
       Instant.parse("2025-10-30T00:00:00Z"),
@@ -35,17 +32,9 @@ class PointServiceIntegrationTest {
   @Autowired
   private UserRepository userRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
   @Autowired
   private PointService pointService;
 
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @Nested
   @DisplayName("포인트 조회 시")
   class Get {
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/product/ProductsTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/product/ProductsTest.java
deleted file mode 100644
index 28568d400..000000000
--- a/apps/commerce-api/src/test/java/com/loopers/domain/product/ProductsTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.loopers.domain.product;
-
-import com.loopers.domain.money.Money;
-import com.loopers.domain.stock.Stock;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Test;
-
-import java.util.List;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-@DisplayName("Products 일급컬렉션 테스트")
-class ProductsTest {
-
-  @Test
-  @DisplayName("Product 리스트로 일급컬렉션을 생성한다")
-  void from() {
-    Product product1 = Product.of("상품1", Money.of(1000L), "설명1", Stock.of(10L), 1L);
-    Product product2 = Product.of("상품2", Money.of(2000L), "설명2", Stock.of(20L), 2L);
-    List<Product> productList = List.of(product1, product2);
-
-    Products products = Products.from(productList);
-
-    assertThat(products.toList()).hasSize(2);
-  }
-
-  @Test
-  @DisplayName("브랜드 ID 목록을 중복 제거하여 추출한다")
-  void getBrandIds() {
-    Product product1 = Product.of("상품1", Money.of(1000L), "설명1", Stock.of(10L), 1L);
-    Product product2 = Product.of("상품2", Money.of(2000L), "설명2", Stock.of(20L), 1L);
-    Product product3 = Product.of("상품3", Money.of(3000L), "설명3", Stock.of(30L), 2L);
-    Products products = Products.from(List.of(product1, product2, product3));
-
-    List<Long> brandIds = products.getBrandIds();
-
-    assertThat(brandIds)
-        .hasSize(2)
-        .containsExactlyInAnyOrder(1L, 2L);
-  }
-
-  @Test
-  @DisplayName("내부 리스트를 불변 복사본으로 반환한다")
-  void toList() {
-    Product product = Product.of("상품1", Money.of(1000L), "설명1", Stock.of(10L), 1L);
-    Products products = Products.from(List.of(product));
-
-    List<Product> list1 = products.toList();
-    List<Product> list2 = products.toList();
-
-    assertThat(list1)
-        .isNotSameAs(list2)
-        .isEqualTo(list2);
-  }
-}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/productlike/LikedProductTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/productlike/LikedProductTest.java
new file mode 100644
index 000000000..4c36598c5
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/productlike/LikedProductTest.java
@@ -0,0 +1,87 @@
+package com.loopers.domain.productlike;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.loopers.domain.brand.Brand;
+import com.loopers.domain.money.Money;
+import com.loopers.domain.product.Product;
+import com.loopers.domain.stock.Stock;
+import java.time.LocalDateTime;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+@DisplayName("LikedProduct DTO 테스트")
+class LikedProductTest {
+
+  private static final LocalDateTime LIKED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
+
+  @DisplayName("LikedProduct.of()로 생성할 때")
+  @Nested
+  class Create {
+
+    @Test
+    @DisplayName("ProductLike, Product, Brand를 조합하여 LikedProduct를 생성한다")
+    void shouldCreate_whenAllParametersAreValid() {
+      ProductLike productLike = ProductLike.of(1L, 10L, LIKED_AT_2025_10_30);
+      Product product = Product.of("상품명", Money.of(50000L), "설명", Stock.of(100L), 5L);
+      Brand brand = Brand.of("브랜드명", "브랜드 설명");
+
+      LikedProduct result = LikedProduct.of(productLike, product, brand);
+
+      assertThat(result)
+          .extracting("productId", "productName", "price", "brandId", "brandName", "likedAt")
+          .containsExactly(product.getId(), "상품명", 50000L, brand.getId(), "브랜드명",
+              LIKED_AT_2025_10_30);
+    }
+  }
+
+  @DisplayName("ProductLike 검증")
+  @Nested
+  class ValidateProductLike {
+
+    @Test
+    @DisplayName("ProductLike가 null이면 예외가 발생한다")
+    void shouldThrowException_whenProductLikeIsNull() {
+      Product product = Product.of("상품명", Money.of(50000L), "설명", Stock.of(100L), 5L);
+      Brand brand = Brand.of("브랜드명", "브랜드 설명");
+
+      assertThatThrownBy(() -> LikedProduct.of(null, product, brand))
+          .isInstanceOf(NullPointerException.class)
+          .hasMessage("ProductLike는 null일 수 없습니다.");
+    }
+  }
+
+  @DisplayName("Product 검증")
+  @Nested
+  class ValidateProduct {
+
+    @Test
+    @DisplayName("Product가 null이면 예외가 발생한다")
+    void shouldThrowException_whenProductIsNull() {
+      ProductLike productLike = ProductLike.of(1L, 10L, LIKED_AT_2025_10_30);
+      Brand brand = Brand.of("브랜드명", "브랜드 설명");
+
+      assertThatThrownBy(() -> LikedProduct.of(productLike, null, brand))
+          .isInstanceOf(NullPointerException.class)
+          .hasMessage("Product는 null일 수 없습니다.");
+    }
+  }
+
+  @DisplayName("Brand 검증")
+  @Nested
+  class ValidateBrand {
+
+    @Test
+    @DisplayName("Brand가 null이면 예외가 발생한다")
+    void shouldThrowException_whenBrandIsNull() {
+      ProductLike productLike = ProductLike.of(1L, 10L, LIKED_AT_2025_10_30);
+      Product product = Product.of("상품명", Money.of(50000L), "설명", Stock.of(100L), 5L);
+
+      assertThatThrownBy(() -> LikedProduct.of(productLike, product, null))
+          .isInstanceOf(NullPointerException.class)
+          .hasMessage("Brand는 null일 수 없습니다.");
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikeServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikeServiceIntegrationTest.java
new file mode 100644
index 000000000..2722b3e68
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikeServiceIntegrationTest.java
@@ -0,0 +1,180 @@
+package com.loopers.domain.productlike;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import com.loopers.domain.money.Money;
+import com.loopers.domain.product.Product;
+import com.loopers.domain.product.ProductRepository;
+import com.loopers.domain.stock.Stock;
+import com.loopers.support.error.CoreException;
+import com.loopers.support.test.IntegrationTestSupport;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.domain.Page;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.test.context.bean.override.mockito.MockitoBean;
+
+@DisplayName("ProductLikeService 통합 테스트")
+class ProductLikeServiceIntegrationTest extends IntegrationTestSupport {
+
+  private static final Clock FIXED_CLOCK = Clock.fixed(Instant.parse("2025-10-30T00:00:00Z"), ZoneId.systemDefault());
+  private static final LocalDateTime FIXED_LIKED_AT = LocalDateTime.ofInstant(FIXED_CLOCK.instant(), FIXED_CLOCK.getZone());
+
+  @Autowired
+  private ProductLikeService productLikeService;
+
+  @Autowired
+  private ProductRepository productRepository;
+
+  @Autowired
+  private ProductLikeRepository productLikeRepository;
+
+  @MockitoBean
+  private Clock clock;
+
+  @BeforeEach
+  void setUpClock() {
+    Mockito.when(clock.instant()).thenReturn(FIXED_CLOCK.instant());
+    Mockito.when(clock.getZone()).thenReturn(FIXED_CLOCK.getZone());
+  }
+
+  private Product saveProduct(String name, long price, Long brandId) {
+    return productRepository.save(
+        Product.of(name, Money.of(price), name + " 설명", Stock.of(10L), brandId)
+    );
+  }
+
+  @Nested
+  @DisplayName("좋아요 생성")
+  class CreateLike {
+
+    @Test
+    @DisplayName("유효한 사용자와 상품이면 좋아요가 생성된다.")
+    void createsLike_whenValid() {
+      long brandId = 1L;
+      Product product = saveProduct("상품1", 10000L, brandId);
+
+      productLikeService.createLike(brandId, product.getId());
+
+      List<ProductLike> likes = productLikeRepository.findByUserIdAndProductIdIn(brandId, List.of(brandId));
+      assertThat(likes).hasSize(1)
+          .element(0)
+          .extracting(ProductLike::getUserId, ProductLike::getProductId, ProductLike::getLikedAt)
+          .containsExactly(1L, product.getId(), FIXED_LIKED_AT);
+    }
+
+    @Test
+    @DisplayName("userId가 null이면 예외가 발생한다")
+    void throwsWhenUserIdNull() {
+      assertThatThrownBy(() -> productLikeService.createLike(null, 1L))
+          .isInstanceOf(CoreException.class)
+          .hasMessage("사용자는 비어있을 수 없습니다.");
+    }
+
+    @Test
+    @DisplayName("productId가 null이면 예외가 발생한다")
+    void throwsWhenProductIdNull() {
+      assertThatThrownBy(() -> productLikeService.createLike(1L, null))
+          .isInstanceOf(CoreException.class)
+          .hasMessage("상품은 비어있을 수 없습니다.");
+    }
+  }
+
+  @Nested
+  @DisplayName("좋아요 상태 조회")
+  class FindLikeStatus {
+
+    @Test
+    @DisplayName("사용자가 특정 상품을 좋아요 했으면 true를 반환한다")
+    void returnsTrueWhenLiked() {
+      long brandId = 1L;
+      Product product = saveProduct("상품1", 10000L, brandId);
+      productLikeRepository.save(ProductLike.of(1L, product.getId(), FIXED_LIKED_AT));
+
+      boolean result = productLikeService.isLiked(1L, product.getId());
+
+      assertThat(result).isTrue();
+    }
+
+    @Test
+    @DisplayName("좋아요하지 않았다면 false를 반환한다")
+    void returnsFalseWhenNotLiked() {
+      long brandId = 1L;
+      Product product = saveProduct("상품1", 10000L, brandId);
+
+      boolean result = productLikeService.isLiked(1L, product.getId());
+
+      assertThat(result).isFalse();
+    }
+  }
+
+  @Nested
+  @DisplayName("좋아요 목록 조회")
+  class FindLikedProducts {
+
+    @Test
+    @DisplayName("사용자가 좋아요한 상품 목록을 LATEST 순으로 조회한다")
+    void returnsLikedProducts_sortedByLatest() {
+      long brandId = 1L;
+      Product product1 = saveProduct("상품1", 10000L, brandId);
+      Product product2 = saveProduct("상품2", 20000L, brandId);
+      productLikeRepository.save(ProductLike.of(1L, product1.getId(), FIXED_LIKED_AT));
+      productLikeRepository.save(
+          ProductLike.of(1L, product2.getId(), FIXED_LIKED_AT.plusSeconds(10)));
+
+      Page<LikedProduct> result = productLikeService.findLikedProducts(1L, LikeSortType.LATEST,
+          PageRequest.of(0, 10));
+
+      assertThat(result.getContent())
+          .hasSize(2)
+          .extracting(LikedProduct::productId, LikedProduct::likedAt)
+          .containsExactly(
+              org.assertj.core.groups.Tuple.tuple(product2.getId(),
+                  FIXED_LIKED_AT.plusSeconds(10)),
+              org.assertj.core.groups.Tuple.tuple(product1.getId(), FIXED_LIKED_AT)
+          );
+    }
+
+    @Test
+    @DisplayName("좋아요한 상품이 없으면 빈 페이지를 반환한다")
+    void returnsEmptyPage_whenNoLikes() {
+      Page<LikedProduct> result = productLikeService.findLikedProducts(1L, LikeSortType.LATEST,
+          PageRequest.of(0, 10));
+
+      assertThat(result.getContent()).isEmpty();
+      assertThat(result.getTotalElements()).isZero();
+    }
+
+    @Test
+    @DisplayName("페이지네이션이 동작한다")
+    void supportsPagination() {
+      long brandId = 1L;
+      for (int i = 1; i <= 5; i++) {
+        Product product = saveProduct("상품" + i, 10000L * i, brandId);
+        productLikeRepository.save(
+            ProductLike.of(1L, product.getId(), FIXED_LIKED_AT.plusSeconds(i)));
+      }
+
+      Page<LikedProduct> firstPage = productLikeService.findLikedProducts(1L,
+          LikeSortType.LATEST, PageRequest.of(0, 2));
+      Page<LikedProduct> secondPage = productLikeService.findLikedProducts(1L,
+          LikeSortType.LATEST, PageRequest.of(1, 2));
+
+      assertThat(firstPage.getContent()).hasSize(2);
+      assertThat(firstPage.getTotalElements()).isEqualTo(5);
+      assertThat(secondPage.getContent()).hasSize(2);
+      assertThat(secondPage.getTotalElements()).isEqualTo(5);
+    }
+  }
+
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikeTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikeTest.java
deleted file mode 100644
index c86d0a653..000000000
--- a/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikeTest.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package com.loopers.domain.productlike;
-
-import com.loopers.support.error.CoreException;
-import com.loopers.support.error.ErrorType;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-@DisplayName("ProductLike 도메인 테스트")
-class ProductLikeTest {
-
-  private static final LocalDateTime LIKED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
-
-  @DisplayName("ProductLike를 생성할 때")
-  @Nested
-  class Create {
-
-    @DisplayName("사용자 ID, 상품 ID, 좋아요 시각을 모두 제공하면 ProductLike가 생성된다")
-    @Test
-    void shouldCreate_whenValid() {
-      Long userId = 1L;
-      Long productId = 100L;
-
-      ProductLike productLike = ProductLike.of(userId, productId, LIKED_AT_2025_10_30);
-
-      assertThat(productLike).extracting("userId", "productId", "likedAt")
-          .containsExactly(userId, productId, LIKED_AT_2025_10_30);
-    }
-  }
-
-  @DisplayName("userId 검증")
-  @Nested
-  class ValidateUserId {
-
-    @DisplayName("null이면 예외가 발생한다")
-    @Test
-    void shouldThrowException_whenNull() {
-      Long productId = 100L;
-
-      assertThatThrownBy(() -> ProductLike.of(null, productId, LIKED_AT_2025_10_30))
-          .isInstanceOf(CoreException.class)
-          .hasMessage("사용자는 비어있을 수 없습니다.")
-          .extracting("errorType").isEqualTo(ErrorType.INVALID_PRODUCT_LIKE_USER_EMPTY);
-    }
-  }
-
-  @DisplayName("productId 검증")
-  @Nested
-  class ValidateProductId {
-
-    @DisplayName("null이면 예외가 발생한다")
-    @Test
-    void shouldThrowException_whenNull() {
-      Long userId = 1L;
-
-      assertThatThrownBy(() -> ProductLike.of(userId, null, LIKED_AT_2025_10_30))
-          .isInstanceOf(CoreException.class)
-          .hasMessage("상품은 비어있을 수 없습니다.")
-          .extracting("errorType").isEqualTo(ErrorType.INVALID_PRODUCT_LIKE_PRODUCT_EMPTY);
-    }
-  }
-
-  @DisplayName("likedAt 검증")
-  @Nested
-  class ValidateLikedAt {
-
-    @DisplayName("null이면 예외가 발생한다")
-    @Test
-    void shouldThrowException_whenNull() {
-      Long userId = 1L;
-      Long productId = 100L;
-
-      assertThatThrownBy(() -> ProductLike.of(userId, productId, null))
-          .isInstanceOf(CoreException.class)
-          .hasMessage("좋아요 일시는 비어있을 수 없습니다.")
-          .extracting("errorType").isEqualTo(ErrorType.INVALID_PRODUCT_LIKE_LIKED_AT_EMPTY);
-    }
-  }
-
-  @DisplayName("비즈니스 로직")
-  @Nested
-  class BusinessLogic {
-
-    @DisplayName("해당 사용자의 좋아요이면 true를 반환한다")
-    @Test
-    void shouldReturnTrue_whenLikedByUser() {
-      Long userId = 1L;
-      Long productId = 100L;
-      ProductLike productLike = ProductLike.of(userId, productId, LIKED_AT_2025_10_30);
-
-      boolean result = productLike.isLikedBy(userId);
-
-      assertThat(result).isTrue();
-    }
-
-    @DisplayName("다른 사용자의 좋아요이면 false를 반환한다")
-    @Test
-    void shouldReturnFalse_whenNotLikedByUser() {
-      Long userId = 1L;
-      Long otherUserId = 2L;
-      Long productId = 100L;
-      ProductLike productLike = ProductLike.of(userId, productId, LIKED_AT_2025_10_30);
-
-      boolean result = productLike.isLikedBy(otherUserId);
-
-      assertThat(result).isFalse();
-    }
-  }
-}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikesTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikesTest.java
deleted file mode 100644
index d26e59003..000000000
--- a/apps/commerce-api/src/test/java/com/loopers/domain/productlike/ProductLikesTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-package com.loopers.domain.productlike;
-
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-
-import java.time.LocalDateTime;
-import java.util.List;
-import java.util.Map;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-@DisplayName("ProductLikes 일급 컬렉션 테스트")
-class ProductLikesTest {
-
-  private static final LocalDateTime LIKED_AT_2025_10_30 = LocalDateTime.of(2025, 10, 30, 0, 0, 0);
-
-  @Nested
-  @DisplayName("생성")
-  class Create {
-
-    @Test
-    @DisplayName("빈 리스트로 생성 성공")
-    void from_emptyList() {
-      ProductLikes productLikes = ProductLikes.from(List.of());
-
-      assertThat(productLikes).isNotNull();
-    }
-
-    @Test
-    @DisplayName("여러 개의 ProductLike로 생성 성공")
-    void from_multipleProductLikes() {
-      List<ProductLike> likes = List.of(
-          ProductLike.of(1L, 100L, LIKED_AT_2025_10_30),
-          ProductLike.of(1L, 200L, LIKED_AT_2025_10_30)
-      );
-
-      ProductLikes productLikes = ProductLikes.from(likes);
-
-      assertThat(productLikes).isNotNull();
-    }
-
-    @Test
-    @DisplayName("null 리스트로 생성 시 IllegalArgumentException 발생")
-    void from_nullList() {
-      assertThatThrownBy(() -> ProductLikes.from(null))
-          .isInstanceOf(IllegalArgumentException.class)
-          .hasMessage("productLikes cannot be null");
-    }
-
-    @Test
-    @DisplayName("empty() 정적 팩토리 메서드로 빈 컬렉션 생성")
-    void empty() {
-      ProductLikes productLikes = ProductLikes.empty();
-
-      assertThat(productLikes).isNotNull();
-    }
-  }
-
-  @Nested
-  @DisplayName("toLikedByProductId 변환")
-  class ToLikedByProductId {
-
-    @Test
-    @DisplayName("빈 리스트를 빈 Map으로 변환")
-    void toLikedByProductId_empty() {
-      ProductLikes productLikes = ProductLikes.empty();
-
-      Map<Long, Boolean> result = productLikes.toLikedByProductId();
-
-      assertThat(result).isEmpty();
-    }
-
-    @Test
-    @DisplayName("ProductLike 리스트를 좋아요 Map으로 변환 (모두 true)")
-    void toLikedByProductId_success() {
-      List<ProductLike> likes = List.of(
-          ProductLike.of(1L, 100L, LIKED_AT_2025_10_30),
-          ProductLike.of(1L, 200L, LIKED_AT_2025_10_30),
-          ProductLike.of(1L, 300L, LIKED_AT_2025_10_30)
-      );
-      ProductLikes productLikes = ProductLikes.from(likes);
-
-      Map<Long, Boolean> result = productLikes.toLikedByProductId();
-
-      assertThat(result)
-          .hasSize(3)
-          .containsEntry(100L, true)
-          .containsEntry(200L, true)
-          .containsEntry(300L, true);
-    }
-  }
-
-  @Nested
-  @DisplayName("toLikeStatusByProductId 변환")
-  class ToLikeStatusByProductId {
-
-    @Test
-    @DisplayName("null productIds로 호출 시 빈 Map 반환")
-    void toLikeStatusByProductId_nullProductIds() {
-      ProductLikes productLikes = ProductLikes.empty();
-
-      Map<Long, Boolean> result = productLikes.toLikeStatusByProductId(null);
-
-      assertThat(result).isEmpty();
-    }
-
-    @Test
-    @DisplayName("빈 productIds로 호출 시 빈 Map 반환")
-    void toLikeStatusByProductId_emptyProductIds() {
-      ProductLikes productLikes = ProductLikes.from(List.of(
-          ProductLike.of(1L, 100L, LIKED_AT_2025_10_30)
-      ));
-
-      Map<Long, Boolean> result = productLikes.toLikeStatusByProductId(List.of());
-
-      assertThat(result).isEmpty();
-    }
-
-    @Test
-    @DisplayName("좋아요한 상품과 안 한 상품 혼합 상태로 변환")
-    void toLikeStatusByProductId_mixed() {
-      List<ProductLike> likes = List.of(
-          ProductLike.of(1L, 100L, LIKED_AT_2025_10_30),
-          ProductLike.of(1L, 300L, LIKED_AT_2025_10_30)
-      );
-      ProductLikes productLikes = ProductLikes.from(likes);
-      List<Long> allProductIds = List.of(100L, 200L, 300L, 400L);
-
-      Map<Long, Boolean> result = productLikes.toLikeStatusByProductId(allProductIds);
-
-      assertThat(result)
-          .hasSize(4)
-          .containsEntry(100L, true)
-          .containsEntry(200L, false)
-          .containsEntry(300L, true)
-          .containsEntry(400L, false);
-    }
-
-    @Test
-    @DisplayName("모든 상품을 좋아요하지 않은 경우")
-    void toLikeStatusByProductId_allNotLiked() {
-      ProductLikes productLikes = ProductLikes.empty();
-      List<Long> allProductIds = List.of(100L, 200L, 300L);
-
-      Map<Long, Boolean> result = productLikes.toLikeStatusByProductId(allProductIds);
-
-      assertThat(result)
-          .hasSize(3)
-          .containsEntry(100L, false)
-          .containsEntry(200L, false)
-          .containsEntry(300L, false);
-    }
-
-    @Test
-    @DisplayName("모든 상품을 좋아요한 경우")
-    void toLikeStatusByProductId_allLiked() {
-      List<ProductLike> likes = List.of(
-          ProductLike.of(1L, 100L, LIKED_AT_2025_10_30),
-          ProductLike.of(1L, 200L, LIKED_AT_2025_10_30),
-          ProductLike.of(1L, 300L, LIKED_AT_2025_10_30)
-      );
-      ProductLikes productLikes = ProductLikes.from(likes);
-      List<Long> allProductIds = List.of(100L, 200L, 300L);
-
-      Map<Long, Boolean> result = productLikes.toLikeStatusByProductId(allProductIds);
-
-      assertThat(result)
-          .hasSize(3)
-          .containsEntry(100L, true)
-          .containsEntry(200L, true)
-          .containsEntry(300L, true);
-    }
-
-    @Test
-    @DisplayName("좋아요 목록에 없는 상품 ID 조회 시 false 반환")
-    void toLikeStatusByProductId_productNotInList() {
-      List<ProductLike> likes = List.of(
-          ProductLike.of(1L, 100L, LIKED_AT_2025_10_30)
-      );
-      ProductLikes productLikes = ProductLikes.from(likes);
-      List<Long> allProductIds = List.of(100L, 999L);
-
-      Map<Long, Boolean> result = productLikes.toLikeStatusByProductId(allProductIds);
-
-      assertThat(result)
-          .hasSize(2)
-          .containsEntry(100L, true)
-          .containsEntry(999L, false);
-    }
-  }
-}
diff --git a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
index 404b4bc13..86c211fde 100644
--- a/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/domain/user/UserServiceIntegrationTest.java
@@ -9,28 +9,23 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import com.loopers.support.test.IntegrationTestSupport;
 import com.loopers.support.error.CoreException;
 import com.loopers.support.error.ErrorType;
-import com.loopers.utils.DatabaseCleanUp;
 import java.time.Clock;
 import java.time.LocalDate;
-import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
 
-@SpringBootTest
-class UserServiceIntegrationTest {
+@DisplayName("UserService 통합 테스트")
+class UserServiceIntegrationTest extends IntegrationTestSupport {
 
   @Autowired
   private UserRepository userRepository;
 
-  @Autowired
-  private DatabaseCleanUp databaseCleanUp;
-
   private UserService userService;
 
   @BeforeEach
@@ -38,11 +33,6 @@ void setUp() {
     userService = new UserService(userRepository, Clock.systemDefaultZone());
   }
 
-  @AfterEach
-  void tearDown() {
-    databaseCleanUp.truncateAllTables();
-  }
-
   @Nested
   @DisplayName("회원가입 시")
   class RegisterUser {
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/brand/BrandApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/brand/BrandApiE2ETest.java
new file mode 100644
index 000000000..dd8fb853f
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/brand/BrandApiE2ETest.java
@@ -0,0 +1,92 @@
+package com.loopers.interfaces.api.brand;
+
+import com.loopers.domain.brand.Brand;
+import com.loopers.infrastructure.brand.BrandJpaRepository;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.brand.BrandDto.BrandViewResponse;
+import com.loopers.utils.DatabaseCleanUp;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class BrandApiE2ETest {
+
+  private static final ParameterizedTypeReference<ApiResponse<BrandViewResponse>> BRAND_RESPONSE_TYPE = new ParameterizedTypeReference<>() {
+  };
+
+  private final TestRestTemplate testRestTemplate;
+  private final BrandJpaRepository brandJpaRepository;
+  private final DatabaseCleanUp databaseCleanUp;
+
+  @Autowired
+  public BrandApiE2ETest(
+      TestRestTemplate testRestTemplate,
+      BrandJpaRepository brandJpaRepository,
+      DatabaseCleanUp databaseCleanUp)
+   {
+    this.testRestTemplate = testRestTemplate;
+    this.brandJpaRepository = brandJpaRepository;
+    this.databaseCleanUp = databaseCleanUp;
+  }
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @DisplayName("GET /api/v1/brands/{brandId}")
+  @Nested
+  class GetBrand {
+
+    @DisplayName("브랜드가 존재하면 브랜드 정보를 반환한다")
+    @Test
+    void returnsBrandInfo_whenBrandExists() {
+      Brand brand = Brand.of("나이키", "스포츠 브랜드");
+      Brand savedBrand = brandJpaRepository.save(brand);
+
+      ResponseEntity<ApiResponse<BrandViewResponse>> response =
+          testRestTemplate.exchange(
+              "/api/v1/brands/" + savedBrand.getId(),
+              HttpMethod.GET,
+              null,
+              BRAND_RESPONSE_TYPE
+          );
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(response.getBody()).isNotNull(),
+          () -> assertThat(response.getBody().data())
+              .extracting("brandId", "name", "description")
+              .containsExactly(savedBrand.getId(), "나이키", "스포츠 브랜드")
+      );
+    }
+
+    @DisplayName("브랜드가 존재하지 않으면 404를 반환한다")
+    @Test
+    void returnsNotFound_whenBrandDoesNotExist() {
+      long nonExistentBrandId = 999L;
+
+      ResponseEntity<ApiResponse<BrandViewResponse>> response =
+          testRestTemplate.exchange(
+              "/api/v1/brands/" + nonExistentBrandId,
+              HttpMethod.GET,
+              null,
+              BRAND_RESPONSE_TYPE
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/like/LikeApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/like/LikeApiE2ETest.java
new file mode 100644
index 000000000..0d4e116e2
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/like/LikeApiE2ETest.java
@@ -0,0 +1,327 @@
+package com.loopers.interfaces.api.like;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import com.loopers.domain.brand.Brand;
+import com.loopers.domain.money.Money;
+import com.loopers.domain.product.Product;
+import com.loopers.domain.productlike.ProductLike;
+import com.loopers.domain.stock.Stock;
+import com.loopers.infrastructure.brand.BrandJpaRepository;
+import com.loopers.infrastructure.product.ProductJpaRepository;
+import com.loopers.infrastructure.productlike.ProductLikeJpaRepository;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.like.LikeDto.LikedProductListResponse;
+import com.loopers.interfaces.api.like.LikeDto.LikedProductResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import com.loopers.utils.DatabaseCleanUp;
+import java.time.LocalDateTime;
+import java.util.List;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class LikeApiE2ETest {
+
+  private static final String BASE_URL = "/api/v1/like/products";
+
+  private final TestRestTemplate restTemplate;
+  private final BrandJpaRepository brandJpaRepository;
+  private final ProductJpaRepository productJpaRepository;
+  private final ProductLikeJpaRepository productLikeJpaRepository;
+  private final DatabaseCleanUp databaseCleanUp;
+
+  @Autowired
+  LikeApiE2ETest(
+      TestRestTemplate restTemplate,
+      BrandJpaRepository brandJpaRepository,
+      ProductJpaRepository productJpaRepository,
+      ProductLikeJpaRepository productLikeJpaRepository,
+      DatabaseCleanUp databaseCleanUp
+  ) {
+    this.restTemplate = restTemplate;
+    this.brandJpaRepository = brandJpaRepository;
+    this.productJpaRepository = productJpaRepository;
+    this.productLikeJpaRepository = productLikeJpaRepository;
+    this.databaseCleanUp = databaseCleanUp;
+  }
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @Nested
+  @DisplayName("POST /api/v1/like/products/{productId}")
+  class LikeProduct {
+
+    @Test
+    @DisplayName("상품 좋아요 등록에 성공하면 200과 함께 좋아요 수가 증가한다")
+    void likesProductSuccessfully() {
+      Brand brand = brandJpaRepository.save(Brand.of("브랜드A", "설명"));
+      Product product = productJpaRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명", Stock.of(10L), brand.getId())
+      );
+
+      ResponseEntity<ApiResponse<Object>> response =
+          restTemplate.exchange(
+              BASE_URL + "/" + product.getId(),
+              HttpMethod.POST,
+              new HttpEntity<>(null, userHeaders(1L)),
+              new ParameterizedTypeReference<>() {
+              }
+          );
+
+      Product updated = productJpaRepository.findById(product.getId()).orElseThrow();
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(productLikeJpaRepository.existsByUserIdAndProductId(1L, product.getId())).isTrue(),
+          () -> assertThat(updated.getLikeCount()).isEqualTo(1L)
+      );
+    }
+
+    @Test
+    @DisplayName("같은 사용자가 두 번 좋아요 요청해도 1개만 반영된다")
+    void ensuresIdempotencyOnDuplicateRequests() {
+      Brand brand = brandJpaRepository.save(Brand.of("브랜드A", "설명"));
+      Product product = productJpaRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명", Stock.of(10L), brand.getId())
+      );
+
+      HttpEntity<Void> request = new HttpEntity<>(null, userHeaders(1L));
+      ParameterizedTypeReference<ApiResponse<Object>> responseType = new ParameterizedTypeReference<>() {
+      };
+
+      restTemplate.exchange(BASE_URL + "/" + product.getId(), HttpMethod.POST, request, responseType);
+      restTemplate.exchange(BASE_URL + "/" + product.getId(), HttpMethod.POST, request, responseType);
+
+      Product updated = productJpaRepository.findById(product.getId()).orElseThrow();
+      assertThat(updated.getLikeCount()).isEqualTo(1L);
+    }
+
+    @Test
+    @DisplayName("존재하지 않는 상품이면 404를 반환한다")
+    void returnsNotFoundWhenProductMissing() {
+      ResponseEntity<ApiResponse<Object>> response =
+          restTemplate.exchange(
+              BASE_URL + "/999999",
+              HttpMethod.POST,
+              new HttpEntity<>(null, userHeaders(1L)),
+              new ParameterizedTypeReference<>() {
+              }
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+  }
+
+  @Nested
+  @DisplayName("DELETE /api/v1/like/products/{productId}")
+  class UnlikeProduct {
+
+    @Test
+    @DisplayName("좋아요 취소에 성공하면 좋아요 수가 감소한다")
+    void unlikesProductSuccessfully() {
+      Brand brand = brandJpaRepository.save(Brand.of("브랜드A", "설명"));
+      Product product = productJpaRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명", Stock.of(10L), brand.getId())
+      );
+
+      HttpEntity<Void> request = new HttpEntity<>(null, userHeaders(1L));
+      ParameterizedTypeReference<ApiResponse<Object>> responseType = new ParameterizedTypeReference<>() {
+      };
+      restTemplate.exchange(BASE_URL + "/" + product.getId(), HttpMethod.POST, request, responseType);
+
+      ResponseEntity<ApiResponse<Object>> response = restTemplate.exchange(
+          BASE_URL + "/" + product.getId(),
+          HttpMethod.DELETE,
+          request,
+          responseType
+      );
+
+      Product updated = productJpaRepository.findById(product.getId()).orElseThrow();
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(productLikeJpaRepository.existsByUserIdAndProductId(1L, product.getId())).isFalse(),
+          () -> assertThat(updated.getLikeCount()).isZero()
+      );
+    }
+
+    @Test
+    @DisplayName("이미 취소된 좋아요를 다시 취소해도 200 응답한다")
+    void ensuresIdempotentUnlikes() {
+      Brand brand = brandJpaRepository.save(Brand.of("브랜드A", "설명"));
+      Product product = productJpaRepository.save(
+          Product.of("상품1", Money.of(10000L), "설명", Stock.of(10L), brand.getId())
+      );
+
+      HttpEntity<Void> request = new HttpEntity<>(null, userHeaders(1L));
+      ParameterizedTypeReference<ApiResponse<Object>> responseType = new ParameterizedTypeReference<>() {
+      };
+
+      restTemplate.exchange(BASE_URL + "/" + product.getId(), HttpMethod.POST, request, responseType);
+      restTemplate.exchange(BASE_URL + "/" + product.getId(), HttpMethod.DELETE, request, responseType);
+      ResponseEntity<ApiResponse<Object>> response = restTemplate.exchange(
+          BASE_URL + "/" + product.getId(),
+          HttpMethod.DELETE,
+          request,
+          responseType
+      );
+
+      Product updated = productJpaRepository.findById(product.getId()).orElseThrow();
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(productLikeJpaRepository.existsByUserIdAndProductId(1L, product.getId())).isFalse(),
+          () -> assertThat(updated.getLikeCount()).isZero()
+      );
+    }
+  }
+
+  @Nested
+  @DisplayName("GET /api/v1/like/products")
+  class GetLikedProducts {
+
+    @Test
+    @DisplayName("기본 정렬(latest)로 좋아요 상품 목록을 반환한다")
+    void returnsLikedProductsSortedByLatest() {
+      Long userId = 99L;
+      Brand brand = brandJpaRepository.save(Brand.of("나이키", "스포츠 브랜드"));
+      Product first = saveProductWithLikes("에어포스", 12000L, brand.getId(), 3L);
+      Product second = saveProductWithLikes("조던", 10000L, brand.getId(), 2L);
+      Product third = saveProductWithLikes("덩크", 9000L, brand.getId(), 1L);
+
+      productLikeJpaRepository.save(ProductLike.of(userId, first.getId(), LocalDateTime.of(2025, 1, 3, 0, 0)));
+      productLikeJpaRepository.save(ProductLike.of(userId, second.getId(), LocalDateTime.of(2025, 1, 2, 0, 0)));
+      productLikeJpaRepository.save(ProductLike.of(userId, third.getId(), LocalDateTime.of(2025, 1, 1, 0, 0)));
+
+      ResponseEntity<ApiResponse<LikedProductListResponse>> response =
+          restTemplate.exchange(
+              BASE_URL,
+              HttpMethod.GET,
+              new HttpEntity<>(null, userHeaders(userId)),
+              new ParameterizedTypeReference<>() {
+              }
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+      List<LikedProductResponse> products = response.getBody().data().products();
+      assertThat(products)
+          .extracting(LikedProductResponse::productName)
+          .containsExactly("에어포스", "조던", "덩크");
+    }
+
+    @Test
+    @DisplayName("sort=product_name이면 상품명을 기준으로 정렬된다")
+    void returnsProductsSortedByName() {
+      Long userId = 77L;
+      Brand brand = brandJpaRepository.save(Brand.of("나이키", "스포츠 브랜드"));
+      Product first = saveProductWithLikes("조던", 20000L, brand.getId(), 5L);
+      Product second = saveProductWithLikes("덩크", 15000L, brand.getId(), 6L);
+      Product third = saveProductWithLikes("에어맥스", 5000L, brand.getId(), 2L);
+
+      productLikeJpaRepository.save(ProductLike.of(userId, first.getId(), LocalDateTime.now()));
+      productLikeJpaRepository.save(ProductLike.of(userId, second.getId(), LocalDateTime.now()));
+      productLikeJpaRepository.save(ProductLike.of(userId, third.getId(), LocalDateTime.now()));
+
+      ResponseEntity<ApiResponse<LikedProductListResponse>> response =
+          restTemplate.exchange(
+              BASE_URL + "?sort=product_name",
+              HttpMethod.GET,
+              new HttpEntity<>(null, userHeaders(userId)),
+              new ParameterizedTypeReference<>() {
+              }
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+      List<LikedProductResponse> products = response.getBody().data().products();
+      assertThat(products)
+          .extracting(LikedProductResponse::productName)
+          .containsExactly("덩크", "에어맥스", "조던");
+    }
+
+    @Test
+    @DisplayName("sort=price_desc이면 가격이 높은 순으로 정렬된다")
+    void returnsProductsSortedByPriceDesc() {
+      Long userId = 55L;
+      Brand brand = brandJpaRepository.save(Brand.of("나이키", "스포츠 브랜드"));
+      Product cheap = saveProductWithLikes("페가수스", 10000L, brand.getId(), 1L);
+      Product mid = saveProductWithLikes("코르테즈", 50000L, brand.getId(), 1L);
+      Product expensive = saveProductWithLikes("에어조던", 100000L, brand.getId(), 1L);
+
+      productLikeJpaRepository.save(ProductLike.of(userId, cheap.getId(), LocalDateTime.now()));
+      productLikeJpaRepository.save(ProductLike.of(userId, mid.getId(), LocalDateTime.now()));
+      productLikeJpaRepository.save(ProductLike.of(userId, expensive.getId(), LocalDateTime.now()));
+
+      ResponseEntity<ApiResponse<LikedProductListResponse>> response =
+          restTemplate.exchange(
+              BASE_URL + "?sort=price_desc",
+              HttpMethod.GET,
+              new HttpEntity<>(null, userHeaders(userId)),
+              new ParameterizedTypeReference<>() {
+              }
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+      List<LikedProductResponse> products = response.getBody().data().products();
+      assertThat(products)
+          .extracting(LikedProductResponse::productName)
+          .containsExactly("에어조던", "코르테즈", "페가수스");
+    }
+
+    @Test
+    @DisplayName("sort=price_asc이면 가격이 낮은 순으로 정렬된다")
+    void returnsProductsSortedByPriceAsc() {
+      Long userId = 66L;
+      Brand brand = brandJpaRepository.save(Brand.of("나이키", "스포츠 브랜드"));
+      Product cheap = saveProductWithLikes("페가수스", 10000L, brand.getId(), 1L);
+      Product mid = saveProductWithLikes("코르테즈", 50000L, brand.getId(), 1L);
+      Product expensive = saveProductWithLikes("에어조던", 100000L, brand.getId(), 1L);
+
+      productLikeJpaRepository.save(ProductLike.of(userId, cheap.getId(), LocalDateTime.now()));
+      productLikeJpaRepository.save(ProductLike.of(userId, mid.getId(), LocalDateTime.now()));
+      productLikeJpaRepository.save(ProductLike.of(userId, expensive.getId(), LocalDateTime.now()));
+
+      ResponseEntity<ApiResponse<LikedProductListResponse>> response =
+          restTemplate.exchange(
+              BASE_URL + "?sort=price_asc",
+              HttpMethod.GET,
+              new HttpEntity<>(null, userHeaders(userId)),
+              new ParameterizedTypeReference<>() {
+              }
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
+      List<LikedProductResponse> products = response.getBody().data().products();
+      assertThat(products)
+          .extracting(LikedProductResponse::productName)
+          .containsExactly("페가수스", "코르테즈", "에어조던");
+    }
+  }
+
+  private Product saveProductWithLikes(String name, long price, Long brandId, long likeCount) {
+    Product product = Product.of(name, Money.of(price), name + " 설명", Stock.of(10L), brandId);
+    if (likeCount > 0) {
+      product.increaseLikeCount((int) likeCount);
+    }
+    return productJpaRepository.save(product);
+  }
+
+  private HttpHeaders userHeaders(Long userId) {
+    HttpHeaders headers = new HttpHeaders();
+    headers.set(ApiHeaders.USER_ID, String.valueOf(userId));
+    return headers;
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/order/OrderApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/order/OrderApiE2ETest.java
new file mode 100644
index 000000000..b8dc5698e
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/order/OrderApiE2ETest.java
@@ -0,0 +1,330 @@
+package com.loopers.interfaces.api.order;
+
+import com.loopers.domain.brand.Brand;
+import com.loopers.domain.money.Money;
+import com.loopers.domain.order.Order;
+import com.loopers.domain.order.OrderStatus;
+import com.loopers.domain.order.orderitem.OrderItem;
+import com.loopers.domain.order.orderitem.OrderPrice;
+import com.loopers.domain.quantity.Quantity;
+import com.loopers.domain.point.Point;
+import com.loopers.domain.point.PointAmount;
+import com.loopers.domain.product.Product;
+import com.loopers.domain.stock.Stock;
+import com.loopers.domain.user.Gender;
+import com.loopers.domain.user.User;
+import com.loopers.infrastructure.brand.BrandJpaRepository;
+import com.loopers.infrastructure.order.OrderJpaRepository;
+import com.loopers.infrastructure.point.PointJpaRepository;
+import com.loopers.infrastructure.product.ProductJpaRepository;
+import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.order.OrderDto.OrderDetailResponse;
+import com.loopers.interfaces.api.order.OrderDto.OrderListResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import com.loopers.utils.DatabaseCleanUp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class OrderApiE2ETest {
+
+  private static final String BASE_URL = "/api/v1/orders";
+  private static final LocalDate BIRTH_DATE_1990_01_01 = LocalDate.of(1990, 1, 1);
+  private static final LocalDateTime FIRST_ORDER_AT_2025_10_30_10_00 = LocalDateTime.of(2025, 10, 30, 10, 0, 0);
+  private static final LocalDateTime SECOND_ORDER_AT_2025_10_30_11_00 = LocalDateTime.of(2025, 10, 30, 11, 0, 0);
+  private static final ParameterizedTypeReference<ApiResponse<OrderListResponse>> ORDER_LIST_RESPONSE_TYPE =
+      new ParameterizedTypeReference<>() {
+      };
+  private static final ParameterizedTypeReference<ApiResponse<OrderDetailResponse>> ORDER_DETAIL_RESPONSE_TYPE =
+      new ParameterizedTypeReference<>() {
+      };
+
+  private final TestRestTemplate testRestTemplate;
+  private final OrderJpaRepository orderJpaRepository;
+  private final UserJpaRepository userJpaRepository;
+  private final BrandJpaRepository brandJpaRepository;
+  private final ProductJpaRepository productJpaRepository;
+  private final DatabaseCleanUp databaseCleanUp;
+
+  @Autowired
+  OrderApiE2ETest(
+      TestRestTemplate testRestTemplate,
+      OrderJpaRepository orderJpaRepository,
+      UserJpaRepository userJpaRepository,
+      BrandJpaRepository brandJpaRepository,
+      ProductJpaRepository productJpaRepository,
+      DatabaseCleanUp databaseCleanUp
+  ) {
+    this.testRestTemplate = testRestTemplate;
+    this.orderJpaRepository = orderJpaRepository;
+    this.userJpaRepository = userJpaRepository;
+    this.brandJpaRepository = brandJpaRepository;
+    this.productJpaRepository = productJpaRepository;
+    this.databaseCleanUp = databaseCleanUp;
+  }
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @Nested
+  @DisplayName("GET /api/v1/orders")
+  class GetOrders {
+
+    @Test
+    @DisplayName("사용자의 주문 목록과 페이징 정보를 반환한다")
+    void returnsOrdersWithPagination_whenValidUserId() {
+      User user = saveUser("user123", "user@example.com");
+      Brand brand = saveBrand("나이키");
+      Product product = saveProduct("운동화", 30000L, 50L, brand.getId());
+
+      Order first = Order.of(user.getId(), OrderStatus.PENDING, 30000L, FIRST_ORDER_AT_2025_10_30_10_00);
+      addOrderItem(first, product.getId(), "운동화", 1L, 30000L);
+      Order savedFirst = orderJpaRepository.saveAndFlush(first);
+
+      Order second = Order.of(user.getId(), OrderStatus.COMPLETED, 50000L, SECOND_ORDER_AT_2025_10_30_11_00);
+      addOrderItem(second, product.getId(), "운동화", 2L, 25000L);
+      Order savedSecond = orderJpaRepository.saveAndFlush(second);
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, user.getId().toString());
+
+      ResponseEntity<ApiResponse<OrderListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("page", 0)
+                  .queryParam("size", 20)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              new HttpEntity<>(headers),
+              ORDER_LIST_RESPONSE_TYPE
+          );
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(response.getBody()).isNotNull(),
+          () -> assertThat(response.getBody().data().orders())
+              .hasSize(2)
+              .element(0).extracting("orderId", "totalAmount", "status", "itemCount")
+              .containsExactly(savedSecond.getId(), 50000L, OrderStatus.COMPLETED, 1),
+          () -> assertThat(response.getBody().data().orders())
+              .element(1).extracting("orderId", "totalAmount", "status", "itemCount")
+              .containsExactly(savedFirst.getId(), 30000L, OrderStatus.PENDING, 1)
+      );
+    }
+
+    @Test
+    @DisplayName("페이지네이션이 올바르게 동작한다")
+    void returnsPaginatedOrders_whenPageSizeIsSet() {
+      User user = saveUser("user123", "user@example.com");
+      Brand brand = saveBrand("나이키");
+      Product product = saveProduct("운동화", 30000L, 50L, brand.getId());
+
+      for (int i = 0; i < 3; i++) {
+        Order order = Order.of(user.getId(), OrderStatus.PENDING, 30000L, FIRST_ORDER_AT_2025_10_30_10_00);
+        addOrderItem(order, product.getId(), "운동화", 1L, 30000L);
+        orderJpaRepository.saveAndFlush(order);
+      }
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, user.getId().toString());
+
+      ResponseEntity<ApiResponse<OrderListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("page", 0)
+                  .queryParam("size", 2)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              new HttpEntity<>(headers),
+              ORDER_LIST_RESPONSE_TYPE
+          );
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(response.getBody()).isNotNull(),
+          () -> assertThat(response.getBody().data().orders()).hasSize(2),
+          () -> assertThat(response.getBody().data().pageInfo())
+              .extracting("totalElements", "currentPage", "totalPages", "pageSize")
+              .containsExactly(3L, 0, 2, 2)
+      );
+    }
+
+    @Test
+    @DisplayName("다른 사용자의 주문은 조회되지 않는다")
+    void returnsOnlyOwnOrders_whenMultipleUsersExist() {
+      User user1 = saveUser("user1", "user1@example.com");
+      User user2 = saveUser("user2", "user2@example.com");
+      Brand brand = saveBrand("나이키");
+      Product product = saveProduct("운동화", 30000L, 50L, brand.getId());
+
+      Order order1 = Order.of(user1.getId(), OrderStatus.PENDING, 30000L, FIRST_ORDER_AT_2025_10_30_10_00);
+      addOrderItem(order1, product.getId(), "운동화", 1L, 30000L);
+      Order savedOrder1 = orderJpaRepository.saveAndFlush(order1);
+
+      Order order2 = Order.of(user2.getId(), OrderStatus.PENDING, 50000L, FIRST_ORDER_AT_2025_10_30_10_00);
+      addOrderItem(order2, product.getId(), "운동화", 2L, 25000L);
+      orderJpaRepository.saveAndFlush(order2);
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, user1.getId().toString());
+
+      ResponseEntity<ApiResponse<OrderListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("page", 0)
+                  .queryParam("size", 20)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              new HttpEntity<>(headers),
+              ORDER_LIST_RESPONSE_TYPE
+          );
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(response.getBody()).isNotNull(),
+          () -> assertThat(response.getBody().data().orders())
+              .hasSize(1)
+              .element(0).extracting("orderId")
+              .isEqualTo(savedOrder1.getId())
+      );
+    }
+  }
+
+  @Nested
+  @DisplayName("GET /api/v1/orders/{orderId}")
+  class GetOrderDetail {
+
+    @Test
+    @DisplayName("주문 상세 정보를 반환한다")
+    void returnsOrderDetail_whenValidOrderId() {
+      User user = saveUser("user123", "user@example.com");
+      Brand brand = saveBrand("나이키");
+      Product product1 = saveProduct("운동화", 30000L, 50L, brand.getId());
+      Product product2 = saveProduct("슬리퍼", 20000L, 30L, brand.getId());
+
+      Order order = Order.of(user.getId(), OrderStatus.PENDING, 50000L, FIRST_ORDER_AT_2025_10_30_10_00);
+      addOrderItem(order, product1.getId(), "운동화", 1L, 30000L);
+      addOrderItem(order, product2.getId(), "슬리퍼", 1L, 20000L);
+      Order savedOrder = orderJpaRepository.saveAndFlush(order);
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, user.getId().toString());
+
+      ResponseEntity<ApiResponse<OrderDetailResponse>> response =
+          testRestTemplate.exchange(
+              BASE_URL + "/" + savedOrder.getId(),
+              HttpMethod.GET,
+              new HttpEntity<>(headers),
+              ORDER_DETAIL_RESPONSE_TYPE
+          );
+
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(response.getBody()).isNotNull(),
+          () -> assertThat(response.getBody().data())
+              .extracting("orderId", "status", "totalAmount")
+              .containsExactly(savedOrder.getId(), OrderStatus.PENDING, 50000L),
+          () -> assertThat(response.getBody().data().items())
+              .hasSize(2)
+              .extracting("productId", "productName", "quantity", "price")
+              .containsExactlyInAnyOrder(
+                  tuple(product1.getId(), "운동화", 1L, 30000L),
+                  tuple(product2.getId(), "슬리퍼", 1L, 20000L)
+              )
+      );
+    }
+
+    @Test
+    @DisplayName("타인의 주문은 조회할 수 없다 (403 Forbidden)")
+    void returnsForbidden_whenAccessingOthersOrder() {
+      User user1 = saveUser("user1", "user1@example.com");
+      User user2 = saveUser("user2", "user2@example.com");
+      Brand brand = saveBrand("나이키");
+      Product product = saveProduct("운동화", 30000L, 50L, brand.getId());
+
+      Order order = Order.of(user1.getId(), OrderStatus.PENDING, 30000L, FIRST_ORDER_AT_2025_10_30_10_00);
+      addOrderItem(order, product.getId(), "운동화", 1L, 30000L);
+      Order savedOrder = orderJpaRepository.saveAndFlush(order);
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, user2.getId().toString());
+
+      ResponseEntity<ApiResponse<OrderDetailResponse>> response =
+          testRestTemplate.exchange(
+              BASE_URL + "/" + savedOrder.getId(),
+              HttpMethod.GET,
+              new HttpEntity<>(headers),
+              ORDER_DETAIL_RESPONSE_TYPE
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
+    }
+
+    @Test
+    @DisplayName("존재하지 않는 주문은 404 Not Found를 반환한다")
+    void returnsNotFound_whenOrderNotExists() {
+      User user = saveUser("user123", "user@example.com");
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, user.getId().toString());
+
+      ResponseEntity<ApiResponse<OrderDetailResponse>> response =
+          testRestTemplate.exchange(
+              BASE_URL + "/999999",
+              HttpMethod.GET,
+              new HttpEntity<>(headers),
+              ORDER_DETAIL_RESPONSE_TYPE
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+  }
+
+  private User saveUser(String loginId, String email) {
+    LocalDate currentDate = LocalDate.of(2025, 10, 30);
+    User user = User.of(loginId, email, BIRTH_DATE_1990_01_01, Gender.MALE, currentDate);
+    return userJpaRepository.save(user);
+  }
+
+  private Brand saveBrand(String name) {
+    Brand brand = Brand.of(name, name + " 브랜드");
+    return brandJpaRepository.save(brand);
+  }
+
+  private Product saveProduct(String name, Long price, Long stock, Long brandId) {
+    Product product = Product.of(name, Money.of(price), name + " 설명", Stock.of(stock), brandId);
+    return productJpaRepository.save(product);
+  }
+
+  private void addOrderItem(Order order, Long productId, String productName, Long quantity,
+      Long price) {
+    OrderItem item = OrderItem.of(productId, productName, Quantity.of(quantity),
+        OrderPrice.of(price));
+    order.addItem(item);
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/point/PointApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/point/PointApiE2ETest.java
index 054eae9b6..981fe26ab 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/point/PointApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/point/PointApiE2ETest.java
@@ -6,6 +6,7 @@
 import com.loopers.interfaces.api.point.PointDto.ChargeRequest;
 import com.loopers.interfaces.api.point.PointDto.ChargeResponse;
 import com.loopers.interfaces.api.point.PointDto.PointResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
 import com.loopers.utils.DatabaseCleanUp;
 import com.loopers.infrastructure.point.PointJpaRepository;
 import com.loopers.infrastructure.user.UserJpaRepository;
@@ -70,7 +71,7 @@ void tearDown() {
   class GetPoint {
 
     @Test
-    @DisplayName("`X-USER-ID` 헤더가 없을 경우, `400 Bad Request` 응답을 반환한다.")
+    @DisplayName("`X-USER-ID` 헤더가 없으면 `400 Bad Request` 응답을 반환한다")
     void returnBadRequestException_whenUserIdHeaderIsMissing() {
       ParameterizedTypeReference<ApiResponse<PointResponse>> responseType =
           new ParameterizedTypeReference<>() {
@@ -90,7 +91,7 @@ void returnBadRequestException_whenUserIdHeaderIsMissing() {
     }
 
     @Test
-    @DisplayName("포인트 조회에 성공할 경우, 보유 포인트를 응답으로 반환한다.")
+    @DisplayName("포인트 조회에 성공하면 보유 포인트를 응답으로 반환한다")
     void returnsPoint_whenPointExists() {
       //given
       String loginId = "testuser";
@@ -106,7 +107,7 @@ void returnsPoint_whenPointExists() {
 
       // when
       HttpHeaders httpHeaders = new HttpHeaders();
-      httpHeaders.set("X-USER-ID", String.valueOf(savedUser.getId()));
+      httpHeaders.set(ApiHeaders.USER_ID, String.valueOf(savedUser.getId()));
 
       ParameterizedTypeReference<ApiResponse<PointResponse>> responseType =
           new ParameterizedTypeReference<>() {
@@ -130,13 +131,13 @@ void returnsPoint_whenPointExists() {
 
 
     @Test
-    @DisplayName("해당 ID의 회원이 존재하지 않을 경우, null을 반환한다.")
+    @DisplayName("해당 ID의 회원이 존재하지 않으면 null을 반환한다")
     void returnsNull_whenUserDoesNotExists() {
       //given
       Long userId = 999999L;
       // when
       HttpHeaders httpHeaders = new HttpHeaders();
-      httpHeaders.set("X-USER-ID", String.valueOf(userId));
+      httpHeaders.set(ApiHeaders.USER_ID, String.valueOf(userId));
 
       ParameterizedTypeReference<ApiResponse<PointResponse>> responseType =
           new ParameterizedTypeReference<>() {
@@ -164,7 +165,7 @@ void returnsNull_whenUserDoesNotExists() {
   class ChargePoint {
 
     @Test
-    @DisplayName("존재하는 유저가 1000원을 충전할 경우, 충전된 보유 총량을 응답으로 반환한다")
+    @DisplayName("존재하는 유저가 1000원을 충전하면 충전된 보유 총량을 응답으로 반환한다")
     void returnsTotalBalance_whenChargingThousand() {
       // given
       String loginId = "testuser";
@@ -182,7 +183,7 @@ void returnsTotalBalance_whenChargingThousand() {
 
       // when
       HttpHeaders httpHeaders = new HttpHeaders();
-      httpHeaders.set("X-USER-ID", String.valueOf(savedUser.getId()));
+      httpHeaders.set(ApiHeaders.USER_ID, String.valueOf(savedUser.getId()));
 
       ParameterizedTypeReference<ApiResponse<ChargeResponse>> responseType =
           new ParameterizedTypeReference<>() {
@@ -205,7 +206,7 @@ void returnsTotalBalance_whenChargingThousand() {
     }
 
     @Test
-    @DisplayName("존재하지 않는 유저로 요청할 경우, 404 NOT FOUND 응답을 반환한다.")
+    @DisplayName("존재하지 않는 유저로 요청하면 404 NOT FOUND 응답을 반환한다")
     void returnsNotFoundException_whenUserDoesNotExists() {
       // given
       Long userId = 999999L;
@@ -213,7 +214,7 @@ void returnsNotFoundException_whenUserDoesNotExists() {
 
       // when
       HttpHeaders httpHeaders = new HttpHeaders();
-      httpHeaders.set("X-USER-ID", String.valueOf(userId));
+      httpHeaders.set(ApiHeaders.USER_ID, String.valueOf(userId));
 
       ParameterizedTypeReference<ApiResponse<ChargeResponse>> responseType =
           new ParameterizedTypeReference<>() {
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/product/ProductApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/product/ProductApiE2ETest.java
new file mode 100644
index 000000000..1188afe6d
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/product/ProductApiE2ETest.java
@@ -0,0 +1,311 @@
+package com.loopers.interfaces.api.product;
+
+import com.loopers.domain.brand.Brand;
+import com.loopers.domain.money.Money;
+import com.loopers.domain.product.Product;
+import com.loopers.domain.productlike.ProductLike;
+import com.loopers.domain.stock.Stock;
+import com.loopers.domain.user.Gender;
+import com.loopers.domain.user.User;
+import com.loopers.infrastructure.brand.BrandJpaRepository;
+import com.loopers.infrastructure.product.ProductJpaRepository;
+import com.loopers.infrastructure.productlike.ProductLikeJpaRepository;
+import com.loopers.infrastructure.user.UserJpaRepository;
+import com.loopers.interfaces.api.ApiResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductListItemResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductListResponse;
+import com.loopers.interfaces.api.product.ProductDto.ProductResponse;
+import com.loopers.interfaces.api.support.ApiHeaders;
+import com.loopers.utils.DatabaseCleanUp;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.util.List;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+class ProductApiE2ETest {
+
+  private static final String BASE_URL = "/api/v1/products";
+  private static final LocalDate FIXED_DATE = LocalDate.of(2024, 1, 1);
+  private static final LocalDateTime FIXED_DATE_TIME = LocalDateTime.of(2024, 1, 1, 0, 0);
+  private static final ParameterizedTypeReference<ApiResponse<ProductListResponse>> PRODUCT_LIST_RESPONSE_TYPE =
+      new ParameterizedTypeReference<>() {
+      };
+  private static final ParameterizedTypeReference<ApiResponse<ProductResponse>> PRODUCT_RESPONSE_TYPE =
+      new ParameterizedTypeReference<>() {
+      };
+
+  private final TestRestTemplate testRestTemplate;
+  private final ProductJpaRepository productJpaRepository;
+  private final BrandJpaRepository brandJpaRepository;
+  private final UserJpaRepository userJpaRepository;
+  private final ProductLikeJpaRepository productLikeJpaRepository;
+  private final DatabaseCleanUp databaseCleanUp;
+
+  @Autowired
+  ProductApiE2ETest(
+      TestRestTemplate testRestTemplate,
+      ProductJpaRepository productJpaRepository,
+      BrandJpaRepository brandJpaRepository,
+      UserJpaRepository userJpaRepository,
+      ProductLikeJpaRepository productLikeJpaRepository,
+      DatabaseCleanUp databaseCleanUp
+  ) {
+    this.testRestTemplate = testRestTemplate;
+    this.productJpaRepository = productJpaRepository;
+    this.brandJpaRepository = brandJpaRepository;
+    this.userJpaRepository = userJpaRepository;
+    this.productLikeJpaRepository = productLikeJpaRepository;
+    this.databaseCleanUp = databaseCleanUp;
+  }
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+
+  @Nested
+  @DisplayName("GET /api/v1/products")
+  class GetProducts {
+
+    @Test
+    @DisplayName("기본 정렬(latest)로 상품 목록과 페이징 정보를 반환한다")
+    void returnsProductsSortedByLatest_whenSortIsDefault() {
+      Brand brand = saveBrand("나이키");
+      Product first = saveProduct("슬리퍼", 12000L, 10L, brand.getId(), 0);
+      Product second = saveProduct("샌들", 15000L, 5L, brand.getId(), 0);
+
+      ResponseEntity<ApiResponse<ProductListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("page", 0)
+                  .queryParam("size", 20)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              HttpEntity.EMPTY,
+              PRODUCT_LIST_RESPONSE_TYPE
+          );
+
+      ApiResponse<ProductListResponse> responseBody = response.getBody();
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(responseBody).isNotNull(),
+          () -> {
+            ProductListResponse data = responseBody.data();
+            assertThat(data).isNotNull();
+            List<ProductListItemResponse> products = data.products();
+            assertThat(products)
+                .hasSize(2)
+                .element(0).extracting(ProductListItemResponse::productId)
+                .isEqualTo(second.getId());
+            assertThat(products)
+                .element(1).extracting(ProductListItemResponse::productId)
+                .isEqualTo(first.getId());
+          }
+      );
+    }
+
+    @Test
+    @DisplayName("브랜드 필터 + sort=price_asc 조합으로 좋아요 상태를 포함해 반환한다")
+    void returnsFilteredProductsWithLikeStatus_whenBrandAndPriceSortProvided() {
+      Brand nike = saveBrand("나이키");
+      Brand adidas = saveBrand("아디다스");
+      Product nikeProduct = saveProduct("에어맥스", 200000L, 50L, nike.getId(), 5);
+      saveProduct("울트라부스트", 150000L, 20L, adidas.getId(), 0);
+      User user = saveUser("tester1");
+      productLikeJpaRepository.save(ProductLike.of(user.getId(), nikeProduct.getId(), FIXED_DATE_TIME));
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, String.valueOf(user.getId()));
+
+      ResponseEntity<ApiResponse<ProductListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("brandId", nike.getId())
+                  .queryParam("sort", "price_asc")
+                  .queryParam("page", 0)
+                  .queryParam("size", 10)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              new HttpEntity<>(null, headers),
+              PRODUCT_LIST_RESPONSE_TYPE
+          );
+
+      ApiResponse<ProductListResponse> responseBody = response.getBody();
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(responseBody).isNotNull(),
+          () -> {
+            ProductListResponse data = responseBody.data();
+            assertThat(data).isNotNull();
+            List<ProductListItemResponse> products = data.products();
+            assertThat(products)
+                .hasSize(1)
+                .element(0)
+                .extracting("productId", "likeCount", "isLiked")
+                .containsExactly(nikeProduct.getId(), 5L, true);
+          }
+      );
+    }
+
+    @Test
+    @DisplayName("sort=price_asc이면 가격 오름차순으로 정렬한다")
+    void returnsProductsSortedByPriceAscending_whenSortIsPriceAsc() {
+      Brand brand = saveBrand("가격브랜드");
+      Product cheap = saveProduct("슬림핏", 10000L, 10L, brand.getId(), 0);
+      Product pricey = saveProduct("프리미엄", 50000L, 8L, brand.getId(), 0);
+
+      ResponseEntity<ApiResponse<ProductListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("sort", "price_asc")
+                  .queryParam("size", 10)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              HttpEntity.EMPTY,
+              PRODUCT_LIST_RESPONSE_TYPE
+          );
+
+      ApiResponse<ProductListResponse> responseBody = response.getBody();
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(responseBody).isNotNull(),
+          () -> {
+            ProductListResponse data = responseBody.data();
+            assertThat(data).isNotNull();
+            List<ProductListItemResponse> products = data.products();
+            assertThat(products).extracting(ProductListItemResponse::productId)
+                .containsExactly(cheap.getId(), pricey.getId());
+          }
+      );
+    }
+
+    @Test
+    @DisplayName("sort=likes_desc이면 좋아요 내림차순으로 정렬한다")
+    void returnsProductsSortedByLikesDescending_whenSortIsLikesDesc() {
+      Brand brand = saveBrand("좋아요브랜드");
+      Product popular = saveProduct("인기상품", 20000L, 10L, brand.getId(), 10);
+      Product normal = saveProduct("일반상품", 18000L, 10L, brand.getId(), 2);
+
+      ResponseEntity<ApiResponse<ProductListResponse>> response =
+          testRestTemplate.exchange(
+              UriComponentsBuilder.fromUriString(BASE_URL)
+                  .queryParam("sort", "likes_desc")
+                  .queryParam("size", 10)
+                  .build()
+                  .toUri(),
+              HttpMethod.GET,
+              HttpEntity.EMPTY,
+              PRODUCT_LIST_RESPONSE_TYPE
+          );
+
+      ApiResponse<ProductListResponse> responseBody = response.getBody();
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> assertThat(responseBody).isNotNull(),
+          () -> {
+            ProductListResponse data = responseBody.data();
+            assertThat(data).isNotNull();
+            List<ProductListItemResponse> products = data.products();
+            assertThat(products).extracting(ProductListItemResponse::productId)
+                .containsExactly(popular.getId(), normal.getId());
+          }
+      );
+    }
+  }
+
+  @Nested
+  @DisplayName("GET /api/v1/products/{productId}")
+  class GetProductDetail {
+
+    @Test
+    @DisplayName("존재하는 상품이면 상세 정보를 반환한다")
+    void returnsProductDetail_whenProductExists() {
+      Brand brand = saveBrand("루프");
+      Product product = saveProduct("루프 티셔츠", 35000L, 30L, brand.getId(), 2);
+      User user = saveUser("tester2");
+      productLikeJpaRepository.save(ProductLike.of(user.getId(), product.getId(), FIXED_DATE_TIME));
+
+      HttpHeaders headers = new HttpHeaders();
+      headers.set(ApiHeaders.USER_ID, String.valueOf(user.getId()));
+
+      ResponseEntity<ApiResponse<ProductResponse>> response =
+          testRestTemplate.exchange(
+              BASE_URL + "/" + product.getId(),
+              HttpMethod.GET,
+              new HttpEntity<>(null, headers),
+              PRODUCT_RESPONSE_TYPE
+          );
+
+      assertAll(
+          () -> assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK),
+          () -> {
+            ApiResponse<ProductResponse> responseBody = response.getBody();
+            assertThat(responseBody).isNotNull();
+            ProductResponse productResponse = responseBody.data();
+            assertThat(productResponse).isNotNull();
+            assertThat(productResponse)
+                .extracting(
+                    ProductResponse::productId,
+                    ProductResponse::name,
+                    productBrand -> productBrand.brand().brandId(),
+                    ProductResponse::isLiked,
+                    ProductResponse::likeCount
+                )
+                .containsExactly(product.getId(), "루프 티셔츠", brand.getId(), true, 2L);
+          }
+      );
+    }
+
+    @Test
+    @DisplayName("상품이 존재하지 않으면 404를 반환한다")
+    void returnsNotFound_whenProductDoesNotExist() {
+      ResponseEntity<ApiResponse<ProductResponse>> response =
+          testRestTemplate.exchange(
+              BASE_URL + "/999999",
+              HttpMethod.GET,
+              HttpEntity.EMPTY,
+              PRODUCT_RESPONSE_TYPE
+          );
+
+      assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
+    }
+  }
+
+  private Brand saveBrand(String name) {
+    return brandJpaRepository.save(Brand.of(name, name + " 설명"));
+  }
+
+  private Product saveProduct(String name, long price, long stock, Long brandId, long likeCount) {
+    Product product = Product.of(name, Money.of(price), name + " 상세", Stock.of(stock), brandId);
+    if (likeCount > 0) {
+      product.increaseLikeCount((int) likeCount);
+    }
+    return productJpaRepository.save(product);
+  }
+
+  private User saveUser(String loginId) {
+    return userJpaRepository.save(User.of(loginId, loginId + "@loopers.dev", FIXED_DATE.minusYears(20), Gender.MALE, FIXED_DATE));
+  }
+}
diff --git a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/user/UserApiE2ETest.java b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/user/UserApiE2ETest.java
index a5daba7a8..108a5adc8 100644
--- a/apps/commerce-api/src/test/java/com/loopers/interfaces/api/user/UserApiE2ETest.java
+++ b/apps/commerce-api/src/test/java/com/loopers/interfaces/api/user/UserApiE2ETest.java
@@ -52,7 +52,7 @@ void tearDown() {
   @Nested
   class Register {
 
-    @DisplayName("회원 가입이 성공할 경우, 생성된 유저 정보를 응답으로 반환한다.")
+    @DisplayName("회원 가입에 성공하면 생성된 유저 정보를 응답으로 반환한다")
     @Test
     void returnsCreatedUserInfo_whenRegistrationIsSuccessful() {
       // given
@@ -77,7 +77,7 @@ void returnsCreatedUserInfo_whenRegistrationIsSuccessful() {
       );
     }
 
-    @DisplayName("회원 가입 시에 성별이 없을 경우, 400 Bad Request 응답을 반환한다.")
+    @DisplayName("성별이 없으면 400 Bad Request 응답을 반환한다")
     @Test
     void throwsBadRequest_whenGenderIsMissing() {
       // given
@@ -99,7 +99,7 @@ void throwsBadRequest_whenGenderIsMissing() {
   @Nested
   class GetUser {
 
-    @DisplayName("내 정보 조회에 성공할 경우, 해당하는 유저 정보를 응답으로 반환한다.")
+    @DisplayName("내 정보 조회에 성공하면 해당 유저 정보를 응답으로 반환한다")
     @Test
     void returnsUserInfo_whenUserExists() {
       // given
@@ -126,7 +126,7 @@ void returnsUserInfo_whenUserExists() {
       );
     }
 
-    @DisplayName("존재하지 않는 ID로 조회할 경우, 404 Not Found 응답을 반환한다.")
+    @DisplayName("존재하지 않는 ID로 조회하면 404 Not Found 응답을 반환한다")
     @Test
     void returnsNotFound_whenUserDoesNotExist() {
       // given
diff --git a/apps/commerce-api/src/test/java/com/loopers/support/test/IntegrationTestSupport.java b/apps/commerce-api/src/test/java/com/loopers/support/test/IntegrationTestSupport.java
new file mode 100644
index 000000000..6612ef70d
--- /dev/null
+++ b/apps/commerce-api/src/test/java/com/loopers/support/test/IntegrationTestSupport.java
@@ -0,0 +1,18 @@
+package com.loopers.support.test;
+
+import com.loopers.utils.DatabaseCleanUp;
+import org.junit.jupiter.api.AfterEach;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+public abstract class IntegrationTestSupport {
+
+  @Autowired
+  private DatabaseCleanUp databaseCleanUp;
+
+  @AfterEach
+  void tearDown() {
+    databaseCleanUp.truncateAllTables();
+  }
+}
