Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8557d64
feat: PG λͺ¨λ“ˆ μΆ”κ°€ (#24)
minor7295 Dec 4, 2025
178a328
Feature/pg client (#27)
minor7295 Dec 4, 2025
69c895c
test: payment 도메인 λ‹¨μœ„ ν…ŒμŠ€νŠΈ μΆ”κ°€
minor7295 Dec 7, 2025
db0aa8f
feat: payment 도메인 κ΅¬ν˜„
minor7295 Dec 7, 2025
9f55c9b
test: PaymentService 도메인 μ„œλΉ„μŠ€ ν…ŒμŠ€νŠΈ μ½”λ“œ μΆ”κ°€
minor7295 Dec 7, 2025
d3ac90b
feat: PaymentService 도메인 μ„œλΉ„μŠ€ κ΅¬ν˜„
minor7295 Dec 7, 2025
74ac713
refactor: payment λ„λ©”μΈμ˜ μ±…μž„μ΄μ§€λ§Œ order 도메인에 있던 λ‘œμ§λ“€ 이동
minor7295 Dec 7, 2025
b11c85e
test: Order 도메인 μ„œλΉ„μŠ€ 둜직 ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±
minor7295 Dec 7, 2025
a513f1e
refactor: Order λ„λ©”μΈμ˜ 도메인 μ„œλΉ„μŠ€ 둜직 μž¬κ΅¬μ„±
minor7295 Dec 7, 2025
0eba13a
refactor: purchasing facadeμ—μ„œ μ²˜λ¦¬ν•˜κ³  있던 λ‚΄μš© 쀑 도메인 λ ˆμ΄μ–΄λ‘œ μœ„μž„ν•  수 μžˆλŠ” λ‚΄μš©λ“€ 재배치
minor7295 Dec 7, 2025
8060272
refactor: DIP 원칙에 맞좰 PG λ‘œμ§μ—μ„œ infra λ ˆμ΄μ–΄κ°€ domain λ ˆμ΄μ–΄λ₯Ό μ˜μ‘΄ν•˜λ„λ‘ μž¬κ΅¬μ„±
minor7295 Dec 7, 2025
a8205fb
refactor: payment κ΄€λ ¨ μŠ€μΌ€μ€„λ§ λ‘œμ§λ“€μ€ infra λ ˆμ΄μ–΄λ‘œ 이동
minor7295 Dec 7, 2025
818e0c2
refactor: PurchasingFacadeκ°€ repositoryλ₯Ό 직접 μ‚¬μš©ν•˜μ§€ μ•Šλ„λ‘ 도메인 μ„œλΉ„μŠ€ 둜직 μž¬κ΅¬μ„±
minor7295 Dec 7, 2025
5e2b04c
refactor: PuhrchasingFacadeκ°€ 도메인 μ„œλΉ„μŠ€λ₯Ό μ‘°ν•©ν•΄μ„œ μ‚¬μš©ν•˜λŠ” μ—­ν• λ§Œ λ‹΄λ‹Ήν•˜λ„λ‘ μž¬κ΅¬μ„±
minor7295 Dec 7, 2025
f6358e0
refactor: μž¬κ΅¬μ„±ν•œ 도메인 μ„œλΉ„μŠ€ λ‘œμ§μ— 맞좰 ν…ŒμŠ€νŠΈ μ½”λ“œ μž¬κ΅¬μ„±
minor7295 Dec 7, 2025
d9ebd8e
refactor: μ£Όλ¬Έ κ²°μ œμ‹œ 포인트 λ˜λŠ” μΉ΄λ“œλ§Œμ„ μ‚¬μš©ν•˜μ—¬ 결제 ν•  수 μžˆλ„λ‘ μˆ˜μ •
minor7295 Dec 7, 2025
92fd483
refactor: 포인트 λ˜λŠ” μΉ΄λ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ κ²°μ œν•  수 μžˆλ„λ‘ ν…ŒμŠ€νŠΈ μ½”λ“œ μž¬κ΅¬μ„±
minor7295 Dec 7, 2025
6665994
refactor: λ‹€λ₯Έ applicationλ ˆμ΄μ–΄μ™€ λ™μΌν•˜κ²Œ command의 μœ„μΉ˜λ₯Ό domainμ—μ„œ application으둜 이동
minor7295 Dec 8, 2025
fd1155d
refactor: Order도메인 μ„œλΉ„μŠ€μ— λŒ€ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œ 쀑 application λ ˆμ΄μ–΄μ— 더 μ ν•©ν•œ λΆ€λΆ„ 뢄리
minor7295 Dec 8, 2025
c725d49
refactor: Order 도메인 μ„œλΉ„μŠ€μ— 있던 λ‚΄μš© 쀑 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ μ„œλΉ„μŠ€μ— ν•΄λ‹Ήν•˜λŠ” 뢀뢄듀은 application λ ˆμ΄β€¦
minor7295 Dec 8, 2025
a527a91
refactor: infrastructure λ ˆμ΄μ–΄μ— domainλ ˆμ΄μ–΄μ™€ 달리 payment와 paymentgatewayκ°€ …
minor7295 Dec 8, 2025
73678c8
chore: μ€‘λ³΅λ˜λŠ” 둜그 정리, λΆˆν•„μš”ν•˜κ²Œ 높은 level인 λ‘œκ·ΈλŠ” debug 둜그둜 μ „ν™˜
minor7295 Dec 8, 2025
1b25d0c
Merge branch 'base-pr-round7' into feature/refactor-purchasing
minor7295 Dec 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.loopers.application.purchasing;

import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;

/**
* 결제 μš”μ²­ λͺ…λ Ή.
* <p>
* PG 결제 μš”μ²­μ— ν•„μš”ν•œ 정보λ₯Ό λ‹΄λŠ” λͺ…λ Ή λͺ¨λΈμž…λ‹ˆλ‹€.
* </p>
*
* @author Loopers
* @version 1.0
*/
public record PaymentRequestCommand(
String userId,
Long orderId,
String cardType,
String cardNo,
Long amount,
String callbackUrl
) {
public PaymentRequestCommand {
if (userId == null || userId.isBlank()) {
throw new CoreException(ErrorType.BAD_REQUEST, "userIdλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.");
}
if (orderId == null) {
throw new CoreException(ErrorType.BAD_REQUEST, "orderIdλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.");
}
if (cardType == null || cardType.isBlank()) {
throw new CoreException(ErrorType.BAD_REQUEST, "cardType은 ν•„μˆ˜μž…λ‹ˆλ‹€.");
}
if (cardNo == null || cardNo.isBlank()) {
throw new CoreException(ErrorType.BAD_REQUEST, "cardNoλŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.");
}
if (amount == null || amount <= 0) {
throw new CoreException(ErrorType.BAD_REQUEST, "amountλŠ” 0보닀 컀야 ν•©λ‹ˆλ‹€.");
}
if (callbackUrl == null || callbackUrl.isBlank()) {
throw new CoreException(ErrorType.BAD_REQUEST, "callbackUrl은 ν•„μˆ˜μž…λ‹ˆλ‹€.");
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ public RetryRegistry retryRegistry() {
// Exponential Backoff μ μš©ν•˜μ—¬ μΌμ‹œμ  였λ₯˜ μžλ™ 볡ꡬ
retryRegistry.addConfiguration("paymentGatewaySchedulerClient", retryConfig);

log.info("Resilience4j Retry μ„€μ • μ™„λ£Œ:");
log.info(" - 결제 μš”μ²­ API (paymentGatewayClient): Retry μ—†μŒ (μœ μ € μš”μ²­ 경둜 - λΉ λ₯Έ μ‹€νŒ¨)");
log.info(" - 쑰회 API (paymentGatewaySchedulerClient): Exponential Backoff 적용 (μŠ€μΌ€μ€„λŸ¬ - 비동기/배치 기반 Retry)");
log.debug("Resilience4j Retry μ„€μ • μ™„λ£Œ:");
log.debug(" - 결제 μš”μ²­ API (paymentGatewayClient): Retry μ—†μŒ (μœ μ € μš”μ²­ 경둜 - λΉ λ₯Έ μ‹€νŒ¨)");
log.debug(" - 쑰회 API (paymentGatewaySchedulerClient): Exponential Backoff 적용 (μŠ€μΌ€μ€„λŸ¬ - 비동기/배치 기반 Retry)");

return retryRegistry;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.loopers.domain.coupon;

import com.loopers.domain.coupon.discount.CouponDiscountStrategyFactory;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
* 쿠폰 도메인 μ„œλΉ„μŠ€.
* <p>
* 쿠폰 쑰회, μ‚¬μš© λ“±μ˜ 도메인 λ‘œμ§μ„ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
* Repository에 μ˜μ‘΄ν•˜λ©° λΉ„μ¦ˆλ‹ˆμŠ€ κ·œμΉ™μ„ μΊ‘μŠν™”ν•©λ‹ˆλ‹€.
* </p>
*
* @author Loopers
* @version 1.0
*/
@RequiredArgsConstructor
@Component
public class CouponService {
private final CouponRepository couponRepository;
private final UserCouponRepository userCouponRepository;
private final CouponDiscountStrategyFactory couponDiscountStrategyFactory;

/**
* 쿠폰을 μ μš©ν•˜μ—¬ 할인 κΈˆμ•‘μ„ κ³„μ‚°ν•˜κ³  쿠폰을 μ‚¬μš© μ²˜λ¦¬ν•©λ‹ˆλ‹€.
* <p>
* <b>λ™μ‹œμ„± μ œμ–΄ μ „λž΅:</b>
* <ul>
* <li><b>OPTIMISTIC_LOCK μ‚¬μš© κ·Όκ±°:</b> 쿠폰 쀑볡 μ‚¬μš© λ°©μ§€, Hot Spot λŒ€μ‘</li>
* <li><b>@Version ν•„λ“œ:</b> UserCoupon μ—”ν‹°ν‹°μ˜ version ν•„λ“œλ₯Ό 톡해 μžλ™μœΌλ‘œ 낙관적 락 적용</li>
* <li><b>λ™μ‹œ μ‚¬μš© μ‹œ:</b> ν•œ λͺ…λ§Œ μ„±κ³΅ν•˜κ³  λ‚˜λ¨Έμ§€λŠ” OptimisticLockException λ°œμƒ</li>
* <li><b>μ‚¬μš© λͺ©μ :</b> 동일 쿠폰으둜 μ—¬λŸ¬ κΈ°κΈ°μ—μ„œ λ™μ‹œ 주문해도 ν•œ 번만 μ‚¬μš©λ˜λ„λ‘ 보μž₯</li>
* </ul>
* </p>
*
* @param userId μ‚¬μš©μž ID
* @param couponCode 쿠폰 μ½”λ“œ
* @param subtotal μ£Όλ¬Έ μ†Œκ³„ κΈˆμ•‘
* @return 할인 κΈˆμ•‘
* @throws CoreException 쿠폰을 찾을 수 μ—†κ±°λ‚˜ μ‚¬μš© λΆˆκ°€λŠ₯ν•œ 경우, λ™μ‹œ μ‚¬μš©μœΌλ‘œ μΈν•œ 좩돌 μ‹œ
*/
@Transactional
public Integer applyCoupon(Long userId, String couponCode, Integer subtotal) {
// 쿠폰 쑴재 μ—¬λΆ€ 확인
Coupon coupon = couponRepository.findByCode(couponCode)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND,
String.format("쿠폰을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. (쿠폰 μ½”λ“œ: %s)", couponCode)));

// 낙관적 락을 μ‚¬μš©ν•˜μ—¬ μ‚¬μš©μž 쿠폰 쑰회 (λ™μ‹œμ„± μ œμ–΄)
// @Version ν•„λ“œκ°€ μžˆμ–΄ μžλ™μœΌλ‘œ 낙관적 락이 적용됨
UserCoupon userCoupon = userCouponRepository.findByUserIdAndCouponCodeForUpdate(userId, couponCode)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND,
String.format("μ‚¬μš©μžκ°€ μ†Œμœ ν•œ 쿠폰을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. (쿠폰 μ½”λ“œ: %s)", couponCode)));

// 쿠폰 μ‚¬μš© κ°€λŠ₯ μ—¬λΆ€ 확인
if (!userCoupon.isAvailable()) {
throw new CoreException(ErrorType.BAD_REQUEST,
String.format("이미 μ‚¬μš©λœ μΏ ν°μž…λ‹ˆλ‹€. (쿠폰 μ½”λ“œ: %s)", couponCode));
}

// 쿠폰 μ‚¬μš© 처리
userCoupon.use();

// 할인 κΈˆμ•‘ 계산 (μ „λž΅ νŒ¨ν„΄ μ‚¬μš©)
Integer discountAmount = coupon.calculateDiscountAmount(subtotal, couponDiscountStrategyFactory);

try {
// μ‚¬μš©μž 쿠폰 μ €μž₯ (version 체크 μžλ™ μˆ˜ν–‰)
// λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ΄ λ¨Όμ € μˆ˜μ •ν–ˆλ‹€λ©΄ OptimisticLockException λ°œμƒ
userCouponRepository.save(userCoupon);
} catch (ObjectOptimisticLockingFailureException e) {
// 낙관적 락 좩돌: λ‹€λ₯Έ νŠΈλžœμž­μ…˜μ΄ λ¨Όμ € 쿠폰을 μ‚¬μš©ν•¨
throw new CoreException(ErrorType.CONFLICT,
String.format("쿠폰이 이미 μ‚¬μš©λ˜μ—ˆμŠ΅λ‹ˆλ‹€. (쿠폰 μ½”λ“œ: %s)", couponCode));
}

return discountAmount;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package com.loopers.domain.order;

import com.loopers.domain.payment.PaymentStatus;
import com.loopers.domain.product.Product;
import com.loopers.domain.user.Point;
import com.loopers.domain.user.User;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* μ£Όλ¬Έ 도메인 μ„œλΉ„μŠ€.
* <p>
* 주문의 κΈ°λ³Έ CRUD 및 μƒνƒœ 변경을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€.
* </p>
*
* @author Loopers
* @version 1.0
*/
@Component
@RequiredArgsConstructor
public class OrderService {

private final OrderRepository orderRepository;

/**
* 주문을 μ €μž₯ν•©λ‹ˆλ‹€.
*
* @param order μ €μž₯ν•  μ£Όλ¬Έ
* @return μ €μž₯된 μ£Όλ¬Έ
*/
@Transactional
public Order save(Order order) {
return orderRepository.save(order);
}

/**
* μ£Όλ¬Έ ID둜 주문을 μ‘°νšŒν•©λ‹ˆλ‹€.
*
* @param orderId μ£Όλ¬Έ ID
* @return 쑰회된 주문
* @throws CoreException 주문을 찾을 수 μ—†λŠ” 경우
*/
@Transactional(readOnly = true)
public Order getById(Long orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "주문을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."));
}

/**
* μ£Όλ¬Έ ID둜 주문을 μ‘°νšŒν•©λ‹ˆλ‹€ (Optional λ°˜ν™˜).
*
* @param orderId μ£Όλ¬Έ ID
* @return 쑰회된 μ£Όλ¬Έ (μ—†μœΌλ©΄ Optional.empty())
*/
@Transactional(readOnly = true)
public Optional<Order> findById(Long orderId) {
return orderRepository.findById(orderId);
}

/**
* μ‚¬μš©μž ID둜 μ£Όλ¬Έ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.
*
* @param userId μ‚¬μš©μž ID
* @return ν•΄λ‹Ή μ‚¬μš©μžμ˜ μ£Όλ¬Έ λͺ©λ‘
*/
@Transactional(readOnly = true)
public List<Order> findAllByUserId(Long userId) {
return orderRepository.findAllByUserId(userId);
}

/**
* μ£Όλ¬Έ μƒνƒœλ‘œ μ£Όλ¬Έ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€.
*
* @param status μ£Όλ¬Έ μƒνƒœ
* @return ν•΄λ‹Ή μƒνƒœμ˜ μ£Όλ¬Έ λͺ©λ‘
*/
@Transactional(readOnly = true)
public List<Order> findAllByStatus(OrderStatus status) {
return orderRepository.findAllByStatus(status);
}

/**
* 주문을 μƒμ„±ν•©λ‹ˆλ‹€.
*
* @param userId μ‚¬μš©μž ID
* @param items μ£Όλ¬Έ μ•„μ΄ν…œ λͺ©λ‘
* @param couponCode 쿠폰 μ½”λ“œ (선택)
* @param discountAmount 할인 κΈˆμ•‘ (선택)
* @return μƒμ„±λœ μ£Όλ¬Έ
*/
@Transactional
public Order create(Long userId, List<OrderItem> items, String couponCode, Integer discountAmount) {
Order order = Order.of(userId, items, couponCode, discountAmount);
return orderRepository.save(order);
}

/**
* 주문을 μƒμ„±ν•©λ‹ˆλ‹€ (쿠폰 μ—†μŒ).
*
* @param userId μ‚¬μš©μž ID
* @param items μ£Όλ¬Έ μ•„μ΄ν…œ λͺ©λ‘
* @return μƒμ„±λœ μ£Όλ¬Έ
*/
@Transactional
public Order create(Long userId, List<OrderItem> items) {
Order order = Order.of(userId, items);
return orderRepository.save(order);
}

/**
* 주문을 μ™„λ£Œ μƒνƒœλ‘œ λ³€κ²½ν•©λ‹ˆλ‹€.
*
* @param orderId μ£Όλ¬Έ ID
* @return μ™„λ£Œλœ μ£Όλ¬Έ
* @throws CoreException 주문을 찾을 수 μ—†λŠ” 경우
*/
@Transactional
public Order completeOrder(Long orderId) {
Order order = getById(orderId);
order.complete();
return orderRepository.save(order);
}

/**
* 주문을 μ·¨μ†Œ μƒνƒœλ‘œ λ³€κ²½ν•˜κ³  재고λ₯Ό μ›λ³΅ν•˜λ©° 포인트λ₯Ό ν™˜λΆˆν•©λ‹ˆλ‹€.
* <p>
* 도메인 둜직만 μ²˜λ¦¬ν•©λ‹ˆλ‹€. μ‚¬μš©μž 쑰회, μƒν’ˆ 쑰회, Payment μ‘°νšŒλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆμ΄μ–΄μ—μ„œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
* </p>
*
* @param order μ£Όλ¬Έ μ—”ν‹°ν‹°
* @param products μ£Όλ¬Έ μ•„μ΄ν…œμ— ν•΄λ‹Ήν•˜λŠ” μƒν’ˆ λͺ©λ‘ (락이 이미 νšλ“λœ μƒνƒœ)
* @param user μ‚¬μš©μž μ—”ν‹°ν‹° (락이 이미 νšλ“λœ μƒνƒœ)
* @param refundPointAmount ν™˜λΆˆν•  포인트 κΈˆμ•‘
* @throws CoreException μ£Όλ¬Έ λ˜λŠ” μ‚¬μš©μž 정보가 null인 경우
*/
@Transactional
public void cancelOrder(Order order, List<Product> products, User user, Long refundPointAmount) {
if (order == null || user == null) {
throw new CoreException(ErrorType.BAD_REQUEST, "μ·¨μ†Œν•  μ£Όλ¬Έκ³Ό μ‚¬μš©μž μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.");
}

// μ£Όλ¬Έ μ·¨μ†Œ
order.cancel();

// 재고 원볡
increaseStocksForOrderItems(order.getItems(), products);

// 포인트 ν™˜λΆˆ
if (refundPointAmount > 0) {
user.receivePoint(Point.of(refundPointAmount));
}

orderRepository.save(order);
}

/**
* 결제 μƒνƒœμ— 따라 μ£Όλ¬Έ μƒνƒœλ₯Ό μ—…λ°μ΄νŠΈν•©λ‹ˆλ‹€.
* <p>
* 도메인 둜직만 μ²˜λ¦¬ν•©λ‹ˆλ‹€. μ‚¬μš©μž 쑰회, νŠΈλžœμž­μ…˜ 관리, λ‘œκΉ…μ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆμ΄μ–΄μ—μ„œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
* </p>
*
* @param order μ£Όλ¬Έ μ—”ν‹°ν‹°
* @param paymentStatus 결제 μƒνƒœ
* @throws CoreException 주문이 nullμ΄κ±°λ‚˜ 이미 μ™„λ£Œ/μ·¨μ†Œλœ 경우
*/
@Transactional
public void updateStatusByPaymentResult(Order order, PaymentStatus paymentStatus) {
if (order == null) {
throw new CoreException(ErrorType.BAD_REQUEST, "μ£Όλ¬Έ μ •λ³΄λŠ” ν•„μˆ˜μž…λ‹ˆλ‹€.");
}

// 이미 μ™„λ£Œλ˜κ±°λ‚˜ μ·¨μ†Œλœ 주문인 경우 μ²˜λ¦¬ν•˜μ§€ μ•ŠμŒ
if (order.getStatus() == OrderStatus.COMPLETED || order.getStatus() == OrderStatus.CANCELED) {
return;
}

if (paymentStatus == PaymentStatus.SUCCESS) {
// 결제 성곡: μ£Όλ¬Έ μ™„λ£Œ
order.complete();
orderRepository.save(order);
} else if (paymentStatus == PaymentStatus.FAILED) {
// 결제 μ‹€νŒ¨: μ£Όλ¬Έ μ·¨μ†Œ (재고 원볡 및 포인트 ν™˜λΆˆμ€ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ λ ˆμ΄μ–΄μ—μ„œ 처리)
order.cancel();
orderRepository.save(order);
}
// PENDING μƒνƒœ: μƒνƒœ μœ μ§€ (아무 μž‘μ—…λ„ ν•˜μ§€ μ•ŠμŒ)
}

/**
* μ£Όλ¬Έ μ•„μ΄ν…œμ— λŒ€ν•΄ 재고λ₯Ό μ¦κ°€μ‹œν‚΅λ‹ˆλ‹€.
*
* @param items μ£Όλ¬Έ μ•„μ΄ν…œ λͺ©λ‘
* @param products μƒν’ˆ λͺ©λ‘
*/
private void increaseStocksForOrderItems(List<OrderItem> items, List<Product> products) {
Map<Long, Product> productMap = products.stream()
.collect(java.util.stream.Collectors.toMap(Product::getId, product -> product));

for (OrderItem item : items) {
Product product = productMap.get(item.getProductId());
if (product == null) {
throw new CoreException(ErrorType.NOT_FOUND,
String.format("μƒν’ˆμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€. (μƒν’ˆ ID: %d)", item.getProductId()));
}
product.increaseStock(item.getQuantity());
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.loopers.domain.payment;

/**
* μΉ΄λ“œ νƒ€μž….
*/
public enum CardType {
SAMSUNG,
KB,
HYUNDAI
}

Loading