Skip to content

[feat] 정산, 결제 실패 로직 추가 / 스케줄 삭제 API 구현 / 정산 관련 API 응답 수정 #125

Merged
gkdudans merged 9 commits intodevelopfrom
feat/schedule/gkdudans
Aug 18, 2025
Merged

[feat] 정산, 결제 실패 로직 추가 / 스케줄 삭제 API 구현 / 정산 관련 API 응답 수정 #125
gkdudans merged 9 commits intodevelopfrom
feat/schedule/gkdudans

Conversation

@gkdudans
Copy link
Contributor

@gkdudans gkdudans commented Aug 14, 2025

#️⃣ Issue Number

📝 요약(Summary)

  • 스케줄 삭제 API 구현
  • 정산 관련 API 응답 수정

🛠️ PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • CSS 등 사용자 UI 디자인 변경
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📸스크린샷 (선택)

💬 공유사항 to 리뷰어

✅ PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

Summary by CodeRabbit

  • New Features

    • 정기 모임 삭제 기능 추가(리더 전용, 시작 전·준비 상태만 가능).
    • 결제 실패 보고 API 재활성화.
    • 내 모임 목록에 ‘미정산 내역 존재’ 여부 표시.
  • Bug Fixes

    • 스케줄 시간 유효성 강화: 현재 이후 시각만 선택 가능.
    • 결제 처리 중 상태 안내로 중복 처리 방지 및 안정성 향상.
    • 지갑 내역 기본 조회 시 ‘완료’ 상태만 표시하도록 필터링 정교화.
  • Chores

    • 정기 작업 스케줄링 활성화(백그라운드 처리).

@gkdudans gkdudans requested a review from NamYeonW00 August 14, 2025 07:26
@gkdudans gkdudans self-assigned this Aug 14, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 14, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

결제 확정/실패 흐름과 정산/지갑 연동을 재설계하고, 스케줄 삭제 API를 추가했습니다. 검색 API는 내 모임 목록 응답을 DTO로 확장했습니다. 일부 JPA 연관의 cascade를 제거했으며, 스케줄링을 애플리케이션에 활성화했습니다. 여러 레포지토리/DTO/에러코드 시그니처가 변경되었습니다.

Changes

Cohort / File(s) Summary
JPA cascade 정리
src/main/java/com/example/onlyone/domain/chat/entity/ChatRoom.java, .../domain/chat/entity/Message.java
Club, User에 대한 @manytoone에서 cascade 제거. LAZY 유지.
결제 API/플로우 개편
.../payment/controller/PaymentController.java, .../payment/service/PaymentService.java, .../payment/repository/PaymentRepository.java, .../payment/entity/Payment.java, .../payment/entity/Method.java
failPayment 엔드포인트 복구, confirm/reportFail 분리. claimPayment 도입으로 동시성/멱등 처리. Repository에서 paymentKey 기반 메서드 제거. Payment.updateOnConfirm 시그니처 변경(결제키/열거형/지갑거래 연결). 사소한 포맷 수정(Method).
정산/지갑 연동 개선
.../wallet/repository/WalletTransactionRepository.java, .../wallet/service/WalletService.java, .../settlement/repository/UserSettlementRepository.java, .../settlement/service/SettlementService.java, .../domain/user/dto/response/MySettlementDto.java
거래 조회에 상태 필터 추가. 실패 트랜잭션 생성 시 시그니처 변경 및 상태 가드/Transfer 연결. UserSettlementRepo 메서드 추가/프로젝션 변경/수정 쿼리 추가. SettlementService에 롤백 후 실패 로깅 등록, 트랜잭션/크론 변경, 지갑 처리 경로 재구성. MySettlementDto 신설.
스케줄 기능 확장
.../schedule/controller/ScheduleController.java, .../schedule/service/ScheduleService.java, .../schedule/entity/Schedule.java, .../schedule/dto/request/ScheduleRequestDto.java, .../global/exception/ErrorCode.java
스케줄 삭제 API 추가 및 서비스 검증/삭제 로직 구현(UserSchedule/ChatRoom 검증 포함). Schedule→UserSchedule 일대다 추가(cascade+orphanRemoval). scheduleTime에 @FutureOrPresent 추가. 삭제/권한/진행중 결제 코드 등 에러코드 확장. createSchedule에서 @Valid 제거.
검색 응답 확장
.../search/service/SearchService.java, .../search/dto/response/MyMeetingListResponseDto.java
getMyClubs 반환형을 MyMeetingListResponseDto로 변경. 미정산 존재 여부 플래그 계산 추가(UserSettlementRepository 사용). DTO 신설.
스케줄링 활성화
src/main/java/com/example/onlyone/OnlyoneApplication.java
@EnableScheduling 추가.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant PaymentController
  participant PaymentService
  participant TossClient
  participant User/Wallet
  participant Repo

  Client->>PaymentController: POST /payments/confirm
  PaymentController->>PaymentService: confirm(req)
  PaymentService->>Repo: claimPayment(orderId, amount)
  alt existing/in-progress/done
    Repo-->>PaymentService: Payment
  else create
    Repo-->>PaymentService: new Payment or conflict handle
  end
  PaymentService->>TossClient: confirmPayment(req)
  TossClient-->>PaymentService: response(status, method, paymentKey)
  PaymentService->>User/Wallet: load user + wallet, add balance, create WalletTransaction(CHARGE)
  PaymentService->>Repo: payment.updateOnConfirm(paymentKey, Status, Method, WalletTransaction)
  PaymentService-->>PaymentController: ok
  PaymentController-->>Client: 200
Loading
sequenceDiagram
  participant TossWebhook/Client
  participant PaymentController
  participant PaymentService
  participant Repo
  participant User/Wallet

  TossWebhook/Client->>PaymentController: POST /payments/fail
  PaymentController->>PaymentService: reportFail(req)
  PaymentService->>Repo: find/create Payment by orderId
  alt already DONE or same paymentKey
    PaymentService-->>PaymentController: return
  else
    PaymentService->>User/Wallet: load references
    PaymentService->>Repo: record WalletTransaction(FAILED), cancel Payment
  end
  PaymentController-->>TossWebhook/Client: 200
Loading
sequenceDiagram
  participant Client
  participant ScheduleController
  participant ScheduleService
  participant ClubRepo/ScheduleRepo
  participant ChatRepo

  Client->>ScheduleController: DELETE /clubs/{clubId}/schedules/{scheduleId}
  ScheduleController->>ScheduleService: deleteSchedule(clubId, scheduleId)
  ScheduleService->>ClubRepo/ScheduleRepo: validate club & schedule(READY, future)
  ScheduleService->>ScheduleRepo: verify leader via UserSchedule
  ScheduleService->>ChatRepo: find ChatRoom for schedule
  ScheduleService->>ClubRepo/ScheduleRepo: remove from club, delete ChatRoom, delete Schedule
  ScheduleService-->>ScheduleController: void
  ScheduleController-->>Client: 200
Loading
sequenceDiagram
  participant User
  participant SettlementService
  participant WalletService
  participant Repo
  participant TxManager

  User->>SettlementService: updateUserSettlement(clubId, scheduleId)
  SettlementService->>Repo: load wallets, settlement, validations
  SettlementService->>WalletService: record outgoing/incoming ledger
  alt success
    WalletService-->>SettlementService: ok
    SettlementService->>Repo: persist & notify
  else failure
    SettlementService->>TxManager: register rollback hook
    TxManager-->>WalletService: onRollback -> createFailedWalletTransactions(...)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Wallet

Suggested reviewers

  • doehy
  • NamYeonW00

Poem

토스 바람 불어오니 결제는 한 줄로,
쿵짝 지갑 장부마다 상태가 반짝여요.
토끼는 스케줄 지우고 별빛을 정리하며,
정산은 롤백 뒤에도 기록을 놓치지 않죠.
삐빅- 성공! 삐빅- 실패! 오늘도 깔끔한 힙합-캐시플로우 🐇💳✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 53e0487 and ded80fb.

