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

[#326] 후원 및 환불 api 리팩토링 및 구현 #327

Merged
merged 16 commits into from Sep 7, 2021

Conversation

Be-poz
Copy link
Collaborator

@Be-poz Be-poz commented Sep 5, 2021

포인트 충전 api 절차

/payments/charge/ready

request: itemId
response : merchantUid, itemName, amount, email

/payments/charge

request : impUid, merchantUid
response : point


후원 절차

/donations

request : pageName, point
response : donationId, name, message, amount, createdAt

/donations/{donationId}/message

request : name, message, secret
response : -

환불은 내부로직만 바뀌고 엔드포인트 바뀐 것 없음


본인이 보낸 후원을 나중에 볼 수 있게끔 하기위해서 DonationDonationType 이라는 enum 타입 필드를 추가해주고,
후원 요청 시에 2개의 Donation을 각각 만들어서 creator와 donator가 가지고 있는 List에 집어넣어줬을 경우,
/donations/{donationId}/message 호출 시에 하나의 donationId 만 들어올텐데 두 도네이션의 Message를 다 업데이트 하기가 애매한 것 같음!
후원자가 본인이 보낸 후원 메세지에 대한 정보가 defaultMessage 여도 상관없다면 괜찮지만... 아니면 프론트한테 /donations 이후에 response 로 donationId를 2개를 response 해주고 message API 에서 2개의 id를 모두 받는 방법도 있긴하지만!


  1. 이제 거의 모든 API가 토큰이 필요할텐데 (다 인가??) 인수테스트 짜는 담당이 이거 path 좀 config에 추가해줄레?
  2. Payment 환불 시에 환불 기간 지났는지에 대한 로직을
    image
    이걸로 짰는데 테스트 잊으면 안됑!
  3. 이전에는 후원하는 절차에서 결제를 했어서 그때 메일이 발송됐는데, 후원시에는 발송안하고 포인트 충전하면 발송하게끔 해놨어
  4. Donation에 들어가는 Message 로직을 좀 바꿔놨어! 이제 defaultName이 후원자의 nickname이니깐 이 부분 때문에 생성자가 바뀌었을거야 !!

더 말할게 있었던 것 같은데 . .. 기억안나네... 일단 바뀐점 보면서 궁금한거 다 물어봐!!
내가 놓친부분이 무조건 있을 것 같은데, 아마 테스트 짜면서 속속들이 발견될 것 같음 . . !!

@Be-poz Be-poz added feature 새로 추가될 기능 server 백엔드 관련 이슈 refactor 기능에 변경이 없는 코드 리팩터링 labels Sep 5, 2021
@Be-poz Be-poz added this to the TYF-SPRINT6 milestone Sep 5, 2021
@DWL5
Copy link
Collaborator

DWL5 commented Sep 5, 2021

오우 이해하는데 힘들었따.. 일단 지금 코드는 안보고 파즈가 작성한 글만 읽었어
dontation을 2개 만드는 거 자체가 조금 이상하게 느껴진다 ㅜㅜ

고민해 봤는데 donation에 from, to 같은 인자를 만들어서 form이 후원자, to가 창작자 아이디 들가게 하는 건 어때
그래서 donation List는 그대로 관리하되, 내가 기부한 것 만 보고싶으면 from이 '나' 인것을 찾고, 정산계좌 등록해서는 to를 통해 내가 받은것을 볼 수 도 있을 것 같아

내일 아침에 일어나서 또 코드리뷰 할게!!
고민한 내용 잊어버릴까봐 먼저 코멘트남긴다요

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 5, 2021

오호 나는 그 생각에 대해서 DonationMember를 2개 가지고 있을 수 없으니 안되지! 라고 생각했는데 그냥 String , Long 뭐 이런 타입으로 갖고있으면 되겠구나 좋은 방법인 것 같아 수리!

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

Donation이 Member를 2개 가지고 있을 수 없으니 안되지!
-> 이거도 우리 JPA사용해서 연관관계 설정하면 테이블 상에서는 ID값이 들어가는거지?
도메인 내에서 Member 2개를 가지고 있어야 할거 같아서!!

Copy link
Collaborator

@DWL5 DWL5 left a comment

Choose a reason for hiding this comment

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

바뀐 도메인 로직 이해하는 식으로 코드리뷰 했어..!!
테스트 짤 때 좀 참고하려고!!

변경된 도네이션 로직

  • Login된 멤버만 후원을 할 수 있다.
  • donator가 후원할 충분한 금액이 있는지 체크한다.
    • validateEnoughPoint
  • donator의 nickname이 message의 name이 된다.

->>>>>>>> 여기 보니까 creator에 donation을 넣어주고 있는데, donation이 creator하고, donator를 가지고 있는게 더 좋은지 얘기해 보자

변경 된Payment 관련 로직

  • createPayment -사용자가 충전할 item을 설정하고 우리는 Payment 새 엔티티를 만들어 저장 하는 결제 준비 단계

    • 충전은 login상태에서 가능하다.
    • ItemId를 통해 구매 될 Item을 찾는다. (충전금액)
  • 변경 된 환불 변경 로직

    • 환불러한테 환불하기 충분한 금액이 있는지 확인한다. (이번에 추가)
    • isAfterRefundGuaranteeDuration 메소드 추가 (7일 체크)

@@ -0,0 +1,5 @@
package com.example.tyfserver.donation.domain;

public enum DonationType {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Donation이 창작자, 후원자로 바뀌게 한다면 얘는 필요 없어지겠다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

응 맞어! 그리고 지금 코드도 이 enum을 사용하고 있지는 않아 미리 만들어뒀어! 그리고 Donation 엔티티 필드에 DonationStatus 가 아직 있는데 이것도 삭제해야하는데 아직 삭제안했업

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

말 나온김에 DonationStatus Donation 필드에서 빼놔야겠다

@@ -28,4 +28,8 @@ public void reduce(final long amount) {
}
this.point -= amount;
}

public boolean lessThan(Long point) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

오 메소드 명 좋다.

Copy link
Collaborator

Choose a reason for hiding this comment

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

근데 payment도메인의 reduce 함수에서 "포인트가 총액보다 적게 있습니다" 예외가 RuntimeException으로 되어있당

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

오 땡큐 이거 예외 만들어놓앗는데 여기다가 적용안했네 굿

private boolean bankRegistered;

public MemberResponse(Member member) {
this(member.getEmail(), member.getNickname(), member.getPageName(),
member.getBio(), member.getProfileImage(), isBankRegistered(member));
member.getBio(), member.getProfileImage(), member.getAvailablePoint(), isBankRegistered(member));
Copy link
Collaborator

Choose a reason for hiding this comment

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

MemberResponse에 AvailablePoint만 주는 이유는

members/me/point API가 있어서 겠쥬?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이 API는 현재 보유 포인트 달라고 했어서 이거 준거야!
그때 얘기하기로 도네받은 포인트는 그 정산탭 가면 보이니깐 어쩌고 저쩌고 했던걸로 알아서!

@@ -166,4 +168,12 @@ public boolean isRefundBlocked() {
public boolean isNotPaid() {
return status != PaymentStatus.PAID;
}

public boolean isAfterRefundGuaranteeDuration() {
return LocalDateTime.now().isAfter(getCreatedAt().plusDays(7));
Copy link
Collaborator

Choose a reason for hiding this comment

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

환불 비즈니스 로직

  • 7일 이전 AND 환불러가 환불요청한 금액보다 많은 포인트를 가질 경우

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ㅋㅋㅋ 기록해두는거지?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 수리 꼼꼼해~~ 👍
이 부분은 LocalDateTime.now()를 now 변수로 빼도 좋을 것 같네 😃

Suggested change
return LocalDateTime.now().isAfter(getCreatedAt().plusDays(7));
LocalDateTime now = LocalDateTime.now();
return now.isAfter(getCreatedAt().plusDays(7));

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아니면

return LocalDateTime.now
             .isAfter(getCreatedAt().plusDays(7));

는 어때 ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

줄바꿈 하는 것도 나쁘지 않네 이정도면 난 OK 👌

private String nickname;
private String pageName;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy'/'MM'/'dd'/' HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createdAt;
Copy link
Collaborator

Choose a reason for hiding this comment

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

우리 그때 날짜 형식도 협의했었나???

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이거는 로키 조이가 작업해놨던거 재사용한거얌. 이걸 해준 이유는 LocalDateTime은 직렬화 안돼서 저렇게 해줬을거야 ..?
https://jojoldu.tistory.com/361 관련 내용 링크야

Copy link
Collaborator

Choose a reason for hiding this comment

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

따로 협의한건 없는데, 그 전에 LocalDateTime을 포맷팅하지않고 바로 보냈을 때, 형식이 별로라고해서 일반적인 형식(?)으로 변경했었어

return ResponseEntity.status(HttpStatus.CREATED)
.body(paymentService.completePayment(paymentCompleteRequest));
}

@PostMapping("/refund/verification/ready")
public ResponseEntity<RefundVerificationReadyResponse> refundVerificationReady(@Valid @RequestBody RefundVerificationReadyRequest verificationReadyRequest, BindingResult result,
LoginMember loginMember) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

여기서 LoginMember를 사용하지 않는데,
인터셉터에 걸리게만 해놓는건 어때?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이게... 처음에 토큰 필요한 애들이니깐 미리 LoginMember 써놔야지~! 했다가 안쓰는 애들이 많겠구나 싶어서 놔둔거였어 !! 테스트코드하면서 필요하면 추가하고 안쓰면 빼고 해주면 좋을 것 같어!

Copy link
Collaborator

@Rok93 Rok93 Sep 6, 2021

Choose a reason for hiding this comment

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

지금은 안쓰지만, 앞으로 변경 될 후원로직을 생각하면 LoginMember가 필요할거 같아서 굳이 안지워도 될 것 같어!
혹시라도 안쓴다면 나중에 제거해도 될 부분인거 같어 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

테스트 짜면서 알아서들 지워주세요 허헣

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

Donation이 Member를 2개 가지고 있을 수 없으니 안되지!
-> 이거도 우리 JPA사용해서 연관관계 설정하면 테이블 상에서는 ID값이 들어가는거지?
도메인 내에서 Member 2개를 가지고 있어야 할거 같아서!!

도메인 내에 Member 2개를 가지고 있을 수 없어서 String 이나 int로 후원자와 창작자에 대한 한 쪽의 정보를 두자고 한거였어~!
엔티티는 DB 테이블 그 자체라고 봐야돼. Donation이 Member 테이블하고 연관이 되어있으니깐 Member 필드안에 Donation이 들어가있고 한거잖아? 근데 여기에 Member가 2개 들어가있는게 불가능해! 이거를 DB로 바꾼다 생각하고 한 번 생각해보면 좋을 것 같어!

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

내가 말한 형식으로 하려면 아래처럼 하면 되는거아니야?
... JPA초보의 생각

좀 더 생각의 흐름을 보충하자면 테이블로 생각해봤어!!!!!
Donation 테이블이 creator, donator컬럼을 가지는 것은 가능
두 개의 컬럼 다 Member와 외래키 제약 걸어줘야함

그래서 Member는 자기 id가 걸린 Donation을 땡겨 올 수 있음
그게 Donation에서 creator, donator가 존재
밑에 예제에서 donations(받은거)만 땡겨왔는데
추가로 donator에 member id 걸린 애들 찾아서 후원 '한' 리스트도 땡길 수 있지 않을까

Dontation에

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "creator_id")
    private Member creator;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "donator_id")
    private Member donator;

