-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#15] 마이페이지 쿠폰 조회 & 사용 가능한 쿠폰 조회 & 쿠폰 사용 #16
Changes from 21 commits
f1fe151
02691a4
8d2ad96
c9d9a2f
08d2332
7121a2a
6c419e5
33459ed
d85e5a9
c844d64
53c23e2
c36b34d
767f1bb
d8c047f
fca2d3c
3c82ef2
c1b8be5
dc01f8b
2b0c15e
235238a
cfbf82b
450f9d6
894f7c2
4e3d989
784033c
d255cd0
51475f9
f1d6941
c0a419d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.flab.offcoupon.controller; | ||
|
||
import com.flab.offcoupon.dto.response.AllCouponsByMemberIdResponse; | ||
import com.flab.offcoupon.service.mypage.MyPageService; | ||
import com.flab.offcoupon.util.ResponseDTO; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
@RequestMapping("/api/v1/my-page") | ||
@RestController | ||
public class MyPageController { | ||
|
||
private final MyPageService myPageService; | ||
@ResponseStatus(HttpStatus.OK) | ||
@GetMapping("/coupons") | ||
public ResponseEntity<ResponseDTO<List<AllCouponsByMemberIdResponse>>> getAllCoupons(@RequestParam final long memberId) { | ||
return ResponseEntity.status(HttpStatus.OK).body(myPageService.getAllCoupons(memberId)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.flab.offcoupon.controller; | ||
|
||
import com.flab.offcoupon.dto.request.OrderProductRequest; | ||
import com.flab.offcoupon.dto.response.AvailableCouponsByMemberIdResponse; | ||
import com.flab.offcoupon.service.coupon_use.OrderService; | ||
import com.flab.offcoupon.util.ResponseDTO; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.List; | ||
|
||
@RequiredArgsConstructor | ||
@RequestMapping("/api/v1/orders") | ||
@RestController | ||
public class OrderController { | ||
|
||
private final OrderService orderService; | ||
|
||
/** | ||
* 사용 가능한 쿠폰 목록 조회 | ||
* | ||
* @param memberId 회원 ID | ||
* @param productId 상품 ID | ||
* @return 사용 가능한 쿠폰 목록 | ||
*/ | ||
@ResponseStatus(HttpStatus.OK) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Controller에서 ResponseEntity와 @ResponseStatus를 동시에 사용할때에는 ResponseEntity에서 명시된 상태코드가 우선됩니다. 즉, ResponseEntity에서 명시된 상태코드가 HTTP 응답으로 반환됩니다. 하지만 응답이 2번 발생할 수 있기 때문에 동시에 2개를 사용하는 것은 권장되지 않고, 아래와 같이 (제가 한 실수,,) 응답코드를 Created와 Ok를 둘 다 사용한다면 다른 개발자가 코드를 읽을때 혼동을 줄 수도 있습니다. 따라서 휴먼 에러를 방지하기 위해 왠만하면 |
||
@GetMapping("/available-coupons") | ||
public ResponseEntity<ResponseDTO<List<AvailableCouponsByMemberIdResponse>>> getAvailableCoupons(@RequestParam final long memberId, | ||
@RequestParam final long productId) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 부분도 마찬가지로, 자기자신의 쿠폰 목록을 조회하는 것인데 |
||
LocalDateTime now = LocalDateTime.now(); | ||
return ResponseEntity.status(HttpStatus.OK).body(orderService.getAvailableCoupons(memberId, productId, now)); | ||
} | ||
|
||
/** | ||
* 상품 주문<br> | ||
* 결제 SDK 연동은 현재 진행 중인 프로젝트에서 메인으로 다루지 않기 때문에 제외했습니다. | ||
* | ||
* @param productId 상품 ID | ||
* @param orderProductRequest 주문 요청 정보 | ||
* @return 주문 성공 여부 | ||
*/ | ||
@ResponseStatus(HttpStatus.CREATED) | ||
@PostMapping("/products/{productId}") | ||
public ResponseEntity<ResponseDTO<String>> orderProduct(@PathVariable final long productId, | ||
@RequestBody final OrderProductRequest orderProductRequest) { | ||
LocalDateTime now = LocalDateTime.now(); | ||
return ResponseEntity.status(HttpStatus.OK).body(ResponseDTO.getSuccessResult(orderService.orderProduct(productId,orderProductRequest, now))); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.flab.offcoupon.domain.entity; | ||
|
||
import com.flab.offcoupon.domain.entity.params.AppliedCouponInfo; | ||
import com.flab.offcoupon.domain.entity.params.TimeParams; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
/** | ||
* 주문에 사용된 쿠폰 정보를 담는 도메인 객체입니다. | ||
* 하나의 주문에 여러 쿠폰을 사용할 수 있기 때문에 별도의 테이블로 분리하였습니다. | ||
*/ | ||
@ToString | ||
@Getter | ||
@AllArgsConstructor | ||
public final class OrderCoupon { | ||
private long id; | ||
private final long orderDetailId; | ||
private final long couponId; | ||
private final long discountAmount; | ||
private final LocalDateTime createdAt; | ||
private final LocalDateTime updatedAt; | ||
|
||
private OrderCoupon(long orderDetailId, AppliedCouponInfo coupon, TimeParams timeParams){ | ||
this.orderDetailId = orderDetailId; | ||
this.couponId = coupon.getCouponId(); | ||
this.discountAmount = coupon.getDiscountAmount(); | ||
this.createdAt = timeParams.createdAt(); | ||
this.updatedAt = timeParams.updatedAt(); | ||
} | ||
|
||
public static OrderCoupon createOrderCoupon(long orderDetailId, AppliedCouponInfo coupon) { | ||
LocalDateTime now = LocalDateTime.now(); | ||
return new OrderCoupon(orderDetailId, coupon, new TimeParams(now, now)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.flab.offcoupon.domain.entity; | ||
|
||
import com.flab.offcoupon.domain.entity.params.OrderInfo; | ||
import com.flab.offcoupon.domain.entity.params.TimeParams; | ||
import com.flab.offcoupon.util.DateTimeUtils; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
/** | ||
* 주문 상세 정보를 담는 도메인 객체입니다. | ||
*/ | ||
@ToString | ||
@Getter | ||
@AllArgsConstructor | ||
public final class OrderDetail { | ||
private long id; | ||
private final long productId; | ||
private final long quantity; | ||
private final long pricePerEach; // 상품 1개당 가격 | ||
private final long totalOrderPrice; // 총 상품 주문 가격 | ||
private final long totalDiscountPrice; // 할인 가격 | ||
private final long totalPaymentPrice; // 총 상품 주문 가격 - 할인 가격 | ||
private final LocalDateTime createdAt; | ||
private final LocalDateTime updatedAt; | ||
|
||
private OrderDetail(OrderInfo orderInfo, TimeParams timeParams) { | ||
this.productId = orderInfo.getProductId(); | ||
this.quantity = orderInfo.getQuantity(); | ||
this.pricePerEach = orderInfo.getPricePerEach(); | ||
this.totalOrderPrice = orderInfo.getTotalOrderPrice(); | ||
this.totalDiscountPrice = orderInfo.getTotalDiscountPrice(); | ||
this.totalPaymentPrice = orderInfo.getTotalPaymentPrice(); | ||
this.createdAt = timeParams.createdAt(); | ||
this.updatedAt = timeParams.updatedAt(); | ||
} | ||
|
||
public static OrderDetail createOrderDetail(OrderInfo orderInfo) { | ||
LocalDateTime now = DateTimeUtils.nowFromZone(); | ||
return new OrderDetail(orderInfo, new TimeParams(now, now)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.flab.offcoupon.domain.entity; | ||
|
||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.ToString; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
/** | ||
* 상품 정보를 담는 도메인 객체입니다. | ||
*/ | ||
@ToString | ||
@Getter | ||
@AllArgsConstructor | ||
public final class Product { | ||
private long id; | ||
private final String category; | ||
private final String title; | ||
private final String description; | ||
private final long originalPrice; // 원래 가격 | ||
private final Long salePrice; // null일 경우 전체 할인이 적용되지 않은 것으로 간주 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null로 비즈니스 로직을 표현하는 것은 NPE의 가능성을 높입니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 정밀도 향상을 위해 BigDecimal타입으로 변경했고, default 0으로 설정했습니다! |
||
private final long minOrderPrice; // 최소 주문 가격 | ||
private final LocalDateTime createdAt; | ||
private final LocalDateTime updatedAt; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.flab.offcoupon.domain.entity.params; | ||
|
||
import com.flab.offcoupon.domain.entity.Coupon; | ||
import com.flab.offcoupon.domain.entity.Product; | ||
import com.flab.offcoupon.util.DiscountUtils; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@AllArgsConstructor | ||
public final class AppliedCouponInfo { | ||
|
||
private final long couponId; | ||
private final long discountAmount; | ||
|
||
private AppliedCouponInfo(Product product, Coupon coupon) { | ||
this.couponId = coupon.getId(); | ||
this.discountAmount = DiscountUtils.calculateDiscountPricePerUnit(product, coupon); | ||
} | ||
|
||
public static AppliedCouponInfo createAppliedCouponInfo(Product product, Coupon coupon) { | ||
return new AppliedCouponInfo(product, coupon); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.flab.offcoupon.domain.entity.params; | ||
|
||
import com.flab.offcoupon.domain.entity.Coupon; | ||
import com.flab.offcoupon.domain.entity.Product; | ||
import com.flab.offcoupon.util.DiscountUtils; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
|
||
import java.util.List; | ||
|
||
@Getter | ||
@AllArgsConstructor | ||
public final class OrderInfo { | ||
private final long productId; | ||
private final long quantity; | ||
private final long pricePerEach; | ||
private final long totalOrderPrice; | ||
private final List<AppliedCouponInfo> appliedCouponInfos; | ||
private final long totalDiscountPrice; | ||
private final long totalPaymentPrice; | ||
|
||
private OrderInfo(Product product, long quantity, List<Coupon> couponList) { | ||
this.productId = product.getId(); | ||
this.quantity = quantity; | ||
this.pricePerEach = DiscountUtils.getProductPricePerUnit(product); | ||
this.totalOrderPrice = DiscountUtils.calculateTotalPrice(product, quantity); | ||
this.appliedCouponInfos = createAppliedCouponInfos(product, couponList); | ||
this.totalDiscountPrice = DiscountUtils.calculateTotalDiscountPrice(product, couponList, quantity); | ||
this.totalPaymentPrice = DiscountUtils.calculateTotalPaymentPrice(product, quantity, couponList); | ||
} | ||
|
||
public static OrderInfo createOrderInfo(Product product, long quantity, List<Coupon> couponList) { | ||
return new OrderInfo(product, quantity, couponList); | ||
} | ||
|
||
private List<AppliedCouponInfo> createAppliedCouponInfos(Product product, List<Coupon> couponList) { | ||
return couponList.stream() | ||
.map(coupon -> AppliedCouponInfo.createAppliedCouponInfo(product, coupon)) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.mypage; | ||
|
||
import com.flab.offcoupon.domain.entity.CouponStatus; | ||
import com.flab.offcoupon.domain.entity.DiscountType; | ||
|
||
import java.time.LocalDateTime; | ||
/** | ||
* MyBatis에서 여러개의 반환 값을 전달 받기 위한 VO | ||
* | ||
* 마이페이지에서 현재 회원이 가진 모든 쿠폰을 조회하기 위해 사용 | ||
*/ | ||
public record AllCouponsByMemberIdVo( | ||
long couponId, | ||
String category, | ||
String description, | ||
DiscountType discountType, | ||
Long discountRate,// NULL 일 경우 AMOUNT | ||
Long discountPrice,// NULL 일 경우 PERCENT | ||
LocalDateTime validateStartDate, | ||
LocalDateTime validateEndDate, | ||
CouponStatus couponStatus | ||
) | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.order; | ||
|
||
import com.flab.offcoupon.domain.entity.CouponStatus; | ||
import com.flab.offcoupon.domain.entity.DiscountType; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
/** | ||
* MyBatis에서 여러개의 반환 값을 전달 받기 위한 VO | ||
* 마이페이지에서 현재 회원이 가진 모든 쿠폰을 조회하기 위해 사용 | ||
*/ | ||
public record AvailableCouponsByMemberIdVo ( | ||
long couponId, | ||
LocalDateTime validateStartDate, | ||
LocalDateTime validateEndDate, | ||
DiscountType discountType, | ||
Long discountRate,// NULL 일 경우 AMOUNT | ||
Long discountPrice,// NULL 일 경우 PERCENT | ||
String category, | ||
String description, | ||
long couponIssueId, | ||
CouponStatus couponStatus, | ||
long discountedPrice // 할인 가격 | ||
) | ||
{} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.order; | ||
|
||
/** | ||
* MyBatis에서 여러개의 반환 값을 전달 받기 위한 VO | ||
* 발급된 쿠폰들의 상태가 활성화인지 확인하기 위해 사용 | ||
* @param couponIssueId 쿠폰 발급 ID | ||
* @param isActive 쿠폰의 상태가 활성화인지 여부 | ||
*/ | ||
public record CouponIssuesAreActiveVo( | ||
long couponIssueId, | ||
boolean isActive | ||
|
||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.order; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
/** | ||
* MyBatis에서 여러개의 파라미터를 전달하기 위한 VO | ||
* | ||
* @param memberId | ||
* @param productId | ||
* @param currentDateTime | ||
*/ | ||
public record MemberIdProductIdNowVo( | ||
long memberId, | ||
long productId, | ||
LocalDateTime currentDateTime | ||
) { | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.flab.offcoupon.domain.vo.persistence.order; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
/** | ||
* MyBatis에서 여러개의 반환 값을 전달 받기 위한 VO | ||
* @param couponId 쿠폰 ID | ||
* @param isBetweenValidatePeriod 현재 시간이 쿠폰의 유효기간 범위내에 있는지 여부 | ||
*/ | ||
public record ValidateNowIsBetweenPeriodVo( | ||
long couponId, | ||
boolean isBetweenValidatePeriod, | ||
LocalDateTime validateStartDate, | ||
LocalDateTime validateEndDate | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.flab.offcoupon.dto.request; | ||
|
||
import lombok.Generated; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
import java.util.List; | ||
|
||
@Generated | ||
@Getter | ||
@RequiredArgsConstructor | ||
public final class OrderProductRequest { | ||
private final List<Long> couponIssueId; // 하나의 주문에 여러개의 쿠폰을 사용할 경우 | ||
private final List<Long> couponId; // 하나의 주문에 여러개의 쿠폰을 사용할 경우 | ||
private final int quantity; // 주문할 상품 수량 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
자기자신의 쿠폰 리스트를 확인하는데, 파라미터로 memberId를 받을 필요가 있을까요?
이 유저가 다른 유저의 id를 파라미터로 입력해서 데이터를 조회한다면 어떻게 될까요?
별도의 권한체크가 없으니 parameter manipulation 공격이 성립하겠네요. 이 키워드를 한번 찾아보시고, 취약점을 방어해주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 부분은 이번 PR에서 수정하지 않고 미루기로했으니, 우선 해당 URL이 오픈되지 않도록 핸들러매핑이 연결되지 않게
@GetMapping("/coupons")
을 지워주세요~~There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
네엡 다음번 PR에서 수정하도록하겠습니다!