📒 Files selected for processing (15)
  • src/main/java/com/example/onlyone/OnlyoneApplication.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/controller/PaymentController.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/entity/Method.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/entity/Payment.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/repository/PaymentRepository.java (0 hunks)
  • src/main/java/com/example/onlyone/domain/payment/service/PaymentService.java (3 hunks)
  • src/main/java/com/example/onlyone/domain/schedule/dto/request/ScheduleRequestDto.java (2 hunks)
  • src/main/java/com/example/onlyone/domain/schedule/service/ScheduleService.java (2 hunks)
  • src/main/java/com/example/onlyone/domain/search/dto/response/MyMeetingListResponseDto.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/search/service/SearchService.java (3 hunks)
  • src/main/java/com/example/onlyone/domain/settlement/repository/UserSettlementRepository.java (5 hunks)
  • src/main/java/com/example/onlyone/domain/settlement/service/SettlementService.java (4 hunks)
  • src/main/java/com/example/onlyone/domain/wallet/repository/WalletTransactionRepository.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/wallet/service/WalletService.java (5 hunks)
  • src/main/java/com/example/onlyone/global/exception/ErrorCode.java (2 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/schedule/gkdudans

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

NamYeonW00
NamYeonW00 previously approved these changes Aug 14, 2025
Copy link
Contributor

@NamYeonW00 NamYeonW00 left a comment

Choose a reason for hiding this comment

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

고생하셨습니당~!!

@gkdudans gkdudans dismissed NamYeonW00’s stale review August 14, 2025 07:26

The merge-base changed after approval.

NamYeonW00
NamYeonW00 previously approved these changes Aug 14, 2025
Copy link
Contributor

@NamYeonW00 NamYeonW00 left a comment

Choose a reason for hiding this comment

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

굿~~

@gkdudans gkdudans requested review from NamYeonW00 and doehy August 14, 2025 07:28
doehy
doehy previously approved these changes Aug 14, 2025
@doehy
Copy link
Contributor

doehy commented Aug 14, 2025

래빗 안녕

@gkdudans gkdudans dismissed stale reviews from doehy and NamYeonW00 August 14, 2025 07:31

The merge-base changed after approval.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🔭 Outside diff range comments (2)
src/main/java/com/example/onlyone/domain/payment/service/PaymentService.java (2)

92-100: 외부 API 호출을 트랜잭션 경계 밖으로 이동 + 예외 로깅 강화

클래스 레벨 @transactional로 인해 네트워크 호출(tossPaymentClient.confirmPayment)이 트랜잭션 안에서 실행됩니다. 장시간 트랜잭션 유지로 인한 잠금/타임아웃 리스크를 피하기 위해 외부 호출은 트랜잭션 밖에서 수행하고, DB 반영은 별도 트랜잭션으로 분리하는 것을 권장합니다. 또한 예외 매핑 전에 진단 로그를 남겨 운영 가시성을 높여주세요.

로깅 추가 예(간단 적용):

         response = tossPaymentClient.confirmPayment(req);
+        if (response.getOrderId() == null || !response.getOrderId().equals(req.getOrderId())) {
+            log.warn("OrderId mismatch between request and response. req={}, res={}", req.getOrderId(), response.getOrderId());
+            throw new CustomException(ErrorCode.INVALID_PAYMENT_INFO);
+        }
     } catch (FeignException.BadRequest e) {
-        throw new CustomException(ErrorCode.INVALID_PAYMENT_INFO);
+        log.warn("Toss confirm bad request: orderId={}, message={}", req.getOrderId(), e.getMessage());
+        throw new CustomException(ErrorCode.INVALID_PAYMENT_INFO);
     } catch (FeignException e) {
-        throw new CustomException(ErrorCode.TOSS_PAYMENT_FAILED);
+        log.error("Toss confirm failed: orderId={}, message={}", req.getOrderId(), e.getMessage());
+        throw new CustomException(ErrorCode.TOSS_PAYMENT_FAILED);
     } catch (Exception e) {
-        throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
+        log.error("Toss confirm unexpected error: orderId={}, message={}", req.getOrderId(), e.getMessage(), e);
+        throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
     }

트랜잭션 분리 예시(참고용, 별도 메서드로 분리):

// confirm(...)은 @Transactional을 적용하지 않거나, NOT_SUPPORTED로 지정
public ConfirmTossPayResponse confirm(ConfirmTossPayRequest req) {
    // 1) 외부 호출
    ConfirmTossPayResponse response = tossPaymentClient.confirmPayment(req);
    // 2) DB 반영 (별도 트랜잭션)
    persistAfterConfirm(req, response);
    return response;
}

@Transactional // 또는 REQUIRES_NEW
void persistAfterConfirm(ConfirmTossPayRequest req, ConfirmTossPayResponse response) {
    // 현재 confirm(...)의 DB 갱신 로직을 이동
}

104-111: 보안/무결성: 잔액 업데이트에 요청값(req) 대신 승인 응답 금액 사용

지갑 반영 금액을 클라이언트가 보낸 req.getAmount()로 신뢰하면 조작 여지가 생깁니다. 토스 승인 응답의 총액으로 반영하세요.

-        int amount = Math.toIntExact(req.getAmount());
+        int amount = Math.toIntExact(response.getTotalAmount());
         // 포인트 업데이트
         wallet.updateBalance(wallet.getBalance() + amount)

추가로, 방어적으로 req.getAmount()response.getTotalAmount()가 다를 경우 로그/차단하는 검사도 고려하세요.

🧹 Nitpick comments (13)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementDto.java (1)

22-29: 주석 처리된 코드를 제거하거나 완성해주세요.

주석 처리된 factory 메서드가 미완성 상태로 남아있습니다. 사용하지 않는다면 제거하고, 필요하다면 완성해주세요.

-//    public static MySettlementDto from(Page<MySettlementDto> userSettlement) {
-//        return MySettlementDto.builder()
-//                .clubId(userSettlement.getSettlement().getSchedule().getClub().getClubId())
-//                .settlementId(userSettlement.getSettlement().getSettlementId())
-//                .amount(userSettlement.getSettlement().getSchedule().getCost())
-//                .mainImage(userSettlement.getSettlement().getSchedule().getClub().getClubImage())
-//                .build();
-//    }
src/main/java/com/example/onlyone/global/exception/ErrorCode.java (1)

56-58: 새로 추가된 스케줄 관련 에러 코드가 적절합니다.

스케줄 삭제 기능에 필요한 에러 코드들이 잘 정의되었습니다. 다만 두 개의 메시지에서 쉼표 오타가 있습니다.

-    MEMBER_CANNOT_MODIFY_SCHEDULE(403, "SCHEDULE_403_1", "리더만 정기 모임을 수정할 수 있습니다,"),
-    MEMBER_CANNOT_DELETE_SCHEDULE(403, "SCHEDULE_403_2", "리더만 정기 모임을 삭제할 수 있습니다,"),
+    MEMBER_CANNOT_MODIFY_SCHEDULE(403, "SCHEDULE_403_1", "리더만 정기 모임을 수정할 수 있습니다."),
+    MEMBER_CANNOT_DELETE_SCHEDULE(403, "SCHEDULE_403_2", "리더만 정기 모임을 삭제할 수 있습니다."),
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementResponseDto.java (1)

18-18: 접근 제한자 누락을 수정해주세요.

mySettlementList 필드에 접근 제한자가 누락되었습니다.

-    List<MySettlementDto> mySettlementList;
+    private List<MySettlementDto> mySettlementList;
src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java (1)

77-83: 삭제 성공 시 204 No Content가 더 RESTful합니다

삭제 요청이 성공했을 때 본문이 없는 204를 반환하는 것이 일반적입니다. 현재 공통 응답 규약(CommonResponse)을 유지해야 한다면 200도 허용되지만, 가능하다면 다음처럼 204로 단순화하는 것을 권장합니다.

-        return ResponseEntity.ok(CommonResponse.success(null));
+        return ResponseEntity.noContent().build();
src/main/java/com/example/onlyone/domain/user/controller/UserController.java (1)

105-110: 과도한 요청 방지를 위해 페이지 크기 상한을 두세요

클라이언트가 size를 크게 지정해 과부하를 유발할 수 있습니다. 서비스 단에서 size 상한(예: 50)을 적용해 방어적으로 동작하도록 권장합니다.

적용 예시:

   @GetMapping("/settlement")
   public ResponseEntity<?> getMySettlementList(@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC)
                                                Pageable pageable) {
-    return ResponseEntity.ok(CommonResponse.success(userService.getMySettlementList(pageable)));
+    int maxSize = 50;
+    Pageable capped = org.springframework.data.domain.PageRequest.of(
+        pageable.getPageNumber(),
+        Math.min(pageable.getPageSize(), maxSize),
+        pageable.getSort()
+    );
+    return ResponseEntity.ok(CommonResponse.success(userService.getMySettlementList(capped)));
   }

추가로 필요한 import:

import org.springframework.data.domain.PageRequest;
src/main/java/com/example/onlyone/domain/schedule/entity/Schedule.java (2)

60-62: 중복 방지를 위해 Set 사용을 고려하세요

참여자 연관(UserSchedule)은 논리적으로 중복이 없어야 합니다. 컬렉션 타입을 Set으로 바꾸면 중복 방지와 contains/remove 성능이 개선됩니다. 대규모 삭제 시에도 의미가 명확합니다.

-    @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true)
-    private List<UserSchedule> userSchedules = new ArrayList<>();
+    @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL, orphanRemoval = true)
+    private Set<UserSchedule> userSchedules = new HashSet<>();

해당 변경에 필요한 import 수정:

-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;

양방향 연관을 사용할 계획이라면 helper 메서드(add/remove)를 두어 양쪽 일관성을 보장하는 것도 추천합니다.


10-12: Set 전환 시 import 정리 필요

컬렉션 타입을 Set으로 바꿀 경우 HashSet/Set import로 교체하세요.

-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
src/main/java/com/example/onlyone/domain/settlement/repository/UserSettlementRepository.java (3)

28-37: 불필요한 조인 제거로 쿼리 간소화 제안

첫 번째 조회에서는 sch를 사용하지 않으므로 join st.schedule sch는 성능상 불필요합니다.

         from UserSettlement us
         join us.user u
         join us.settlement st
-        join st.schedule sch
         where us.settlement = :settlement

51-74: 정렬 기준 보강 제안: 완료 시간 우선 정렬

요청(REQUESTED)과 완료(COMPLETED)를 함께 보여줄 때, createdAt만으로 정렬하면 완료 항목의 최신성이 반영되지 않을 수 있습니다. 완료 시간(completedTime) 존재 시 우선 사용하도록 정렬을 보강하는 것을 권장합니다.

-    order by us.createdAt desc
+    order by coalesce(us.completedTime, us.createdAt) desc

주의: completedTime 컬럼이 null 허용이어야 하며 JPQL의 coalesce 지원 하에 동작합니다.


103-103: 미정산 존재 여부 확인용 메서드 보강 제안

현재 existsByUserAndSettlementStatusNot(user, COMPLETED)는 CANCELED/FAILED까지 포함시킬 수 있습니다. UI 요구가 “요청(REQUESTED)만 미정산”이라면 다음 메서드 추가를 권장합니다.

     Optional<UserSettlement> findByUserAndSchedule(
             @Param("user") User user,
             @Param("schedule") Schedule schedule
     );
-    boolean existsByUserAndSettlementStatusNot(User user, SettlementStatus settlementStatus);
+    boolean existsByUserAndSettlementStatusNot(User user, SettlementStatus settlementStatus);
+    boolean existsByUserAndSettlementStatus(User user, SettlementStatus settlementStatus);

SearchService.getMyClubs()에서 REQUESTED만 검사하도록 변경하면 의미가 더 명확해집니다(별도 코멘트 참고).

src/main/java/com/example/onlyone/domain/settlement/service/SettlementService.java (3)

122-123: 불필요한 주석 코드 제거

중복된 getCurrentUser() 호출 주석은 혼란을 줄 수 있어 제거를 권장합니다.

-//        User user = userService.getCurrentUser();

166-167: TODO 처리 권장

정산 알림 TODO는 함수 내부에 남아있습니다. 현재 성공 시점에 createNotification은 호출되고 있으므로, TODO는 제거하거나 별도 후처리(예: 비동기 재전송, 실패 큐 등) 로직을 추가해 주세요.


169-204: 실패 기록 보존을 위한 트랜잭션 분리 권장 (REQUIRES_NEW + 별도 빈)

현재 실패 시 saveWalletTransactions도 동일 트랜잭션에서 수행되어 예외 재발생 시 함께 롤백됩니다. 실패 로그를 “항상” 보존하려면 REQUIRES_NEW 트랜잭션에서 분리 커밋되어야 합니다. 단, 동일 클래스 내 self-invocation은 프록시를 우회하므로 효과가 없습니다. 별도 컴포넌트로 분리해 호출하거나 ApplicationEvent/Outbox 패턴을 고려해 주세요.

예시(별도 빈):

@Service
public class LedgerService {
  private final WalletTransactionRepository walletTransactionRepository;
  private final TransferRepository transferRepository;

  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void saveWalletTransactions(Wallet wallet, Wallet leaderWallet, int amount,
                                     WalletTransactionStatus status, UserSettlement userSettlement) {
    // 현재 saveWalletTransactions 구현 그대로 이동
  }
}

