인증결제 뒤로가기 이슈 — 데이터는 남았는데, 맞는 데이터가 없다
배경
우리 서비스에는 인증결제 모듈이 있다.
이 모듈은 내가 처음부터 짠 코드가 아니다.
구조를 처음 봤을 때부터 조금 불안했다.
비즈니스 로직과 그걸 처리하는 컨트롤 코드가 하나의 파일 안에 같이 있었고,
핸들러는 또 별도 파일로 분리되어 있었다.
역할 분리가 어중간하게 되어 있는 구조였다.
그리고 로그는 전부 DEBUG 레벨로 처리되어 있었다.
운영 환경에서는 INFO 레벨 이상부터만 출력된다.
즉, 운영에서 이 모듈에 무슨 일이 일어나도 로그로는 아무것도 안 보인다.
언젠간 터지겠다 싶었는데, 결국 터졌다.
인증결제 흐름
인증결제는 크게 두 단계로 나뉜다.
1
2
3
4
5
1단계: 인증키 요청
사용자 브라우저 → PG사 → 인증키 발급
2단계: 결제 승인 요청
인증키를 가지고 → 서버 → PG사 결제 승인
핵심은 브라우저에서 처리되는 구간이 존재한다는 점이다.
사용자가 브라우저를 조작하면 서버가 개입하기 전에 흐름이 끊길 수 있다.
문제 발생
상황 재현
1
2
3
1. 사용자가 결제 시도 → 인증키 요청 성공
2. 사용자가 브라우저 뒤로가기
3. 사용자가 다시 결제 시도 → 두 번째 결제 승인 성공
두 번째 결제는 정상적으로 됐다.
문제는 첫 번째 시도의 흔적이었다.
데이터 구조
승인 관련 테이블은 두 개로 나뉘어 있다.
1
2
승인 테이블 → 결제 승인 성공 데이터를 저장
승인 실패 테이블 → 결제 승인 실패 데이터를 저장
실제로 남은 데이터
뒤로가기로 중단된 첫 번째 시도에 대한 데이터가
승인 테이블에 남아있었다.
그런데 이상한 점이 한두 개가 아니었다.
- 응답코드: 실패 코드 (승인 테이블에 실패 코드가 들어있음)
- 데이터 생성 시각: 실패가 발생한 시점 기준
- 거래 시각: 실패 시점 기준
- 카드 정보: 없음
승인 테이블에는 성공한 거래 데이터만 있어야 한다.
그런데 실패 코드를 가진 데이터가 승인 테이블에 들어가 있었고,
시각 데이터는 두 번째 정상 결제 기준이 아닌
뒤로가기 시점의 실패 기준으로 맞춰져 있었다.
카드 정보조차 없으니 이게 어떤 거래인지 추적도 힘들었다.
원인 분석
뒤로가기 시점에 서버가 응답을 받은 것
브라우저에서 뒤로가기를 했더라도,
이미 PG사로 나간 요청은 취소되지 않는다.
PG사에서 응답이 왔을 때 서버는 그 응답을 처리했고,
그 결과가 승인 테이블에 저장된 것이다.
문제는 실패 응답임에도 승인 테이블에 저장하는 분기 처리가 잘못되어 있었다는 것이다.
1
2
3
4
5
6
정상 흐름:
승인 성공 응답 → 승인 테이블 저장
승인 실패 응답 → 승인 실패 테이블 저장
실제 발생:
승인 실패 응답 → 승인 테이블에 실패 코드로 저장 (잘못된 분기)
로그가 없어서 추적이 더 힘들었다
DEBUG 레벨로만 남겨진 로그는 운영에서 전혀 보이지 않았다.
어떤 요청이 들어왔고, 어떤 응답을 받았고, 어느 분기를 탔는지
아무것도 남아있지 않았다.
결국 데이터를 하나하나 열어보면서 역으로 추적했다.
내가 한 것
로그가 없으니 데이터로 때웠다.
- 승인 테이블 vs 승인 실패 테이블 데이터 대조
- 생성 시각, 거래 시각 기준으로 순서 재구성
- 첫 번째 시도와 두 번째 시도 데이터 분리
- 잘못 저장된 데이터 정합성 수동으로 맞춤
카드 정보가 없는 건 PG사 응답 자체에 없었던 거라
복구 방법이 없었다. 그냥 불완전한 데이터로 남겼다.
개선이 필요한 부분
이번 이슈를 통해 정리한 것들이다.
1. 응답 코드 기반 저장 분기를 명확히 해야 한다
승인 성공 코드가 아니라면 절대 승인 테이블에 저장하면 안 된다.
실패 응답은 무조건 승인 실패 테이블로 가야 한다.
2. 운영 로그는 INFO 레벨 이상으로 남겨야 한다
결제 요청 시작, 응답 수신, 저장 완료 같은 비즈니스 흐름은
반드시 INFO 레벨로 남겨야 운영에서 추적이 가능하다.
1
2
3
4
5
// 이렇게 하면 운영에서 아무것도 안 보인다
log.debug("결제 승인 응답: {}", response);
// 이렇게 해야 운영에서 보인다
log.info("결제 승인 응답 수신: orderId={}, responseCode={}", orderId, responseCode);
3. 뒤로가기 등 브라우저 이탈 케이스에 대한 처리가 필요하다
인증키에 유효성 검증을 추가하거나,
중복 결제 방지 로직을 서버 측에서 보완해야 한다.
마무리
결제 모듈은 서비스에서 가장 민감한 영역이다. 데이터 하나가 틀리면 정산이 꼬이고 사용자 민원으로 이어진다. 그런 모듈에서 운영 로그가 전혀 남지 않고, 성공/실패 분기가 불명확한 채로 운영되고 있었다는 게 이번 이슈의 본질이었다.
로그가 없으면 데이터로 때워야 하고, 데이터가 틀리면 아무것도 믿을 수 없다. 결제 모듈은 처음부터 꼼꼼하게 짜야 한다.
그래서 지금은
사실 이 문제들은 이미 해결되어 있다.
테스트 서버에는 승인 모듈 전체를 리팩토링한 코드가 올라가 있다.
구조도 정리됐고, 로그도 제대로 남기도록 개선됐다.
코드 리뷰까지 잡혀 있었는데,
다른 개발 건이 겹치면서 반영이 계속 미뤄지고 있다.
이슈는 파악했고, 개선도 됐고, 리뷰도 합의됐는데
정작 운영에는 아직 옛날 코드가 돌아가고 있다.
고쳐진 코드가 있는데 배포를 못 하는 것, 그것도 나름의 답답함이다.