Skip to content

fix/user : 초기 배포 문제 수집 결과에 따른 모델 개선점 구현 #87

Open

Description

@jakdalmak

📌 이슈 제목

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과 사건별 보고서는 당시 처리 근거로 보존한다.
  1. 토스 응답 및 오류 증거 보존 개선
    대상: TossPaymentClient, user/admin의 TossPaymentCancelClient, PaymentConfirmService, 관련 DTO·로그 저장부.
    필요성: 실제 HTTP 및 외부 오류 정보가 누락·대체되어 원인 분석과 복구 판단이 제한되는 문제를 해결한다.
  • 승인 성공·실패 응답에서 실제 수신한 HTTP 상태를 전달하도록 클라이언트 결과 구조를 정비한다.
  • 승인 실패 예외의 HTTP 상태가 UNKNOWN 저장 경로에서도 유지되도록 전달 인자와 로그 저장부를 수정한다.
  • 환불 성공 및 응답 불일치 결과의 HTTP 상태를 상수 200 대신 실제 응답값으로 기록한다.
  • 토스 오류 코드·메시지와 서버 내부 오류 분류 코드를 분리하여 보존한다.
  • 내부 코드인 TOSS_CANCEL_UNCONFIRMED 등으로 외부 오류 코드를 덮어쓰지 않도록 변경한다.
  • 외부 거절, 통신 실패, 응답 파싱 실패, 응답 정합성 불일치, 로컬 DB 반영 실패를 구분하여 기록한다.
  • 응답을 받지 못한 경우 HTTP 상태를 임의 생성하지 않고 미확인으로 기록한다.
  • 사용자에게 노출하는 오류 문구와 내부 분석용 증거를 분리한다.
  • 비밀 값·인증 헤더·개인정보를 제외한 증거 필드만 저장한다.
  • 기존 로그 구조로 보존 가능한 범위를 확인하고, 스키마 변경이 필요하면 별도 변경안을 확정한다.
    완료 기준:
  • 동일 오류의 외부 HTTP·외부 코드·내부 판정 사유를 로그에서 각각 식별할 수 있다.
  • 정상 응답, 오류 응답, 본문 해석 실패, 응답 없는 통신 실패의 기록을 테스트한다.
  1. 승인 실패 분류표 정비
    대상: 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 조합을 매개변수화 테스트로 검증한다.
  • 할부 미지원은 정상 실패 처리되고, 불확정 오류는 기존 금융 상태를 잘못 변경하지 않는다.
  1. 승인 성공 응답 검증 및 저장 계약 보강
    대상: TossPaymentConfirmResponse, PaymentConfirmTransactionService.validateSuccessResponse, Payment.completeConfirm.
    필요성: 주문·금액·상태뿐 아니라 지원 결제수단과 승인 증거를 검증하고, 저장 기준을 명확히 한다.
  • 승인 응답 필드별 필수 여부와 nullable 조건을 공식 계약 및 지원 결제수단 기준으로 정리한다.
  • 기존 paymentKey·orderId·금액·DONE 검증을 유지한다.
  • 통화 및 승인 증거 검증에 필요한 응답 필드를 DTO에 추가한다.
  • 승인 시각·거래키가 누락되거나 부적합한 경우의 처리 기준을 확정하고 구현한다.
  • 지원 결제수단 목록을 명시하고, 알 수 없는 결제수단이 조용히 null로 저장되지 않도록 처리한다.
  • MID 검증은 문자열 고정 비교에 앞서 허용 MID와 기존 거래의 상점 연결 정책을 확인한다.
  • 승인 시각·요청 시각의 저장 기준을 확인하고 명시적인 시간대 변환을 적용한다.
  • 신청·로그 시각과 외부 승인 시각의 저장 기준이 다르면 각 필드의 변환 책임을 구분한다.
  • 기존 데이터의 시간값 일괄 수정은 이번 작업에 포함하지 않는다.
    완료 기준:
  • 다른 주문·키·금액·통화 및 불완전한 응답이 정상 승인으로 반영되지 않는다.
  • 지원 결제수단과 시간대 변환을 테스트한다.
  • 계약상 nullable인 필드를 무조건 필수로 취급하여 정상 응답을 거부하지 않는다.
  1. 환불 성공 및 실패 판정 정비
    대상: user/admin의 TossCancelResponseVerifier, TossPaymentCancelClient, RefundExecutionTransactionService.
    필요성: 실제 관측된 PARTIAL_CANCELED + 잔액 0 응답을 잘못 거부하는 조건을 수정하고, 환불 오류별 후속 처리를 구분한다.
  • 잔액 0이면 반드시 CANCELED여야 한다는 조건을 응답 검증 단계에서 제거한다.
  • DB 반영 단계의 동일한 기대 상태 계산·비교도 함께 수정한다.
  • CANCELED·PARTIAL_CANCELED 허용 여부를 개별 취소 결과와 금액 정합성에 연결하여 판정한다.
  • 외부 Payment 상태는 관측된 값으로 보존하고, 서버의 환불 완료 판정과 분리한다.
  • 대상 주문·결제키·통화·원결제 금액 검증을 유지한다.
  • 취소 거래키의 유일성, 개별 취소 DONE, 요청 금액 및 취소 시각 검증을 유지한다.
  • 기존 완료 취소 이력의 보존 여부를 확인한다.
  • 누적 취소액·잔액·원결제 금액의 정합성을 확인한다.
  • 환불 오류를 확정 거절, 기존 완료 여부 조회 필요, 통신 불확정, 운영 조치 필요로 구분한다.
  • 실패한 환불 요청의 응답만으로 기존 환불의 미완료를 단정하지 않는다.
  • user/admin 양쪽에 동일한 판정 기준을 적용한다.
    완료 기준:
  • 기존 정상 환불과 PARTIAL_CANCELED + 잔액 0 사례가 모두 통과한다.
  • 상태만 유사하고 거래키·금액·누적 취소액이 다른 응답은 거부된다.
  • 이미 반영한 부분 환불을 다시 차감하지 않는다.
  1. 외부 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, 복수 취소, 추가 결제, 단체 배분 사례를 테스트한다.
  • 기존 증거 조회의 확인 결과만으로 금융 상태를 무조건 수정하지 않는다.
  1. 상태복구 트랜잭션 및 감사 이력 구성
    대상: 관리자 복구 서비스, 기존 승인·환불 반영 로직, 관련 Repository·로그 저장부.
    필요성: 외부에서 확정된 결과를 내부 데이터에 한 번만, 일관되게 반영한다.
  • 내부 스냅샷 조회 → 트랜잭션 밖 GET → 판정 → 쓰기 트랜잭션 재검증 순서로 구성한다.
  • 외부 호출 동안 DB 쓰기 잠금을 장시간 유지하지 않는다.
  • 쓰기 진입 시 version·처리 상태·금액·관련 거래 변경 여부를 재검증한다.
  • 일반 승인·환불 경로와 일관된 잠금 순서를 적용한다.
  • 승인 실패 복구 시 결제 처리 상태와 예약 진행 상태를 함께 보정한다.
  • 승인 완료 미반영 복구 시 기존 승인 반영 규칙을 적용하고 중복 납부액 반영을 방지한다.
  • 환불 완료 미반영 복구 시 취소 상태·배분별 납부액·신청 상태를 함께 반영한다.
  • 이미 반환된 예약 자원을 다시 반환하지 않는다.
  • 정상 경로의 상태 검증을 우회하기 위해 UNKNOWN을 임의 중간 상태로 변경하지 않는다.
  • 반복 요청은 추가 차감 없이 이미 처리된 결과로 반환한다.
  • 반영 중 예외가 발생하면 부분 변경이 남지 않도록 전체 롤백한다.
  • 변경 전후 값, 외부 근거, 처리자, 처리 시각, 복구 대상 및 판정 사유를 기록한다.
  • 기존 요청·승인·환불 로그와 원배분 데이터를 보존한다.
    완료 기준:
  • 반복 클릭·동시 요청·일반 결제 처리와의 경합에서 이중 반영이 발생하지 않는다.
  • GET 이후 내부 상태가 변경되면 재판정하거나 안전하게 중단한다.
  • 단체 일부만 변경되는 부분 반영이 발생하지 않는다.
  1. 관리자 상태복구 API 구성
    대상: 관리자 Controller, 요청·응답 DTO, 권한 검증, CustomException + ErrorCode.
    필요성: 관리자가 토스 원문 확인이나 DB 직접 수정 없이 복구를 요청할 수 있도록 한다.
  • API 메서드·경로·대상 식별자·권한·응답 계약을 구현 전에 확정한다.
  • 관리자 권한과 대상 대회·신청·결제의 소속을 검증한다.
  • 요청에는 복구 대상과 필요한 처리 사유만 받고, 변경할 상태·금액은 서버가 결정한다.
  • 기존 증거 조회 API의 책임을 유지하고 복구 실행 기능과 구분한다.
  • 복구 완료·이미 정상·처리 중·확인 필요 결과와 해당 사유를 반환한다.
  • 복구 전후 상태 및 금액, 감사 이력 식별자를 응답에 포함한다.
  • 조회 불가·정합성 불일치·동시 변경 등 업무 오류를 기존 예외 체계로 표현한다.
  • 비밀 값과 외부 원문 개인정보를 응답에 노출하지 않는다.
  • API 사용 예시와 각 결과에 대한 관리자 조치 방법을 문서화한다.
    완료 기준:
  • 관리자가 내부 상태값을 선택하지 않아도 복구 요청을 수행할 수 있다.
  • 권한 없는 요청, 다른 대회 대상 요청, 반복 요청, 복구 불가 요청을 테스트한다.
    이번 범위에서 제외할 사항
  • 주기적으로 모든 UNKNOWN을 조회하는 자동 배치·스케줄러는 별도 이슈로 분리한다.
  • 복구 요청에 따른 자동 재승인·재환불 POST는 포함하지 않는다.
  • MID 설정·권한 문제로 실제 환불이 완료되지 않은 보류 건은 완료 상태로 보정하지 않는다.
  • 관리자 화면의 실제 UI 구현은 별도 범위로 구분한다.
  • 과거 시간값 일괄 보정과 외부 결제를 되돌리는 기능은 포함하지 않는다.
  • 기존 SQL의 UNDO를 일반 관리자 원복 API로 노출하는 작업은 별도 권한·업무 정책 확정 후 진행한다.
    착수 전 확정할 정책
  • 지원 결제수단 및 MID별 허용 범위.
  • 외부 날짜 필드별 저장 시간대.
  • 승인 완료 미반영 복구가 현재 신청·예약 상태에서 허용되는 조건.
  • 관리자 복구 권한과 필수 처리 사유.
  • 증거 보존 항목·보존 기간 및 스키마 변경 필요 여부.
  • 각 목차의 상세 설계 확인 후 해당 목차만 구현하고, 테스트 실행은 별도 승인에 따라 진행한다.

🧩 참고 자료

  • 관련 문서, 이슈 링크 등

Activity

  1. self-assigned this
    on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions