Repository navigation
Open
Description
📌 이슈 제목
fix/user : 초기 배포 문제 수집 결과에 따른 모델 개선점 구현
✨ 기능 설명
- 초기 결제 모델에서는 아래와 같은 문제가 발견되었다.
① 환불 완료 결과 미반영
=> 환불 자체는 토스상에서 성공적으로 처리되었으나, 환불 결과 적용이 서버 내에서 정상적으로 처리되지 않아 CANCELLATION_PENDING 상태로 잔류.
② 일반 오류 이후 승인 실패 미확정
=> PROVIDER_ERROR 로 인해 발생한 결제 실패 후 정상적으로 결제 실패 다음의 내역으로 진행/복구 되지 않고 UNKNOWN으로 잔류하며 사용자가 다음 결제진행하지 못하는 문제 발생.(확인된 건은 잔액 부족이나, 일부 확인되지 않은 원인도 존재.)
③ 할부 미지원 오류 분류 누락
=> 서버 상에서 할부 미지원을 나타내는 NOT_SUPPORTED_INSTALLMENT_PLAN_CARD_OR_MERCHANT가 코드의 확정 실패 목록에 없어 UNKNOWN으로 잔류하여 다음 결제 진행하지 못하는 문제 발생.
- 해결 방안은 아래와 같다.
- 결제/환불 시도 및 예외처리에 따른 선제적 해결 조치 && 관리자의 '상태복구' 요청 기능을 구성한다.
| 유형 | 결제·환불 처리 단계에서의 개선 | 이미 발생한 건의 관리자 복구 |
|---|---|---|
| 환불 완료 결과 미반영 | 취소 응답 검증 조건을 수정하고, 결과가 불명확하면 GET으로 해당 취소 완료 여부 재확인 | 취소 DONE 확인 → 취소 행 확정 + 미반영 납부액 차감 + 신청 상태 정리. 환불 재요청 불필요 |
| 승인 실패 미확정 | PROVIDER_ERROR만으로 실패 확정하지 않고 GET으로 결과 재확인. 계속 불명확하면 UNKNOWN 유지 | ABORTED·승인 없음 확인 → payment FAILED/ABORTED + 예약 HELD. 결제 재시도는 이후 별도 절차 |
| 할부 미지원 오류 분류 누락 | 정확한 오류 코드·HTTP 조건을 확정 실패 분류에 반영하면 신규 발생 예방 가능 | 기존 UNKNOWN 건은 GET 확인 후 payment FAILED/ABORTED + 예약 HELD |
✅ 세부 작업 항목
공통 적용 범위
- 정상 결제 승인 경로, user/admin 환불 경로, 관리자 복구 경로를 수정 대상으로 지정한다.
- 외부 HTTP 상태, 토스 오류 코드, Payment 상태, 개별 취소 상태, 내부 처리 상태를 구분하여 판정한다.
- 기존 승인·환불·신청·예약의 업무 규칙과 잠금 순서를 확인하고 복구 경로에도 적용한다.
- user/admin의 동일한 외부 결과 판정은 공통화 가능성을 검토한다. 업무 서비스 간 직접 의존으로 재사용하지 않는다.
- 수정 대상과 직접 관련된 테스트를 각 작업에 포함한다.
- 과거 복구 SQL과 사건별 보고서는 당시 처리 근거로 보존한다.
- 토스 응답 및 오류 증거 보존 개선
대상: TossPaymentClient, user/admin의 TossPaymentCancelClient, PaymentConfirmService, 관련 DTO·로그 저장부.
필요성: 실제 HTTP 및 외부 오류 정보가 누락·대체되어 원인 분석과 복구 판단이 제한되는 문제를 해결한다.
- 승인 성공·실패 응답에서 실제 수신한 HTTP 상태를 전달하도록 클라이언트 결과 구조를 정비한다.
- 승인 실패 예외의 HTTP 상태가 UNKNOWN 저장 경로에서도 유지되도록 전달 인자와 로그 저장부를 수정한다.
- 환불 성공 및 응답 불일치 결과의 HTTP 상태를 상수 200 대신 실제 응답값으로 기록한다.
- 토스 오류 코드·메시지와 서버 내부 오류 분류 코드를 분리하여 보존한다.
- 내부 코드인 TOSS_CANCEL_UNCONFIRMED 등으로 외부 오류 코드를 덮어쓰지 않도록 변경한다.
- 외부 거절, 통신 실패, 응답 파싱 실패, 응답 정합성 불일치, 로컬 DB 반영 실패를 구분하여 기록한다.
- 응답을 받지 못한 경우 HTTP 상태를 임의 생성하지 않고 미확인으로 기록한다.
- 사용자에게 노출하는 오류 문구와 내부 분석용 증거를 분리한다.
- 비밀 값·인증 헤더·개인정보를 제외한 증거 필드만 저장한다.
- 기존 로그 구조로 보존 가능한 범위를 확인하고, 스키마 변경이 필요하면 별도 변경안을 확정한다.
완료 기준: - 동일 오류의 외부 HTTP·외부 코드·내부 판정 사유를 로그에서 각각 식별할 수 있다.
- 정상 응답, 오류 응답, 본문 해석 실패, 응답 없는 통신 실패의 기록을 테스트한다.
- 승인 실패 분류표 정비
대상: TossConfirmFailureClassifier, PaymentConfirmService, PaymentConfirmTransactionService.
필요성: 확정 가능한 승인 거절이 잘못된 매핑이나 누락 때문에 UNKNOWN으로 남는 문제를 해결한다.
- EXCEED_MAX_AUTH_COUNT의 HTTP 매핑을 400 → 403으로 수정한다.
- INVALID_PASSWORD의 HTTP 매핑을 400 → 403으로 수정한다.
- REJECT_TOSSPAY_INVALID_ACCOUNT의 HTTP 매핑을 400 → 403으로 수정한다.
- NOT_FOUND_PAYMENT_SESSION의 HTTP 매핑을 400 → 404로 수정한다.
- 실제 발생한 NOT_SUPPORTED_INSTALLMENT_PLAN_CARD_OR_MERCHANT를 승인 거절 분류에 추가한다.
- 나머지 할부 관련 오류도 공식 승인 엔드포인트 계약과 대조하여 분류표에 반영한다.
- 현재 공식 승인 오류 표에서 확인되지 않는 기존 코드 3개는 사용 근거를 확인하고 유지·제거 여부를 명시한다.
- 분류표에 엔드포인트·HTTP·외부 코드·내부 판정·후속 조치를 함께 정의한다.
- 확정 거절은 기존 실패 처리 경로를 통해 결제 실패와 예약 진행 상태 해제를 반영한다.
- PROVIDER_ERROR, 이미 처리됨, 처리 중, 통신 불명확 등의 결과는 성급히 실패 확정하지 않는다.
- 인증·권한·상점 설정 오류는 기존 거래의 결제 결과와 분리하여 운영 조치 대상으로 판정한다.
- 미등록 오류와 예상하지 못한 HTTP 조합은 증거를 보존하고 불확정 상태로 처리한다.
- POST 거절만으로 토스 상태를 ABORTED라고 생성하지 않는다.
완료 기준: - 기존 분류표와 변경된 HTTP 조합을 매개변수화 테스트로 검증한다.
- 할부 미지원은 정상 실패 처리되고, 불확정 오류는 기존 금융 상태를 잘못 변경하지 않는다.
- 승인 성공 응답 검증 및 저장 계약 보강
대상: TossPaymentConfirmResponse, PaymentConfirmTransactionService.validateSuccessResponse, Payment.completeConfirm.
필요성: 주문·금액·상태뿐 아니라 지원 결제수단과 승인 증거를 검증하고, 저장 기준을 명확히 한다.
- 승인 응답 필드별 필수 여부와 nullable 조건을 공식 계약 및 지원 결제수단 기준으로 정리한다.
- 기존 paymentKey·orderId·금액·DONE 검증을 유지한다.
- 통화 및 승인 증거 검증에 필요한 응답 필드를 DTO에 추가한다.
- 승인 시각·거래키가 누락되거나 부적합한 경우의 처리 기준을 확정하고 구현한다.
- 지원 결제수단 목록을 명시하고, 알 수 없는 결제수단이 조용히 null로 저장되지 않도록 처리한다.
- MID 검증은 문자열 고정 비교에 앞서 허용 MID와 기존 거래의 상점 연결 정책을 확인한다.
- 승인 시각·요청 시각의 저장 기준을 확인하고 명시적인 시간대 변환을 적용한다.
- 신청·로그 시각과 외부 승인 시각의 저장 기준이 다르면 각 필드의 변환 책임을 구분한다.
- 기존 데이터의 시간값 일괄 수정은 이번 작업에 포함하지 않는다.
완료 기준: - 다른 주문·키·금액·통화 및 불완전한 응답이 정상 승인으로 반영되지 않는다.
- 지원 결제수단과 시간대 변환을 테스트한다.
- 계약상 nullable인 필드를 무조건 필수로 취급하여 정상 응답을 거부하지 않는다.
- 환불 성공 및 실패 판정 정비
대상: user/admin의 TossCancelResponseVerifier, TossPaymentCancelClient, RefundExecutionTransactionService.
필요성: 실제 관측된 PARTIAL_CANCELED + 잔액 0 응답을 잘못 거부하는 조건을 수정하고, 환불 오류별 후속 처리를 구분한다.
- 잔액 0이면 반드시 CANCELED여야 한다는 조건을 응답 검증 단계에서 제거한다.
- DB 반영 단계의 동일한 기대 상태 계산·비교도 함께 수정한다.
- CANCELED·PARTIAL_CANCELED 허용 여부를 개별 취소 결과와 금액 정합성에 연결하여 판정한다.
- 외부 Payment 상태는 관측된 값으로 보존하고, 서버의 환불 완료 판정과 분리한다.
- 대상 주문·결제키·통화·원결제 금액 검증을 유지한다.
- 취소 거래키의 유일성, 개별 취소 DONE, 요청 금액 및 취소 시각 검증을 유지한다.
- 기존 완료 취소 이력의 보존 여부를 확인한다.
- 누적 취소액·잔액·원결제 금액의 정합성을 확인한다.
- 환불 오류를 확정 거절, 기존 완료 여부 조회 필요, 통신 불확정, 운영 조치 필요로 구분한다.
- 실패한 환불 요청의 응답만으로 기존 환불의 미완료를 단정하지 않는다.
- user/admin 양쪽에 동일한 판정 기준을 적용한다.
완료 기준: - 기존 정상 환불과 PARTIAL_CANCELED + 잔액 0 사례가 모두 통과한다.
- 상태만 유사하고 거래키·금액·누적 취소액이 다른 응답은 거부된다.
- 이미 반영한 부분 환불을 다시 차감하지 않는다.
- 외부 GET 기반 복구 판정 구성
대상: AdminRefundEvidenceClient, AdminRefundEvidenceMatcher, AdminRefundEvidenceStore, 복구 판정 담당 코드.
필요성: 기존 증거 조회 기능을 활용하면서 승인·환불의 실제 상태와 내부 반영 여부를 판정한다.
- 승인 복구와 환불 복구에 필요한 GET 응답 필드를 정의한다.
- DB에 저장된 대상 결제 식별자로 조회하고 요청자가 임의 외부 결제키를 지정하지 않도록 한다.
- GET 응답의 주문·결제키·금액·통화 및 적용 가능한 MID 정책을 검증한다.
- READY, IN_PROGRESS, WAITING_FOR_DEPOSIT을 임의 실패 또는 완료로 변경하지 않는다.
- ABORTED, EXPIRED는 대상 승인 시도의 실패·만료 증거와 내부 상태를 대조한다.
- DONE은 기존 승인 반영 여부를 확인하여 중복 납부액 반영을 방지한다.
- CANCELED, PARTIAL_CANCELED는 개별 취소와 배분 기준으로 미반영 결과를 찾는다.
- 로그에 취소 거래키가 없는 사례의 식별 기준을 구성한다.
- 금액 하나만으로 취소를 식별하지 않고 기존 완료 이력과 신규 후보의 유일성을 함께 확인한다.
- 후보가 여러 개이거나 증거가 부족하면 확인 필요로 판정한다.
- 동기 POST의 “신규 취소 1건” 조건을 장기간 뒤 GET 복구에 그대로 적용하지 않는다.
- 관련 결제 수를 1건으로 제한하지 않고 각 결제의 실제 금융 영향을 검토한다.
- INVALIDATED 결제의 allocation 존재 자체를 오류나 납부 증거로 취급하지 않는다.
- 조회 실패·미지원 응답·불일치 사유를 저장하고 상태 변경을 보류한다.
- GET 결과의 관측 시각과 판정 근거를 기록한다.
완료 기준: - 판정 결과가 복구 가능·이미 정상·처리 중·확인 필요로 구분된다.
- 거래키 없는 UNKNOWN, 복수 취소, 추가 결제, 단체 배분 사례를 테스트한다.
- 기존 증거 조회의 확인 결과만으로 금융 상태를 무조건 수정하지 않는다.
- 상태복구 트랜잭션 및 감사 이력 구성
대상: 관리자 복구 서비스, 기존 승인·환불 반영 로직, 관련 Repository·로그 저장부.
필요성: 외부에서 확정된 결과를 내부 데이터에 한 번만, 일관되게 반영한다.
- 내부 스냅샷 조회 → 트랜잭션 밖 GET → 판정 → 쓰기 트랜잭션 재검증 순서로 구성한다.
- 외부 호출 동안 DB 쓰기 잠금을 장시간 유지하지 않는다.
- 쓰기 진입 시 version·처리 상태·금액·관련 거래 변경 여부를 재검증한다.
- 일반 승인·환불 경로와 일관된 잠금 순서를 적용한다.
- 승인 실패 복구 시 결제 처리 상태와 예약 진행 상태를 함께 보정한다.
- 승인 완료 미반영 복구 시 기존 승인 반영 규칙을 적용하고 중복 납부액 반영을 방지한다.
- 환불 완료 미반영 복구 시 취소 상태·배분별 납부액·신청 상태를 함께 반영한다.
- 이미 반환된 예약 자원을 다시 반환하지 않는다.
- 정상 경로의 상태 검증을 우회하기 위해 UNKNOWN을 임의 중간 상태로 변경하지 않는다.
- 반복 요청은 추가 차감 없이 이미 처리된 결과로 반환한다.
- 반영 중 예외가 발생하면 부분 변경이 남지 않도록 전체 롤백한다.
- 변경 전후 값, 외부 근거, 처리자, 처리 시각, 복구 대상 및 판정 사유를 기록한다.
- 기존 요청·승인·환불 로그와 원배분 데이터를 보존한다.
완료 기준: - 반복 클릭·동시 요청·일반 결제 처리와의 경합에서 이중 반영이 발생하지 않는다.
- GET 이후 내부 상태가 변경되면 재판정하거나 안전하게 중단한다.
- 단체 일부만 변경되는 부분 반영이 발생하지 않는다.
- 관리자 상태복구 API 구성
대상: 관리자 Controller, 요청·응답 DTO, 권한 검증, CustomException + ErrorCode.
필요성: 관리자가 토스 원문 확인이나 DB 직접 수정 없이 복구를 요청할 수 있도록 한다.
- API 메서드·경로·대상 식별자·권한·응답 계약을 구현 전에 확정한다.
- 관리자 권한과 대상 대회·신청·결제의 소속을 검증한다.
- 요청에는 복구 대상과 필요한 처리 사유만 받고, 변경할 상태·금액은 서버가 결정한다.
- 기존 증거 조회 API의 책임을 유지하고 복구 실행 기능과 구분한다.
- 복구 완료·이미 정상·처리 중·확인 필요 결과와 해당 사유를 반환한다.
- 복구 전후 상태 및 금액, 감사 이력 식별자를 응답에 포함한다.
- 조회 불가·정합성 불일치·동시 변경 등 업무 오류를 기존 예외 체계로 표현한다.
- 비밀 값과 외부 원문 개인정보를 응답에 노출하지 않는다.
- API 사용 예시와 각 결과에 대한 관리자 조치 방법을 문서화한다.
완료 기준: - 관리자가 내부 상태값을 선택하지 않아도 복구 요청을 수행할 수 있다.
- 권한 없는 요청, 다른 대회 대상 요청, 반복 요청, 복구 불가 요청을 테스트한다.
이번 범위에서 제외할 사항 - 주기적으로 모든 UNKNOWN을 조회하는 자동 배치·스케줄러는 별도 이슈로 분리한다.
- 복구 요청에 따른 자동 재승인·재환불 POST는 포함하지 않는다.
- MID 설정·권한 문제로 실제 환불이 완료되지 않은 보류 건은 완료 상태로 보정하지 않는다.
- 관리자 화면의 실제 UI 구현은 별도 범위로 구분한다.
- 과거 시간값 일괄 보정과 외부 결제를 되돌리는 기능은 포함하지 않는다.
- 기존 SQL의 UNDO를 일반 관리자 원복 API로 노출하는 작업은 별도 권한·업무 정책 확정 후 진행한다.
착수 전 확정할 정책 - 지원 결제수단 및 MID별 허용 범위.
- 외부 날짜 필드별 저장 시간대.
- 승인 완료 미반영 복구가 현재 신청·예약 상태에서 허용되는 조건.
- 관리자 복구 권한과 필수 처리 사유.
- 증거 보존 항목·보존 기간 및 스키마 변경 필요 여부.
- 각 목차의 상세 설계 확인 후 해당 목차만 구현하고, 테스트 실행은 별도 승인에 따라 진행한다.
🧩 참고 자료
- 관련 문서, 이슈 링크 등
Metadata
Metadata
Assignees
Labels
No labels
Activity