Skip to content

Commit b90c603

Browse files
committed
refator: 쿠폰 이벤트 처리를 위해 도메인 서비스 로직 기능 분리(할인율 계산 + 사용완료처리) -> 각각 처리하도록 분리 및 분리 확인을 위한 쿠폰 상태 추가
1 parent 48bfb90 commit b90c603

5 files changed

Lines changed: 58 additions & 22 deletions

File tree

apps/commerce-api/src/main/java/com/loopers/application/order/OrderFacade.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.loopers.application.order;
22

3-
import com.loopers.domain.coupon.CouponDiscountResult;
43
import com.loopers.domain.coupon.CouponService;
54
import com.loopers.domain.order.Order;
65
import com.loopers.domain.order.OrderCommand.Item;
@@ -9,7 +8,6 @@
98
import com.loopers.domain.order.OrderService;
109
import com.loopers.domain.payment.Payment;
1110
import com.loopers.domain.payment.PaymentProcessor;
12-
import com.loopers.domain.point.PointService;
1311
import com.loopers.domain.product.Product;
1412
import com.loopers.domain.product.ProductService;
1513
import com.loopers.domain.user.User;
@@ -31,7 +29,6 @@ public class OrderFacade {
3129
private final ProductService productService;
3230
private final UserService userService;
3331
private final OrderService orderService;
34-
private final PointService pointService;
3532
private final CouponService couponService;
3633
private final List<PaymentProcessor> paymentProcessors;
3734

@@ -54,18 +51,18 @@ public OrderInfo placeOrder(PlaceOrder command) {
5451
long finalPaymentAmount = totalAmount;
5552

5653
if (command.couponId() != null) {
57-
CouponDiscountResult discountResult = couponService.useCouponAndCalculateDiscount(
54+
long disCountAmount = couponService.calculateDiscountAmount(
5855
command.couponId(),
5956
totalAmount
6057
);
6158

62-
finalPaymentAmount -= discountResult.discountAmount();
59+
finalPaymentAmount -= disCountAmount;
6360
}
6461

6562
productService.deductStock(products, orderItems);
6663

6764
PaymentProcessor processor = paymentProcessors.stream()
68-
.filter(p -> p.supports(command.paymentType())) // command에 paymentType 필드 추가 필요
65+
.filter(p -> p.supports(command.paymentType()))
6966
.findFirst()
7067
.orElseThrow(() -> new CoreException(ErrorType.BAD_REQUEST, "지원하지 않는 결제 방식입니다."));
7168

apps/commerce-api/src/main/java/com/loopers/domain/coupon/Coupon.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ public class Coupon extends BaseEntity {
2727

2828
private boolean used;
2929

30+
@Enumerated(EnumType.STRING)
31+
@Column(name = "status", nullable = false)
32+
private CouponStatus status;
33+
3034
public Coupon(long userId, CouponType type, long discountValue) {
3135
if (type == CouponType.PERCENTAGE && (discountValue < 0 || discountValue > 100)) {
3236
throw new CoreException(ErrorType.BAD_REQUEST, "할인율은 0~100% 사이여야 합니다.");
@@ -52,4 +56,24 @@ public void use() {
5256
this.used = true;
5357
}
5458

59+
public void reserve() {
60+
if (this.status != CouponStatus.ISSUED) {
61+
throw new CoreException(ErrorType.BAD_REQUEST,
62+
"현재 상태(" + this.status + ")에서는 쿠폰을 예약할 수 없습니다.");
63+
}
64+
65+
this.status = CouponStatus.RESERVED;
66+
}
67+
68+
public void confirmUse() {
69+
if (this.status != CouponStatus.RESERVED) {
70+
throw new CoreException(ErrorType.BAD_REQUEST,
71+
"예약 상태가 아니므로 사용을 확정할 수 없습니다: " + this.status);
72+
}
73+
this.status = CouponStatus.USED;
74+
}
75+
76+
public boolean canUse() {
77+
return this.status == CouponStatus.ISSUED;
78+
}
5579
}

apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponDiscountResult.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

apps/commerce-api/src/main/java/com/loopers/domain/coupon/CouponService.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
import com.loopers.support.error.CoreException;
44
import com.loopers.support.error.ErrorType;
5-
import jakarta.transaction.Transactional;
65
import lombok.RequiredArgsConstructor;
76
import org.springframework.stereotype.Component;
7+
import org.springframework.transaction.annotation.Transactional;
88

99
@RequiredArgsConstructor
1010
@Component
@@ -18,22 +18,37 @@ public Coupon getCoupon(Long id) {
1818
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "쿠폰을 찾을 수 없습니다."));
1919
}
2020

21-
@Transactional
22-
public CouponDiscountResult useCouponAndCalculateDiscount(
21+
@Transactional(readOnly = true)
22+
public long calculateDiscountAmount(
2323
Long couponId,
2424
long totalOrderAmount
2525
) {
2626
Coupon coupon = couponRepository.findById(couponId)
2727
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "쿠폰을 찾을 수 없습니다."));
2828

29+
if (!coupon.canUse()) {
30+
throw new CoreException(ErrorType.BAD_REQUEST, "사용할 수 없는 쿠폰입니다.");
31+
}
32+
33+
return coupon.calculateDiscountAmount(totalOrderAmount);
34+
}
35+
36+
@Transactional
37+
public void reserveCoupon(Long couponId) {
38+
Coupon coupon = couponRepository.findById(couponId)
39+
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "쿠폰을 찾을 수 없습니다."));
40+
2941
try {
30-
coupon.use();
42+
coupon.reserve();
3143
} catch (CoreException e) {
32-
throw new CoreException(ErrorType.BAD_REQUEST, "이미 사용되었거나 사용할 수 없는 쿠폰입니다.");
44+
throw new CoreException(ErrorType.BAD_REQUEST, "쿠폰 예약에 실패했습니다.");
3345
}
46+
}
3447

35-
long discountAmount = coupon.calculateDiscountAmount(totalOrderAmount);
36-
37-
return new CouponDiscountResult(discountAmount, couponId);
48+
@Transactional
49+
public void confirmCouponUsage(Long couponId) {
50+
Coupon coupon = couponRepository.findById(couponId)
51+
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "쿠폰을 찾을 수 없습니다."));
52+
coupon.confirmUse();
3853
}
3954
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.loopers.domain.coupon;
2+
3+
public enum CouponStatus {
4+
ISSUED,
5+
RESERVED,
6+
USED,
7+
CANCELED
8+
}

0 commit comments

Comments
 (0)