Member에

    @OneToMany(mappedBy = "creator")
    private final List<Donation> donations = new ArrayList<>();

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

내가 말한 형식으로 하려면 아래처럼 하면 되는거아니야?
... JPA초보의 생각

Dontation에

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "creator_id")
    private Member creator;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "donator_id")
    private Member donator;

Member에

    @OneToMany(mappedBy = "creator")
    private final List<Donation> donations = new ArrayList<>();

당연히 안된다고 생각했는데 혹시 몰라서 프로젝트 따로파서 했는데 되는줄 알고 식겁했네 안된다~!
근데 말이 안되긴해! 수리는 지금 RDBMS 관점에서 보고 있는거야
가령, DB Doonation Table에서 fk 로 creator_id, donator_id 를 갖고있으니깐 되지 않을까? 라고 생각하는데
만약 객체지향적으로가 아니라 데이터 중심적으로 Donation 필드안에 Long 타입으로 저 두 개의 필드값을 갖고 있으면 되겠지!
근데 그게 아니라 Member 라는 객체를 포함하고 있고 객체의 관점에서 봐야돼!

그냥 단순히 DB와의 연관관계라고 생각하고 보면 돼. 객체의 관점에서 수리의 말을 보면 DonationMember라는 2개의 테이블과 매핑되어 있는거야. 근데 그게 아니잖아!? 그래서 안돼!

+) 아 어떤식으로 안됐냐면, 테이블 자체는 만들어졌어! DB 관점에서는 DonationMember에 대한 fk만 갖고있으면 되니깐! 근데 donationRepository.findById() 를 통해서 Donation 을 갖고오니깐 Member 타입 필드들이 프록시 객체가 아니라 null로 갖고 오더라구! 즉, 아예 Member 를 만들어서 갖고오지를 못하더라~!

Copy link
Collaborator

@Rok93 Rok93 left a comment

Choose a reason for hiding this comment

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

리뷰가 좀 늦었네 😅🙏
전체적으로 확인했는데 사실 뭐가 빠졌다거나 이런 부분은 바로바로 캐치가 안되는거 같아서 테스트짜면서 상의할 부분있으면 얘기해보면 될 것 같아😅
파즈 고생많았어! 👍

return ResponseEntity.status(HttpStatus.CREATED)
.body(paymentService.completePayment(paymentCompleteRequest));
}

@PostMapping("/refund/verification/ready")
public ResponseEntity<RefundVerificationReadyResponse> refundVerificationReady(@Valid @RequestBody RefundVerificationReadyRequest verificationReadyRequest, BindingResult result,
LoginMember loginMember) {
Copy link
Collaborator

@Rok93 Rok93 Sep 6, 2021

Choose a reason for hiding this comment

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

지금은 안쓰지만, 앞으로 변경 될 후원로직을 생각하면 LoginMember가 필요할거 같아서 굳이 안지워도 될 것 같어!
혹시라도 안쓴다면 나중에 제거해도 될 부분인거 같어 🤔

@@ -166,4 +168,12 @@ public boolean isRefundBlocked() {
public boolean isNotPaid() {
return status != PaymentStatus.PAID;
}

public boolean isAfterRefundGuaranteeDuration() {
return LocalDateTime.now().isAfter(getCreatedAt().plusDays(7));
Copy link
Collaborator

Choose a reason for hiding this comment

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

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 수리 꼼꼼해~~ 👍
이 부분은 LocalDateTime.now()를 now 변수로 빼도 좋을 것 같네 😃

Suggested change
return LocalDateTime.now().isAfter(getCreatedAt().plusDays(7));
LocalDateTime now = LocalDateTime.now();
return now.isAfter(getCreatedAt().plusDays(7));

return LocalDateTime.now().isAfter(getCreatedAt().plusDays(7));
}

public void validateMemberHasEnoughPoint() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

환불을 하기전에 포인트가 충분히 있는건지 확인하는 로직인 것 같은데, Payment가 member를 필드로 가지다보니까, 내부에서 member.validateEnoughPoint(point); 해버리니까 이게 무슨 기능인지 조금 헷갈리네 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

네이밍을 바꾸면 나아지려나 ??

Copy link
Collaborator

Choose a reason for hiding this comment

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

응 네이밍을 조금 변경하면 되지 않을까 싶은데 음... validateMemberHasRefundablePoint() (이게 좋은지는 잘 모르겠는데) refund와 같이 환불의 의미가 들어가면 더 이해하기 쉽지 않을까? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

오 맞어 Payment 에서 환불관련해서 저 메서드를 호출하는건데 refund 키워드가 들어가있어야 확실히 이해하기가 편하겠다! 그 네이밍 좋은데 그걸로할까?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

일단 적용해둘게~

private String nickname;
private String pageName;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy'/'MM'/'dd'/' HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime createdAt;
Copy link
Collaborator

Choose a reason for hiding this comment

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

따로 협의한건 없는데, 그 전에 LocalDateTime을 포맷팅하지않고 바로 보냈을 때, 형식이 별로라고해서 일반적인 형식(?)으로 변경했었어

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

나도 궁금해서 직접 해보면서 이해할려고 테스트 짜봤는데 아니 왜 난 되냐구ㅜㅜ
이 테스트는 통과해!!!
나는 기존에 JPA책 예제에서 해봤어!!
약간 카카오톡 선물하기 느낌으로 이해하면 편할듯?

대략적인 코드

public class Purchasing extends BaseEntity {
    @ManyToOne(fetch = FetchType.LAZY)
    private Member sender;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member receiver;
}
public class Member extends BaseEntity {
    @OneToMany(mappedBy = "receiver")
    List<Purchasing> purchasings = new ArrayList<>();
}

테스트 코드

  @Test
    public void test() {
        Member sender = new Member("sender", "a", "b", "c");
        Member receiver = new Member("receiver", "a", "b", "c");
        Delivery delivery = new Delivery();
        Member saveSender = memberRepository.save(sender);
        Member saveReceiver = memberRepository.save(receiver);
        Delivery saveDelivery = deliveryRepository.save(delivery);

        Purchasing purchasing = new Purchasing();
        purchasing.setSender(saveSender);
        purchasing.setReceiver(saveReceiver);
        purchasing.setDelivery(saveDelivery);


        Purchasing savePurchasing = orderRepository.save(purchasing);

        Assertions.assertEquals("receiver", savePurchasing.getReceiver().getName());
        Assertions.assertEquals("sender", savePurchasing.getSender().getName());
    }

ps.
하~ Order 클래스 이름 때문에 계속 에러 나서 해멤
https://itdar.tistory.com/371
db sql에서 사용하는 애는 클래스 이름으로 사용하면 안되나벼
그래서 Purchasing으로 바꾸고 해봤어!!

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

나도 궁금해서 직접 해보면서 이해할려고 테스트 짜봤는데 아니 왜 난 되냐구ㅜㅜ
이 테스트는 통과해!!!
나는 기존에 JPA책 예제에서 해봤어!!
약간 카카오톡 선물하기 느낌으로 이해하면 편할듯?

대략적인 코드

public class Purchasing extends BaseEntity {
    @ManyToOne(fetch = FetchType.LAZY)
    private Member sender;

    @ManyToOne(fetch = FetchType.LAZY)
    private Member receiver;
}
public class Member extends BaseEntity {
    @OneToMany(mappedBy = "receiver")
    List<Purchasing> purchasings = new ArrayList<>();
}

테스트 코드

  @Test
    public void test() {
        Member sender = new Member("sender", "a", "b", "c");
        Member receiver = new Member("receiver", "a", "b", "c");
        Delivery delivery = new Delivery();
        Member saveSender = memberRepository.save(sender);
        Member saveReceiver = memberRepository.save(receiver);
        Delivery saveDelivery = deliveryRepository.save(delivery);

        Purchasing purchasing = new Purchasing();
        purchasing.setSender(saveSender);
        purchasing.setReceiver(saveReceiver);
        purchasing.setDelivery(saveDelivery);


        Purchasing savePurchasing = orderRepository.save(purchasing);

        Assertions.assertEquals("receiver", savePurchasing.getReceiver().getName());
        Assertions.assertEquals("sender", savePurchasing.getSender().getName());
    }

ps.
하~ Order 클래스 이름 때문에 계속 에러 나서 해멤
https://itdar.tistory.com/371
db sql에서 사용하는 애는 클래스 이름으로 사용하면 안되나벼
그래서 Purchasing으로 바꾸고 해봤어!!

JPA 책 몇쪽이야?
그리고 테스트가 잘못됐어 savePurchasingpurchasing을 그대로 return 해. 그런데 purchasing 은 이미 위에서 코드로 set을 해줬기 때문에 당연히 테스트가 통과할 수 밖에 없어.
Entitymanager em Autowired 시키고, em.flush(); em.clear() 시킨 후에 저장한 Purchasing 을 찾은 후에 그걸로 getReceiver().getName(), getSender().getName() 한 번 해볼레?

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

책은 JPA 단원 마지막에 실습예제 한 거야!!

글구 마침 파즈가 얘기한 것 처럼 테스트해보고 있었는데 통과해!
전체 테스트코드로 올릴게!!

  @Test
    public void test() {
        Member sender = new Member("sender", "a", "b", "c");
        Member receiver = new Member("receiver", "a", "b", "c");
        Delivery delivery = new Delivery();
        Member saveSender = memberRepository.save(sender);
        Member saveReceiver = memberRepository.save(receiver);
        Delivery saveDelivery = deliveryRepository.save(delivery);

        Purchasing purchasing = new Purchasing();
        purchasing.setSender(saveSender);
        purchasing.setReceiver(saveReceiver);
        purchasing.setDelivery(saveDelivery);


        Purchasing savePurchasing = orderRepository.save(purchasing);

        Assertions.assertEquals("receiver", savePurchasing.getReceiver().getName());
        Assertions.assertEquals("sender", savePurchasing.getSender().getName());

        em.flush();
        em.clear();

        Purchasing saved = orderRepository.getById(savePurchasing.getId());
        Assertions.assertEquals("receiver", saved.getReceiver().getName());
        Assertions.assertEquals("sender", saved.getSender().getName());
    }

Co-authored-by: Gyeonglok Kim <goodboy302@naver.com>
@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

책은 JPA 단원 마지막에 실습예제 한 거야!!

글구 마침 파즈가 얘기한 것 처럼 테스트해보고 있었는데 통과해!
전체 테스트코드로 올릴게!!

  @Test
    public void test() {
        Member sender = new Member("sender", "a", "b", "c");
        Member receiver = new Member("receiver", "a", "b", "c");
        Delivery delivery = new Delivery();
        Member saveSender = memberRepository.save(sender);
        Member saveReceiver = memberRepository.save(receiver);
        Delivery saveDelivery = deliveryRepository.save(delivery);

        Purchasing purchasing = new Purchasing();
        purchasing.setSender(saveSender);
        purchasing.setReceiver(saveReceiver);
        purchasing.setDelivery(saveDelivery);


        Purchasing savePurchasing = orderRepository.save(purchasing);

        Assertions.assertEquals("receiver", savePurchasing.getReceiver().getName());
        Assertions.assertEquals("sender", savePurchasing.getSender().getName());

        em.flush();
        em.clear();

        Purchasing saved = orderRepository.getById(savePurchasing.getId());
        Assertions.assertEquals("receiver", saved.getReceiver().getName());
        Assertions.assertEquals("sender", saved.getSender().getName());
    }

이거는 보니깐 중간테이블 낀 형태같은데
Purchasing, Member, Delivery 코드까지 같이 올려줄레?

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

엇 JoinColumn 지정안하면 테이블 생성되는 그건가!!!
그래서 JoinColumn 넣어서 테스트 했는데 잘되!!

일단 Purchasing, Member, Delivery 코드 올릴게!!

@Entity
@Getter
@NoArgsConstructor
public class Purchasing extends BaseEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "sender_id")
    private Member sender;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "receiver_id")
    private Member receiver;

    @OneToMany(mappedBy = "purchasing", cascade = CascadeType.ALL)
    List<OrderItem> orderItems;

    @Temporal(TemporalType.TIMESTAMP)
    private Date orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name ="delivery_id")
    private Delivery delivery;

    public void addOrderItem(OrderItem orderItem) {
        if (orderItems == null) {
            orderItems = new ArrayList<>();
        }

        orderItems.add(orderItem);
        orderItem.setPurchasing(this);
    }

    public void setSender(Member member) {

        if (this.sender != null) {
            this.sender.removeOrder(this);
        }

        this.sender = member;
        member.addOrder(this);
    }

    public void setReceiver(Member member) {

        if (this.receiver != null) {
            this.receiver.removeOrder(this);
        }

        this.receiver = member;
        member.addOrder(this);
    }

    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.setPurchasing(this);
    }
}
@Entity
@Getter
@NoArgsConstructor
public class Member extends BaseEntity {