그리고 SettlementService에서는 LedgerService를 주입받아 호출합니다. 이렇게 하면 실패 시에도 트랜잭션 로그가 남습니다.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 14acaaf and 53e0487.

📒 Files selected for processing (19)
  • src/main/java/com/example/onlyone/domain/chat/entity/ChatRoom.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/chat/entity/Message.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/controller/PaymentController.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/repository/PaymentRepository.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/payment/service/PaymentService.java (4 hunks)
  • src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/schedule/entity/Schedule.java (2 hunks)
  • src/main/java/com/example/onlyone/domain/schedule/service/ScheduleService.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/search/dto/response/MyMeetingListResponseDto.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/search/service/SearchService.java (3 hunks)
  • src/main/java/com/example/onlyone/domain/settlement/controller/SettlementController.java (0 hunks)
  • src/main/java/com/example/onlyone/domain/settlement/dto/response/UserSettlementDto.java (0 hunks)
  • src/main/java/com/example/onlyone/domain/settlement/repository/UserSettlementRepository.java (4 hunks)
  • src/main/java/com/example/onlyone/domain/settlement/service/SettlementService.java (2 hunks)
  • src/main/java/com/example/onlyone/domain/user/controller/UserController.java (2 hunks)
  • src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementDto.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementResponseDto.java (1 hunks)
  • src/main/java/com/example/onlyone/domain/user/service/UserService.java (4 hunks)
  • src/main/java/com/example/onlyone/global/exception/ErrorCode.java (1 hunks)
💤 Files with no reviewable changes (2)
  • src/main/java/com/example/onlyone/domain/settlement/dto/response/UserSettlementDto.java
  • src/main/java/com/example/onlyone/domain/settlement/controller/SettlementController.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-31T06:40:23.207Z
Learnt from: gkdudans
PR: GoormOnlyOne/OnlyOne-Back#53
File: src/main/java/com/example/onlyone/domain/schedule/service/ScheduleService.java:52-62
Timestamp: 2025-07-31T06:40:23.207Z
Learning: In the OnlyOne application's ScheduleService, the user has refactored the updateScheduleStatus scheduled method to use JPQL batch update instead of iterating through individual entities for better performance. They are also considering future improvements with Spring Batch or QueryDSL update functionality for more complex batch processing scenarios.

Applied to files:

  • src/main/java/com/example/onlyone/domain/schedule/service/ScheduleService.java
  • src/main/java/com/example/onlyone/domain/schedule/entity/Schedule.java
🧬 Code Graph Analysis (3)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementDto.java (2)
src/main/java/com/example/onlyone/domain/search/dto/response/MyMeetingListResponseDto.java (1)
  • Builder (8-14)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementResponseDto.java (1)
  • Builder (10-29)
src/main/java/com/example/onlyone/domain/search/dto/response/MyMeetingListResponseDto.java (1)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementDto.java (1)
  • Builder (10-30)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementResponseDto.java (1)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementDto.java (1)
  • Builder (10-30)
🔇 Additional comments (20)
src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementDto.java (2)

10-12: Lombok 어노테이션 구성이 적절합니다.

Builder 패턴과 Getter, AllArgsConstructor를 적절히 조합하여 불변 객체로 설계되었습니다.


14-20: 필드 구성이 정산 정보 조회에 적합합니다.

정산 조회에 필요한 핵심 정보들(클럽ID, 스케줄ID, 금액, 이미지, 상태, 제목, 생성일시)을 모두 포함하고 있어 적절합니다.

src/main/java/com/example/onlyone/domain/user/dto/response/MySettlementResponseDto.java (2)

10-18: 페이지네이션 응답 DTO 구조가 표준적입니다.

Spring의 Page 인터페이스와 일관된 필드명을 사용하여 표준적인 페이지네이션 응답 구조를 구현했습니다.


20-28: 정적 팩토리 메서드가 잘 구현되었습니다.

Spring의 Page 객체를 적절히 매핑하여 응답 DTO로 변환하는 로직이 깔끔합니다.

src/main/java/com/example/onlyone/domain/chat/entity/ChatRoom.java (1)

27-31: CASCADE 제거가 적절합니다.

ChatRoom이 삭제될 때 Club이 함께 삭제되는 것을 방지하기 위해 CascadeType.ALL을 제거한 것은 올바른 변경입니다. ChatRoom과 Club은 다대일 관계에서 자식이 부모를 삭제해서는 안 됩니다.

src/main/java/com/example/onlyone/domain/chat/entity/Message.java (1)

29-32: CASCADE 제거로 안전성이 향상되었습니다.

Message에서 User로의 CASCADE를 제거하여 메시지 삭제 시 사용자가 함께 삭제되는 위험을 방지했습니다. 이는 데이터 일관성과 안전성 측면에서 올바른 변경입니다.

src/main/java/com/example/onlyone/domain/schedule/controller/ScheduleController.java (1)

77-83: 엔드포인트 추가 전반은 명확하고 일관적입니다

경로/메서드/Swagger 문서화 모두 명확하며 서비스 계층 위임도 적절합니다.

src/main/java/com/example/onlyone/domain/user/controller/UserController.java (1)

13-15: 페이징 관련 의존성 추가 적절

Pageable, Sort, PageableDefault 추가가 새 엔드포인트 요구사항과 잘 맞습니다.

src/main/java/com/example/onlyone/domain/schedule/service/ScheduleService.java (1)

228-233: 확인 완료 — ChatRoom 삭제 시 UserChatRoom 연관 삭제 보장됨

ChatRoom 엔티티에 userChatRooms에 대해 cascade = CascadeType.ALLorphanRemoval = true 설정이 존재합니다. 따라서 chatRoomRepository.delete(chatRoom) 호출 시 연관된 UserChatRoom이 JPA 레벨에서 함께 제거되어 FK 제약 위반 우려는 없습니다.

  • 확인 위치: src/main/java/com/example/onlyone/domain/chat/entity/ChatRoom.java — @OneToMany(mappedBy = "chatRoom", cascade = CascadeType.ALL, orphanRemoval = true) (messages 약 라인 46, userChatRooms 약 라인 49)
src/main/java/com/example/onlyone/domain/search/service/SearchService.java (3)

10-12: 의존성 및 DTO 도입 적절

MyMeetingListResponseDto 도입과 Settlement 연동을 위한 import 추가가 변경 목적과 정합합니다.


37-37: UserSettlementRepository 주입 적절

정산 상태 존재 여부 확인을 위해 필요한 의존성 주입이 적절합니다.


186-198: 확인 완료 — getMyClubs 반환 타입 변경이 호출부에 반영되어 문제없음

요약: rg 결과, getMyClubs() 호출부는 한 곳(SearchController)뿐이며 컨트롤러에서 반환값을 CommonResponse.success(...)로 그대로 전달하고 있습니다. MyMeetingListResponseDto도 서비스/DTO에만 정의되어 있어 추가 수정 불필요합니다.

수정/검토 위치:

  • src/main/java/com/example/onlyone/domain/search/controller/SearchController.java:83 — getMyClubs() 호출부
  • src/main/java/com/example/onlyone/domain/search/service/SearchService.java:187 — 변경된 메서드 시그니처
  • src/main/java/com/example/onlyone/domain/search/dto/response/MyMeetingListResponseDto.java — DTO 정의
src/main/java/com/example/onlyone/domain/user/service/UserService.java (4)

6-9: 정산 DTO 및 Repository import 추가 적절

정산 조회 API 도입을 위한 import 추가가 일관적입니다.


28-29: Page/Pageable 및 LocalDateTime import 적절

페이징 및 기간 필터링을 위한 의존성 추가 적절합니다.

Also applies to: 37-37


51-52: UserSettlementRepository 주입 적절

정산 조회를 User 도메인으로 이동하는 방향과 정합합니다.


382-387: 최근/요청 정산 목록 조회 API 도입 적절

현재 사용자 컨텍스트 기반, 10일 컷오프 필터, 페이징 변환까지 흐름이 간결합니다.

src/main/java/com/example/onlyone/domain/settlement/repository/UserSettlementRepository.java (2)

4-4: MySettlementDto 도입 적절

Projection 기반 조회에 맞는 DTO import가 적절합니다.


87-91: 메서드 명세/프로젝션 일치 양호

findMyRecentOrRequested는 DTO 생성자 인자 순서 및 countQuery와 정합적으로 보입니다.

src/main/java/com/example/onlyone/domain/settlement/service/SettlementService.java (1)

139-143: 비관적 락(PESSIMISTIC_WRITE) 적용 확인 — 수정 불필요

WalletRepository에 @lock(LockModeType.PESSIMISTIC_WRITE)가 선언되어 있어 findByUser 호출에 비관적 락이 적용됩니다(스크립트 검증 완료).

  • src/main/java/com/example/onlyone/domain/wallet/repository/WalletRepository.java — 라인 12: @lock(LockModeType.PESSIMISTIC_WRITE), 라인 13: Optional findByUser(User user);
src/main/java/com/example/onlyone/domain/payment/service/PaymentService.java (1)

117-126: 결론: “cascade 미설정” 지적은 부정확합니다 — WalletTransaction.payment에 CascadeType.ALL이 설정되어 있습니다

간단히 검증한 결과, src/main/java/com/example/onlyone/domain/wallet/entity/WalletTransaction.java 에서
@OnetoOne(cascade = CascadeType.ALL, orphanRemoval = true) 로 Payment와 연관이 설정되어 있습니다.
PaymentService.confirm()에서는 walletTransactionRepository.save(walletTransaction) 이후에 payment 객체를 생성해 walletTransaction.updatePayment(payment) 하고 있으므로, 같은 트랜잭션 내에서 flush 시 JPA의 영속성 전파(cascade)로 Payment가 영속화됩니다. 따라서 반드시 paymentRepository.save(payment)를 추가해야 한다는 원리적 지적은 맞지 않습니다.

권장 보완(안전성/명확성)

  • 파일 위치

    • src/main/java/com/example/onlyone/domain/wallet/entity/WalletTransaction.java — @OnetoOne(cascade = CascadeType.ALL, orphanRemoval = true)
    • src/main/java/com/example/onlyone/domain/payment/service/PaymentService.java — confirm(...) (walletTransactionRepository.save(...) → walletTransaction.updatePayment(payment))
    • src/main/java/com/example/onlyone/domain/payment/repository/PaymentRepository.java — findByTossOrderId(...) (멱등성 체크)
  • 제안

    • 명시적으로 저장을 원하면 paymentRepository.save(payment)를 호출해도 되며(읽는 사람에게 의도 명확), 또는 payment를 연결한 후 walletTransactionRepository.save(walletTransaction)를 한 번 더 호출해도 됩니다.
    • race condition 대비: tossOrderId에 유니크 제약이 있으므로 저장 시 DataIntegrityViolationException(또는 JPA 예외)을 잡아 ErrorCode.ALREADY_COMPLETED_PAYMENT로 매핑하는 처리는 권장합니다.

권장 코드 예시(선택적)

         walletTransaction.updatePayment(payment);
-        return response;
+        // cascade = CascadeType.ALL 이 설정되어 있으므로 대부분의 경우 명시적 save는 불필요합니다.
+        // (명시적 저장을 선호하면 아래 주석 해제)
+        // paymentRepository.save(payment);
+        // 또는 저장 주위에서 DataIntegrityViolationException을 catch하여 ALREADY_COMPLETED_PAYMENT로 매핑하세요.
+        return response;

Likely an incorrect or invalid review comment.

@gkdudans gkdudans requested a review from doehy August 18, 2025 00:13
NamYeonW00
NamYeonW00 previously approved these changes Aug 18, 2025
@gkdudans gkdudans dismissed NamYeonW00’s stale review August 18, 2025 00:16

The merge-base changed after approval.

@gkdudans gkdudans changed the title [feat] 스케줄 삭제 API 구현 / 정산 관련 API 응답 수정 [feat] 정산, 결제 실패 로직 추가 / 스케줄 삭제 API 구현 / 정산 관련 API 응답 수정 Aug 18, 2025
@gkdudans gkdudans requested a review from NamYeonW00 August 18, 2025 00:20
Copy link
Contributor

@NamYeonW00 NamYeonW00 left a comment

Choose a reason for hiding this comment

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

굿굿

@gkdudans gkdudans merged commit 5dd816b into develop Aug 18, 2025
1 check was pending
choigpt pushed a commit that referenced this pull request Aug 28, 2025
[feat] 정산, 결제 실패 로직 추가 / 스케줄 삭제 API 구현 / 정산 관련 API 응답 수정
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants