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: 영수증 검증 및 구독 상태 조회 API 구현 #219

Merged
merged 16 commits into from
Aug 19, 2024

Conversation

ahnsugyeong
Copy link
Member

@ahnsugyeong ahnsugyeong commented Aug 5, 2024

🚀 개요

  1. Client App에서 결제한 영수증 데이터를 서버로 전달

  2. 전달받은 영수증 데이터를 각 플랫폼에 검증 요청(현재는 구글만)

  • googleInAppPurchaseVerify: request로 영수증 정보 들어오면 google play 라이브러리로 검증 요청 → paymentState가 0이면 검증 성공
  1. 검증 결과에 따라서 서버에서 주문정보를 업데이트
  • 이미 활성 된 구독 정보 존재하는지 확인 → 있으면 exception
  • 없으면 SubscriptionPurchase에서 expiryTimeMillis 받아와서 저장
  1. 주문정보 업데이트 후 Client App으로 결과를 리턴

⏳ 작업 내용

#218

📝 논의사항

  1. 구독이 만료되면 해당 객체를 삭제하는 것이 아니라, 상태를 EXPIRED로 갱신하도록 로직을 구성했습니다. 이렇게 되면 사용자와 구독의 관계가 1:N이 되는데 어떻게 생각하시는지 궁금합니다

  2. 구독 정보를 조회할 때마다 만료된 구독인지 확인하고 상태를 갱신하는 로직을 추가했는데, 스케줄링으로 추가적으로 처리하는 것에 대해 어떻게 생각하시는지 궁금합니다

  3. IDE에서 자동으로 정렬이 되어버려서 불필요한 파일들이 수정되었는데 시간 날 때 Spotless 같은 거 도입해 봐도 좋을 것 같습니다 😅😅

@ahnsugyeong ahnsugyeong requested review from swa07016 and CYY1007 August 5, 2024 14:28
@ahnsugyeong ahnsugyeong self-assigned this Aug 5, 2024
@ahnsugyeong ahnsugyeong linked an issue Aug 5, 2024 that may be closed by this pull request
5 tasks
@ahnsugyeong ahnsugyeong added the ✨ feature New feature or request label Aug 5, 2024
SubscriptionPurchase purchase = googleInAppPurchaseVerify(request.getPackageName(), request.getProductId(), request.getPurchaseToken());

// 활성된 구독 존재 여부 확인
boolean activeSubscriptionExists = subscriptionQueryAdapter.findByMemberId(member.getId()).stream()
Copy link
Member

Choose a reason for hiding this comment

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

exists 쿼리로 변경해봐도 좋을 것 같습니다!

다량의 데이터와 함께 두 쿼리의 성능을 비교해보면 더 좋을 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

10만개의 데이터를 기준으로 비교한 결과,
기존: 1.1ms, exists 쿼리: 0.53ms
로 약 2배 단축된 것을 확인할 수 있었습니다. 감사합니다!

@Transactional
public void createSubscription(final Member member, final SubscriptionRequest.ReceiptDTO request) {
try {
SubscriptionPurchase purchase = googleInAppPurchaseVerify(request.getPackageName(), request.getProductId(), request.getPurchaseToken());
Copy link
Member

Choose a reason for hiding this comment

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

외부 서버와의 네트워크 통신은 트랜잭션 밖에서 수행되면 좋을 것 같습니다!

커넥션을 오래 점유하는 등 여러 문제점이 있습니다

트랜잭션 범위를 최소화하면 좋을 것 같아요:)

Copy link
Member Author

Choose a reason for hiding this comment

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

덕분에 AOP와 트랜잭션에 대해 더 알아갈 수 있었습니다. 감사합니다! 👍🏻👍🏻
서비스 레이어에서 영수증 검증 메서드(외부 API 호출)와 DB에 구독 객체를 저장하는 메서드를 하나의 메서드로 호출하려고 했는데, self-invocation 문제 때문에 복잡해지더라구요.
그래서 Controller에서 각각의 메서드를 개별적으로 호출하는 방식으로 작성해봤는데, 성훈님이 리뷰에서 의도하신 것과 일치하는지 확인 부탁드려요!

Copy link
Member

Choose a reason for hiding this comment

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

넵! 잘 반영해주셨습니다:)

public SubscriptionResponse.SubscriptionDTO getActiveSubscriptionByMemberId(Member member, final Long memberId) {
validateMember(member, memberId);

Subscription subscription = subscriptionQueryAdapter.findAllByMemberId(memberId).stream()
Copy link
Member

Choose a reason for hiding this comment

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

해당 멤버의 모든 구독 정보 목록을 메모리에 들고오지 않고 처리할 수 있는 방향으로 쿼리를 고민해보셔도 좋을 것 같아요!

private final SubscriptionService subscriptionService;

@Operation(summary = "06-01 Subscription 💳 영수증 검증 및 구독하기 V2", description = "결제 영수증을 검증하고, 구독 정보를 갱신하는 API입니다.")
@PostMapping("/subscriptions")
Copy link
Member

Choose a reason for hiding this comment

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

/subscriptions 를 클래스 상단 RequestMapping에 포함해도 될 것 같아요!

@@ -68,7 +65,14 @@ public enum ErrorCode {
BAD_LAST_MESSAGE_ROLE(HttpStatus.BAD_REQUEST, "CHATTING_003", "마지막 메시지의 역할이 user가 아닙니다."),
CAN_NOT_EMPTY_CONTENT(HttpStatus.BAD_REQUEST, "CHATTING_004", "content가 비어있습니다."),
NOT_FOUND_ROLE(HttpStatus.BAD_REQUEST, "CHATTING_005", "해당하는 role을 찾을 수 없습니다."),
NOT_FOUND_MODEL(HttpStatus.BAD_REQUEST, "CHATTING_006", "해당하는 mode을 찾을 수 없습니다.");
NOT_FOUND_MODEL(HttpStatus.BAD_REQUEST, "CHATTING_006", "해당하는 mode을 찾을 수 없습니다."),
Copy link
Member

Choose a reason for hiding this comment

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

mode -> model로 변경부탁드립니다!

이건 프로젝트 초기부터 오타로 되어있던 것 같네요 ㅜㅜ

@swa07016
Copy link
Member

swa07016 commented Aug 5, 2024

  1. 구독이 만료되면 해당 객체를 삭제하는 것이 아니라, 상태를 EXPIRED로 갱신하도록 로직을 구성했습니다. 이렇게 되면 사용자와 구독의 관계가 1:N이 되는데 어떻게 생각하시는지 궁금합니다
    → 괜찮다고 생각합니다!

  2. 구독 정보를 조회할 때마다 만료된 구독인지 확인하고 상태를 갱신하는 로직을 추가했는데, 스케줄링으로 추가적으로 처리하는 것에 대해 어떻게 생각하시는지 궁금합니다
    → 조회할때만 갱신해도 될 것 같은데, 스케줄링을 고려하셨던 이유가 궁금합니다!

  3. IDE에서 자동으로 정렬이 되어버려서 불필요한 파일들이 수정되었는데 시간 날 때 Spotless 같은 거 도입해 봐도 좋을 것 같습니다 😅😅
    → Spotless는 이전에 도입을 했었는데, 멀티모듈 분리 이후 동작하지 않는 이슈가 있어서 용욱님께서 해결해주시면 감사하겠습니다 :) @CYY1007

@ahnsugyeong
Copy link
Member Author

초기 의도는 조회 API 호출 시 활성화된 구독에 대해서만 만료 여부를 확인하고 상태를 갱신하려고 했습니다!
이렇게 하면 스케줄링으로 미리 만료 처리된 구독은 추가적인 확인 없이 바로 처리할 수 있다고 생각해서, 사용자가 적은 밤 시간대에 스케줄링을 돌려 한 번에 갱신하는 방법을 고안해봤습니다.
근데 코드 작성하다보니 해당 부분을 놓치기도 했고, 그 하나의 로직을 위해 이 그렇게 하지 않아도 될 것 같다는 생각이 들어 팀원분들의 의견이 궁금했습니다..ㅎㅎ

@ahnsugyeong ahnsugyeong merged commit bd507dc into develop Aug 19, 2024
@ahnsugyeong ahnsugyeong deleted the feature/218 branch August 19, 2024 07:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

🚀 [feature] 영수증 검증 및 구독 상태 조회 API 구현
2 participants