fix(charge): 쿠폰 충전 회원 락을 적용한다
This commit is contained in:
50
docs/plan-task/20260518_쿠폰충전회원락보강.md
Normal file
50
docs/plan-task/20260518_쿠폰충전회원락보강.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# 쿠폰 충전 회원 락 보강 작업 계획
|
||||
|
||||
## 목적
|
||||
- 쿠폰 캔 지급 흐름에서도 일반 결제 완료 흐름과 동일하게 회원 row lock을 잡은 뒤 회원 잔액을 증가시킨다.
|
||||
- 동시 충전/쿠폰 사용 상황에서 `Member.charge(...)`의 `+=` 기반 잔액 증가가 유실되지 않도록 한다.
|
||||
|
||||
## 현재 확인된 문제
|
||||
- `ChargeService.chargeByCoupon(...)`은 `CouponType.CAN` 분기에서 컨트롤러/인증 계층이 전달한 `member` 인스턴스에 바로 `member.charge(0, coupon.can, "pg")`를 호출한다.
|
||||
- 일반 결제 완료 경로(`payverseWebhook`, `payverseVerify`, `verify`, `verifyHecto`, `appleVerify`, `processGoogleIap`)는 `memberRepository.findByIdForUpdate(...)`로 회원 row를 잠근 뒤 `member.charge(...)`를 호출한다.
|
||||
- 쿠폰 충전만 이 패턴과 달라, 같은 회원에게 쿠폰 충전과 다른 잔액 증가 작업이 동시에 실행될 경우 회원 캔 잔액 유실 가능성이 남는다.
|
||||
|
||||
## 확정 범위
|
||||
- `ChargeService.chargeByCoupon(...)`의 `CouponType.CAN` 분기만 최소 수정한다.
|
||||
- 쿠폰 번호 검증, 이미 사용된 쿠폰 검증, `Charge(status = COUPON)` 생성, 성공 메시지 반환 동작은 유지한다.
|
||||
- 포인트 쿠폰(`CouponType.POINT`) 로직은 변경하지 않는다.
|
||||
- 공개 API 요청/응답 스키마는 변경하지 않는다.
|
||||
- 본 문서를 기준으로 최소 구현을 진행한다.
|
||||
|
||||
## 구현 항목
|
||||
- [x] 쿠폰 캔 지급 lock 패턴 적용
|
||||
- [x] `chargeByCoupon(...)`에서 `member.id!!`를 기준으로 `memberRepository.findByIdForUpdate(...)`를 호출한다.
|
||||
- [x] lock 조회로 얻은 회원 엔티티를 `couponCharge.member`에 연결한다.
|
||||
- [x] lock 조회로 얻은 회원 엔티티에 `member.charge(0, coupon.can, "pg")`를 호출한다.
|
||||
- [x] lock 조회 실패 시 기존 인증 실패 계열 예외 메시지 패턴을 따른다.
|
||||
|
||||
- [x] 회귀 테스트 추가
|
||||
- [x] `CouponType.CAN` 쿠폰 사용 시 `memberRepository.findByIdForUpdate(...)`가 호출되는지 검증한다.
|
||||
- [x] lock 조회로 얻은 회원에 쿠폰 캔이 반영되는지 검증한다.
|
||||
- [x] 이미 사용된 쿠폰이면 lock 조회와 캔 지급이 진행되지 않는지 검증한다.
|
||||
- [x] `CouponType.POINT` 쿠폰 동작이 변경되지 않았는지 기존 또는 신규 테스트로 확인한다.
|
||||
|
||||
- [x] 최소 구현 검증
|
||||
- [x] 관련 테스트를 먼저 실행해 실패를 확인한다.
|
||||
- [x] 구현 후 관련 테스트가 통과하는지 확인한다.
|
||||
- [x] `./gradlew ktlintCheck`를 실행한다.
|
||||
- [x] 필요 시 `./gradlew test`로 전체 회귀를 확인한다.
|
||||
|
||||
## 검증 항목
|
||||
- [x] 쿠폰 캔 지급 테스트 통과
|
||||
- [x] 쿠폰 포인트 지급 기존 동작 유지 확인
|
||||
- [x] `./gradlew ktlintCheck`
|
||||
- [x] `./gradlew test`
|
||||
|
||||
## 검증 로그
|
||||
- [x] 문서 작성 검증: `ChargeService.chargeByCoupon(...)`의 `CouponType.CAN` 분기와 일반 결제 완료 경로의 `memberRepository.findByIdForUpdate(...)` 사용 패턴을 확인하고 작업 범위를 문서화했다.
|
||||
- [x] RED 검증: `./gradlew test --tests 'kr.co.vividnext.sodalive.can.charge.ChargeServiceTest'` 실행 시 `WantedButNotInvoked`로 `memberRepository.findByIdForUpdate(10L)` 미호출 실패를 확인했다.
|
||||
- [x] 구현 검증: `CouponType.CAN` 분기에서 locked member를 조회하고, `couponCharge.member`와 `member.charge(...)` 모두 locked member를 사용하도록 변경했다.
|
||||
- [x] 관련 테스트 검증: `./gradlew test --tests 'kr.co.vividnext.sodalive.can.charge.ChargeServiceTest'` 통과를 확인했다.
|
||||
- [x] 린트 검증: `./gradlew ktlintCheck` 통과를 확인했다.
|
||||
- [x] 전체 테스트 검증: `./gradlew test` 통과를 확인했다.
|
||||
Reference in New Issue
Block a user