Skip to content
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

feat: 거래 내역은 경매 식별자, 구매(입찰) 가격, 구매 수량, 거래 유형(입찰, 환불), 구매자, 판매자, 생성일자를 포함한다. #178

Merged
merged 12 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
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
Expand Up @@ -20,7 +20,6 @@
* @param startedAt 경매 시작 시간
* @param finishedAt 경매 종료 시간
* @param isShowStock 재고를 보여줄지 여부
* @param status 현재 경매 상태
*/
@Builder
public record AuctionInfo(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.wootecam.luckyvickyauction.core.payment.domain;

import com.wootecam.luckyvickyauction.core.member.domain.Member;
import com.wootecam.luckyvickyauction.global.exception.BadRequestException;
import com.wootecam.luckyvickyauction.global.exception.ErrorCode;
import java.time.ZonedDateTime;
import lombok.Builder;
import lombok.Getter;

Expand All @@ -15,17 +18,23 @@ public class BidHistory {
private long auctionId;
private Member seller;
private Member buyer;
private ZonedDateTime createdAt;
private ZonedDateTime updatedAt;

public static final String ERROR_VARIATION_UPDATE_AT = "생성 시간보다 수정 시간이 더 작을 수 없습니다. 생성시간: %s, 수정시간: %s";

@Builder
public BidHistory(
final Long id,
final String productName,
final long price,
final long quantity,
BidStatus bidStatus,
final BidStatus bidStatus,
final long auctionId,
final Member seller,
final Member buyer) {
final Member buyer,
final ZonedDateTime createdAt,
final ZonedDateTime updatedAt) {
this.id = id;
this.productName = productName;
this.price = price;
Expand All @@ -34,10 +43,19 @@ public BidHistory(
this.auctionId = auctionId;
this.seller = seller;
this.buyer = buyer;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}

public boolean isRefundStatus() {
return bidStatus.equals(BidStatus.REFUND);
/**
* 해당 거래 내역을 환불 상태로 전환합니다.
*/
public void markAsRefund(ZonedDateTime updatedAt) {
if (bidStatus.equals(BidStatus.REFUND)) {
throw new BadRequestException("이미 환불된 입찰 내역입니다.", ErrorCode.B005);
}
Comment on lines +54 to +56
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제안 : 이미 환불 처리가 되었는데 환불 요청을 하는 것이면 예외를 터트리는 것보다 그냥 return 하는 것은 어떨까요?

Copy link
Collaborator Author

@chhs2131 chhs2131 Aug 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 의견입니다! 사실 '거래 내역' 입장에서는 자신의 상태가 환불됨 -> 환불됨 으로 변경된다고 해서 예외로 인식하지 않아도 될 것 같아요!

하지만 해당 로직이 PaymentService에서 사용되던 것을 BidHistory 내부로 이전한 것이라, PaymentService와 PaymentServiceTest 등에도 영향이 있어, 별도의 태스크를 생성하고 그곳에서 처리하는 것이 좋을 것 같습니다..!!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어떤 이름의 이슈를 생성할지 모르겠네요. 일단 Priority2의 리펙터링 이슈를 생성할께요

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bidStatus = BidStatus.REFUND;
setUpdatedAt(updatedAt);
}

/**
Expand All @@ -54,4 +72,12 @@ public boolean isOwnedBy(Member member) {
String signInId = member.getSignInId();
return seller.isSameMember(signInId) || buyer.isSameMember(signInId);
}

private void setUpdatedAt(ZonedDateTime updatedAt) {
if (updatedAt.isBefore(createdAt)) {
throw new BadRequestException(String.format(ERROR_VARIATION_UPDATE_AT, createdAt, updatedAt),
ErrorCode.B006);
}
this.updatedAt = updatedAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.wootecam.luckyvickyauction.core.payment.domain.BidStatus;
import com.wootecam.luckyvickyauction.global.exception.BadRequestException;
import com.wootecam.luckyvickyauction.global.exception.ErrorCode;
import java.time.ZonedDateTime;
import lombok.Builder;

@Builder
Expand All @@ -15,7 +16,9 @@ public record BidHistoryInfo(
BidStatus bidStatus,
long auctionId,
Member seller,
Member buyer
Member buyer,
ZonedDateTime createdAt,
ZonedDateTime updatedAt
) {

public static final String ERROR_PRODUCT_NAME = "상품 이름은 비어있을 수 없습니다.";
Expand All @@ -28,6 +31,8 @@ public record BidHistoryInfo(
validateNotNull(bidStatus, "입찰 상태");
validateNotNull(seller, "판매자 정보");
validateNotNull(buyer, "구매자 정보");
validateNotNull(createdAt, "거래 일자");
validateNotNull(updatedAt, "변경 일자");

validateProductName(productName);
validatePrice(price);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,22 +71,22 @@ private boolean submitBid(long price, long auctionId, long quantity, Member buye
*
* @param buyer 환불을 요청한 사용자
* @param bidHistoryId 환불할 입찰 내역의 id
* @param requestTime 환불 요청을 한 시간
*/
public void refund(Member buyer, long bidHistoryId) {
public void refund(Member buyer, long bidHistoryId, ZonedDateTime requestTime) {
if (!buyer.isBuyer()) {
throw new UnauthorizedException("구매자만 환불을 할 수 있습니다.", ErrorCode.P000);
}

BidHistory refundTargetBidHistory = findRefundTargetBidHistory(bidHistoryId);
if (refundTargetBidHistory.isRefundStatus()) {
throw new BadRequestException("이미 환불된 입찰 내역입니다.", ErrorCode.P003);
}
refundTargetBidHistory.markAsRefund(requestTime);

Member refundTargetBuyer = refundTargetBidHistory.getBuyer();
if (!buyer.isSameMember(refundTargetBuyer.getSignInId())) {
throw new UnauthorizedException("환불할 입찰 내역의 구매자만 환불을 할 수 있습니다.", ErrorCode.P004);
}

// 사용자 포인트 변경
long price = refundTargetBidHistory.getPrice();
long quantity = refundTargetBidHistory.getQuantity();

Expand All @@ -96,18 +96,9 @@ public void refund(Member buyer, long bidHistoryId) {

// 환불 요청에 대한 정보 저장
if (cancelBid(refundTargetBidHistory.getAuctionId(), quantity)) {
Member savedBuyer = memberRepository.save(buyer);
Member savedSeller = memberRepository.save(seller);
bidHistoryRepository.save(BidHistory.builder()
.id(refundTargetBidHistory.getId())
.auctionId(refundTargetBidHistory.getAuctionId())
.productName(refundTargetBidHistory.getProductName())
.price(price)
.quantity(quantity)
.bidStatus(BidStatus.REFUND)
.seller(savedSeller)
.buyer(savedBuyer)
.build());
memberRepository.save(buyer);
memberRepository.save(seller);
bidHistoryRepository.save(refundTargetBidHistory); // 정상적으로 환불 처리된 경우 해당 이력을 '환불' 상태로 변경
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public enum ErrorCode {
B002("입찰 내역 생성 시, 상품 이름이 비어있는 경우 예외가 발생합니다."),
B003("입찰 내역 생성 시, 거래 가격이 0과 같거나 작을 경우 예외가 발생합니다."),
B004("입찰 내역 생성 시, 거래 수량이 0과 같거나 작을 경우 예외가 발생합니다."),
B005("환불 시, 이미 환불된 입찰 내역일 경우 예외가 발생합니다."),
B006("거래 내역 수정 시, 생성 시간 보다 수정 시간이 더 과거일 때 예외가 발생합니다."),

// Member 관련 예외 코드
M000("로그인(회원가입) 시, 이미 존재하는 회원 아이디로 로그인을 시도한 경우 예외가 발생합니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ public static BidHistoryInfo convertToBidHistoryInfo(BidHistory bidHistory) {
.auctionId(bidHistory.getAuctionId())
.seller(bidHistory.getSeller())
.buyer(bidHistory.getBuyer())
.createdAt(bidHistory.getCreatedAt())
.updatedAt(bidHistory.getUpdatedAt())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,51 @@
package com.wootecam.luckyvickyauction.core.payment.domain;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import com.wootecam.luckyvickyauction.global.exception.BadRequestException;
import com.wootecam.luckyvickyauction.global.exception.ErrorCode;
import java.time.ZonedDateTime;
import org.junit.jupiter.api.Test;

public class BidHistoryTest {
class BidHistoryTest {

@Test
void 거래_내역이_환불_상태인지_확인할_수_있다() {
void 성공적으로_환불_표시를_하고_업데이트_시간을_갱신_한다() {
// given
ZonedDateTime now = ZonedDateTime.now();
BidHistory refundBidHistory = BidHistory.builder()
.bidStatus(BidStatus.REFUND)
.id(1L)
.auctionId(1L)
.productName("test")
.price(100L)
.quantity(1L)
.bidStatus(BidStatus.BID)
.createdAt(now)
.updatedAt(now)
.build();

// when
boolean isRefundStatus = refundBidHistory.isRefundStatus();
refundBidHistory.markAsRefund(now.plusMinutes(1));

// then
assertThat(isRefundStatus).isTrue();
assertThat(refundBidHistory.getBidStatus()).isEqualTo(BidStatus.REFUND);
assertThat(refundBidHistory.getUpdatedAt()).isEqualTo(now.plusMinutes(1));
}

@Test
void 이미_환불된_경매에_환불_표시를_하면_예외가_발생한다() {
// given
ZonedDateTime now = ZonedDateTime.now();
BidHistory refundBidHistory = BidHistory.builder()
.bidStatus(BidStatus.REFUND)
.build();

// expect
assertThatThrownBy(() -> refundBidHistory.markAsRefund(now))
.isInstanceOf(BadRequestException.class)
.hasMessage("이미 환불된 입찰 내역입니다.")
.satisfies(exception -> assertThat(exception).hasFieldOrPropertyWithValue("errorCode", ErrorCode.B005));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.wootecam.luckyvickyauction.core.payment.domain.BidStatus;
import com.wootecam.luckyvickyauction.global.exception.BusinessException;
import com.wootecam.luckyvickyauction.global.exception.ErrorCode;
import java.time.ZonedDateTime;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
Expand All @@ -18,39 +19,59 @@
class BidHistoryInfoTest {

static Stream<Arguments> bidHistoryInfoArguments() {
ZonedDateTime now = ZonedDateTime.now();

return Stream.of(
Arguments.of("상품 이름은 비어있을 수 없습니다.", ErrorCode.B002,
1L, "", 10000, 1, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("상품 이름은 빈 칸일 수 없습니다.", ErrorCode.B002,
1L, " ", 10000, 1, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("입찰 가격은 0보다 커야 합니다. 입찰 가격: 0", ErrorCode.B003,
1L, "상품이름", 0, 1, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("수량은 0보다 커야 합니다. 수량: 0", ErrorCode.B004,
1L, "상품이름", 10000, 0, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("상품 이름은 Null일 수 없습니다.", ErrorCode.G000,
1L, null, 10000, 1, BidStatus.REFUND, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("입찰 상태는 Null일 수 없습니다.", ErrorCode.G000,
1L, "상품이름", 10000, 1, null, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("판매자 정보는 Null일 수 없습니다.", ErrorCode.G000,
1L, "상품이름", 10000, 1, BidStatus.BID, 1L,
null,
new Member(2L, "buyer", "password", Role.BUYER, null)),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, now),
Arguments.of("구매자 정보는 Null일 수 없습니다.", ErrorCode.G000,
1L, "상품이름", 10000, 1, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
null)
null,
now, now),
Arguments.of("거래 일자는 Null일 수 없습니다.", ErrorCode.G000,
1L, "상품이름", 10000, 1, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null),
null, now),
Arguments.of("변경 일자는 Null일 수 없습니다.", ErrorCode.G000,
1L, "상품이름", 10000, 1, BidStatus.BID, 1L,
new Member(1L, "seller", "password", Role.SELLER, null),
new Member(2L, "buyer", "password", Role.BUYER, null),
now, null)
);
}

Expand All @@ -65,10 +86,12 @@ static Stream<Arguments> bidHistoryInfoArguments() {
Long auctionId = 1L;
Member seller = new Member(1L, "seller", "password", Role.SELLER, null);
Member buyer = new Member(2L, "buyer", "password", Role.BUYER, null);
ZonedDateTime createdAt = ZonedDateTime.now();
ZonedDateTime updatedAt = ZonedDateTime.now();

// when
BidHistoryInfo bidHistoryInfo = new BidHistoryInfo(id, productName, price, quantity, bidStatus, auctionId,
seller, buyer);
seller, buyer, createdAt, updatedAt);

// then
assertAll(
Expand All @@ -95,11 +118,14 @@ static Stream<Arguments> bidHistoryInfoArguments() {
BidStatus bidStatus,
Long auctionId,
Member seller,
Member buyer
Member buyer,
ZonedDateTime createdAt,
ZonedDateTime updatedAt
) {
// expect
assertThatThrownBy(
() -> new BidHistoryInfo(id, productName, price, quantity, bidStatus, auctionId, seller, buyer))
() -> new BidHistoryInfo(id, productName, price, quantity, bidStatus, auctionId, seller, buyer,
createdAt, updatedAt))
.isInstanceOf(BusinessException.class)
.satisfies(exception -> assertThat(exception).hasFieldOrPropertyWithValue("errorCode", errorCode));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ private BidHistory createBidHistory(Long id, BidHistory bidHistory) {
.auctionId(bidHistory.getAuctionId())
.seller(bidHistory.getSeller())
.buyer(bidHistory.getBuyer())
.createdAt(bidHistory.getCreatedAt())
.updatedAt(bidHistory.getUpdatedAt())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.wootecam.luckyvickyauction.global.exception.ErrorCode;
import com.wootecam.luckyvickyauction.global.exception.NotFoundException;
import com.wootecam.luckyvickyauction.global.exception.UnauthorizedException;
import java.time.ZonedDateTime;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
Expand Down Expand Up @@ -49,6 +50,7 @@ static Stream<Arguments> provideMembersForSuccess() {
@MethodSource("provideMembersForSuccess")
void 소유자가_거래내역_조회시_성공한다(Member member, String description) {
// given
ZonedDateTime now = ZonedDateTime.now();
Member seller = Member.builder().id(1L).signInId("판매자").role(Role.SELLER).build(); // 소유자
Member buyer = Member.builder().id(2L).signInId("구매자").role(Role.BUYER).build(); // 소유자

Expand All @@ -61,6 +63,8 @@ static Stream<Arguments> provideMembersForSuccess() {
.auctionId(1L)
.seller(seller)
.buyer(buyer)
.createdAt(now)
.updatedAt(now)
.build();
bidHistoryRepository.save(bidHistory);

Expand Down Expand Up @@ -118,6 +122,7 @@ static Stream<Arguments> provideMembersForSuccess() {
@Test
void 다른_판매자의_구매이력_조회시_예외가_발생한다() {
// given
ZonedDateTime now = ZonedDateTime.now();
Member seller1 = Member.builder().id(1L).signInId("판매자").role(Role.SELLER).build(); // 판매자
Member seller2 = Member.builder().id(2L).signInId("옆집 사장님").role(Role.SELLER).build(); // 판매자
Member buyer = Member.builder().id(3L).signInId("구매자").role(Role.BUYER).build(); // 구매자
Expand All @@ -131,6 +136,8 @@ static Stream<Arguments> provideMembersForSuccess() {
.auctionId(1L)
.seller(seller1)
.buyer(buyer)
.createdAt(now)
.updatedAt(now)
.build();
bidHistoryRepository.save(bidHistory);

Expand Down
Loading