    @Id @GeneratedValue(strategy = IDENTITY)
    private Long id;

    private String name;

    private String city;
    private String street;
    private String zipcode;

    @OneToMany(mappedBy = "receiver")
    List<Purchasing> purchasings = new ArrayList<>();

    public Member(String name, String city, String street, String zipcode) {
        this.name = name;
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }

    public void addOrder(Purchasing purchasing) {
        purchasings.add(purchasing);
    }

    public void removeOrder(Purchasing purchasing) {
        this.purchasings.remove(purchasing);
    }
}
@Entity
@NoArgsConstructor
public class Delivery {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String city;
    private String street;
    private String zipcode;


    @OneToOne(mappedBy = "delivery")
    private Purchasing purchasing;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus deliveryStatus;

    public void setPurchasing(Purchasing purchasing) {
        this.purchasing = purchasing;
    }
}

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

엇 JoinColumn 지정안하면 테이블 생성되는 그건가!!!
그래서 JoinColumn 넣어서 테스트 했는데 잘되!!

일단 Purchasing, Member, Delivery 코드 올릴게!!

내가 따로 생성해서 돌려본 도메인이랑 똑같은데 왜 난 안되지???????????? 싶었는데 디버깅으로 확인해서 쿼리가 바로 안나가고 그래서 막 그랬네... 근데 이거 문제점이 있어. 암만 생각해도 1:n이 아니고 말이 안되거든 ?? 그리고 디버그 찍어보면 찾은 객체 내부에 null로만 가득 차있어

수리 혹시 Purchasing 필드에 int 로 아무거나 값주고 생성자에서 그 값 생성시킨 후에 찾은 Purchasing에 getter 호출해서 값 잘나오는지 확인해볼레??

image
나는 이렇게 진행했어 연관관계는 우리 프로젝트와 동일하구.
donation1.getDonator().getName(), donation1.getCreator().getName() 시에 그 때 쿼리를 호출하기는 해
근데 바로 밑의 donation1.getAmount() 에서는 아무것도 출력되지않아. 쿼리 호출도 당연히 안하고 객체가 아니니깐 레이지로딩되지 않으니깐.
image
내부에는 이렇게 null로 가득차있어!!

수리 한 번 확인해봐! 이것도 되는지! Donation Member가 n:1인데 Donation 엔티티 내부에 Member가 2개다?? 이것부터가 n:1이 아닌거긴한데.. 일단 확인부터 고고

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

image

getTest해도 이렇게 부르는거 맞나?

Hibernate: 
    select
        purchasing0_.id as id1_6_0_,
        purchasing0_.create_date as create_d2_6_0_,
        purchasing0_.last_modified_date as last_mod3_6_0_,
        purchasing0_.delivery_id as delivery7_6_0_,
        purchasing0_.order_date as order_da4_6_0_,
        purchasing0_.receiver_id as receiver8_6_0_,
        purchasing0_.sender_id as sender_i9_6_0_,
        purchasing0_.status as status5_6_0_,
        purchasing0_.test as test6_6_0_ 
    from
        purchasing purchasing0_ 
    where
        purchasing0_.id=?

혹시 몰라서 test get하는거 말고 주석처리해쓰

 @Test
    public void test() {
        Member sender = new Member("sender", "a", "b", "c");
        Member receiver = new Member("receiver", "a", "b", "c");
        Delivery delivery = new Delivery();
        Member saveSender = memberRepository.save(sender);
        Member saveReceiver = memberRepository.save(receiver);
        Delivery saveDelivery = deliveryRepository.save(delivery);

        Purchasing purchasing = new Purchasing();
        purchasing.setSender(saveSender);
        purchasing.setReceiver(saveReceiver);
        purchasing.setDelivery(saveDelivery);


        Purchasing savePurchasing = orderRepository.save(purchasing);

       /* Assertions.assertEquals("receiver", savePurchasing.getReceiver().getName());
        Assertions.assertEquals("sender", savePurchasing.getSender().getName());
*/
        em.flush();
        em.clear();

        Purchasing saved = orderRepository.getById(savePurchasing.getId());
       /* Assertions.assertEquals("receiver", saved.getReceiver().getName());
        Assertions.assertEquals("sender", saved.getSender().getName());*/

        Assertions.assertEquals(1, saved.getTest());
    }

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

Purchasing purchasing = new Purchasing(3);

...
public Puchasing(int value){
this.value = value;
}

요렇게 해서!
image

그리고 saved 빼온거를 디버깅으로 위에처럼 한 번 확인해봐바!

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

파.. 파즈!!!!!!!!!!! null이야;

image

근대 왜 테스트는 통과하는거지?

image

@Be-poz
Copy link
Collaborator Author

Be-poz commented Sep 6, 2021

그건 아마 그때 딱 쿼리 찍어서 그대로 반환해줘서 그런 것 같어~! 원래대로라면 객체안에 집어넣고 그 값을 끄집어 내야하는데 PurchasingMember를 2개 갖고있어서 객체 조합을 제대로 못시켜주는듯! 1:n이 아니라 2:n 이니깐!!
결론은... Donation에 2개의 Member는 불가능한걸로~~ 😢

@DWL5
Copy link
Collaborator

DWL5 commented Sep 6, 2021

파즈 답변고마워~
이래서 디버깅을 해야하는 구나..

왜 헷갈리게 get하면 값이 나오고 난리여 ㅜ

Copy link
Collaborator

@Joyykim Joyykim left a comment

Choose a reason for hiding this comment

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

고생했어!!!

Context context = new Context();
context.setVariable("page_url", CREATOR_PREFIX_DOMAIN + payment.getItemName());
Copy link
Collaborator

Choose a reason for hiding this comment

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

이제 쓰이지 않는 CREATOR_PREFIX_DOMAIN 상수는 제거하자

Comment on lines 172 to 175
public boolean isAfterRefundGuaranteeDuration() {
return LocalDateTime.now()
.isAfter(getCreatedAt().plusDays(7));
}
Copy link
Collaborator

@Joyykim Joyykim Sep 7, 2021

Choose a reason for hiding this comment

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

Suggested change
public boolean isAfterRefundGuaranteeDuration() {
return LocalDateTime.now()
.isAfter(getCreatedAt().plusDays(7));
}
public boolean isAfterRefundGuaranteeDuration() {
LocalDate createdDate = getCreatedAt().toLocalDate();
LocalDate nowDate = LocalDate.now();
return nowDate.isAfter(createdDate.plusDays(6));
}

환불 규정은 7일이내야. 산 날짜로부터 7일 이내에는 0시 1분이던 23시 59분이던 시간에 관계없이 환불할 수 있어야 해.

예)

  • 1일 1시 0분 구매했다면
  • 7일 23시 59분 까지 환불 가능해야함
  • 8일 0시 0분 부터는 환불 불가해야함

지금은 LocalDateTime로 비교하고 있어서 같은 날짜라도 시간에 따라 환불 가능/불가가 달라져.
그리고 plusDays()를 6으로 한건 1일에 구매했다면 7일까지는 환불이 가능해야해서야.
아래는 내가 테스트해본 코드야.

스크린샷 2021-09-07 오후 12 47 44

Copy link
Collaborator

Choose a reason for hiding this comment

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

직접 테스트해볼거면 이 코드 써~

import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.time.LocalDateTime;

public class LocalDateTimeTest {
    @Test
    void name() {
        int year = 2020;
        int month = 1;

        // 1일 1시 0분 구매
        LocalDateTime createdAt = LocalDateTime.of(year, month, 1, 1, 0);

        // 7일 23시 59분 까지 환불 가능
        LocalDateTime now = LocalDateTime.of(year, month, 7, 23, 59);
        assert !isAfter2(createdAt, now);

        // 8일 0시 0분 부터는 환불 불가해야함
        now = LocalDateTime.of(year, month, 8, 0, 0);
        assert isAfter2(createdAt, now);

        // 현재 코드(AS-IS)는 8일 1시 1분 부터 환불 불가임
        now = LocalDateTime.of(year, month, 8, 1, 1);
        assert isAfter2(createdAt, now);
    }

    /** AS-IS */
    private boolean isAfter1(LocalDateTime createdAt, LocalDateTime now) {
        return now.isAfter(createdAt.plusDays(7));
    }

    /** TO-BE */
    private boolean isAfter2(LocalDateTime createdAt, LocalDateTime now) {
        LocalDate createdDate = createdAt.toLocalDate();
        LocalDate nowDate = now.toLocalDate();

        return nowDate.isAfter(createdDate.plusDays(6));
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature 새로 추가될 기능 refactor 기능에 변경이 없는 코드 리팩터링 server 백엔드 관련 이슈
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants