실결제 장애 대응
포트폴리오
개발

실결제 장애 대응

실결제 장애 대응: 당일 수습과 재발 방지 체계

2026 · voidx프론트엔드 (문제 규명 · 방어 계층 · 검증 · 결제 잠금 판단)주식회사 업폴 (Upfall)21
  • Next.js
  • React
  • TanStack Query
  • Playwright
  • Vitest
  • 결제/멤버십
  • 관리자 도구
장애 인지 당일 결제 전역 차단(4개 진입점)과 피해 사용자 환불·사과 완료
FE 사각지대 3종 실증·수정
주문 API 응답 13케이스 → 오류 리졸버 + 한/영 16키
결제 단위 테스트 22건 → 47건
실카드 결제·환불 시나리오 15건 통과
유료 오픈 전 백엔드 결함 4건 선제 발견
조건 충족까지 3개월 결제 잠금 유지

결론

당일 수습 뒤 근본 원인이 확정될 때까지 3개월간 결제를 잠갔고, 실카드 왕복 검증을 거쳐 재개했습니다. 확신이 없으면 열지 않았습니다.

배경

전날 외부 네트워크 행사에서 voidx 서비스가 잠재 고객에게 소개됐습니다. 회원가입 후 결제해 멤버십이 되면 에이전트를 체험하는 구조인데, 실사용자가 멤버십 결제에서 중복 승인(동일 금액 2회)과 멤버십 미활성화를 동시에 겪었습니다. 사이트에는 취소·환불 동선이 없었고, 당시 결제·멤버십 백엔드는 레거시 코드와 중단된 리팩터링이 겹쳐 정상 동작하지 않는 상태였습니다. 구독 결제가 유일한 매출 경로인 서비스에서 신뢰가 걸린 P0 인시던트였습니다. 결제·멤버십 백엔드 구현은 별도 담당자 소관이고, 제 역할은 문제 규명과 프론트엔드 방어 계층 구축, 검증, 결제 잠금 판단이었습니다.

한 일

  1. 장애를 알린 것은 모니터링이 아니라 사용자의 문의 메일: "첫 결제 후 실패 메시지가 떠 한 번 더 결제했더니 2회 실결제됐고, 멤버십은 조회되지 않는다." 문의를 받은 즉시 팀에 공유했습니다. "사용자가 알려줄 때까지 모른다"는 감지 채널의 부재 자체를 재발 방지의 1번 과제로 삼았습니다.
  2. 프론트엔드에서 결제를 잠가 추가 피해 차단: 결제 파이프라인과 멤버십 백엔드가 정상 동작하지 않았고 담당자의 장기 부재로 즉시 손대기 어려웠습니다. 익숙하지 않은 언어의 코드를 단기간에 정상화하긴 어렵다고 보고, 백엔드를 고치는 대신 전역 결제 차단 스위치를 만들어 4개 진입점을 잠갔습니다(조회 화면은 유지). CTA를 비활성화하고 문구를 오픈 일정 안내로 바꿨습니다.
  3. 피해 사용자 당일 응대: 백엔드 환불 API가 동작하지 않아 PG사 대시보드에서 직접 전액 환불하고 사과 메일로 회신했습니다.
  4. 원인은 단정하지 않고 실증한 것만 고침: 원인 가설을 세워 프론트 사각지대 3종을 실증·수정했습니다. 2xx면 응답 본문 검증 없이 성공 처리하던 판정, 본문 없는 204 응답에서 화면이 죽던 문제, 결제 후 멤버십 활성화를 확인하지 않던 흐름입니다. 서버 측 멱등키 결함은 이와 별도로 발견했습니다. 근본 원인은 끝까지 하나로 확정하지 못했고, 구조적 배경으로는 대형 CMS 기준으로 기획된 권한·멤버십 구조가 방향이 바뀌는 동안 정리되지 못하고 덧붙여지기만 한 점이 있었습니다.
  5. 잠금을 해제 조건이 붙은 게이트로 설계: 임시방편으로 두지 않았습니다. "백엔드 중복 방지 처리와 보상 트랜잭션 배포 완료"를 선결 조건으로 문서에 못 박고, 환경 플래그로 잠금을 제어하되 기본값이 잠금이 되도록 뒀습니다. 인시던트 때 코드 배포 없이 결제 CTA를 즉시 잠그면서, 실수 커밋 한 번으로 차단이 조용히 풀리는 경로까지 막았습니다.
  6. 오류 응답 전수 매핑과 테스트 고정: 백엔드 원문 오류와 내부 API 경로가 사용자 화면에 그대로 노출되던 것을, 주문 API 응답 13개 케이스를 오류 리졸버와 한/영 메시지 16키로 매핑하고 테스트 47건으로 고정해 제거했습니다. 판정은 HTTP 상태 코드만 보던 것에서 응답 본문의 코드와 데이터까지 확인하도록 바꾸고, 204 처리는 공통 응답 처리 계층에서 일괄 정리했습니다.
  7. e2e 실행 환경 자체를 안전하게 설계하고 실카드로 검증: 결제처럼 파괴적인 플로우의 e2e는 기존 dev 서버 재사용을 금지(reuseExistingServer: false)해 프로덕션 API를 바라보는 서버에서 탈퇴·결제 시나리오가 실행되는 사고를 차단하고, 백엔드 rate limit을 공유하는 시나리오는 직렬(worker 1개)로 실행하며, setup·journeys·locale 3프로젝트로 분리했습니다. 백엔드에 중복 방지 키가 들어온 뒤에는 코드를 직접 읽어 확인한 다음 결제 흐름에 연결하고, 실카드 결제·환불 15개 시나리오로 결제 성공만이 아니라 해지·재개·환불까지의 전체 왕복을 검증했습니다.
  8. 재개는 조건이 충족될 때까지 열지 않음: 서버측 멱등 처리(백엔드 담당)와 검증이 갖춰질 때까지 3개월간 결제 잠금을 유지했습니다. 성급히 열지 않는 판단이었습니다.

결과

  • 당일: 결제 전역 차단(4개 진입점), 피해 사용자 전액 환불·사과 완료
  • FE 사각지대 3종 실증·수정: 2xx 본문 미검증 · 204 크래시 · 멤버십 활성화 미확인
  • 13개 케이스 → 16키: 주문 API 응답을 오류 리졸버와 한국어·영어 메시지로 전수 매핑
  • 22건 → 47건: 결제 관련 단위 테스트
  • 15건: 실카드 시나리오 검증 통과 (재청구 없음 · 환불 중복 없음 · 타인 결제 403 차단 포함)
  • 4건: 유료 오픈 전 선제 발견한 백엔드 결함
  • 3개월: 조건 충족까지 결제 잠금 유지

정리

이를 코드만의 문제가 아니라 정책의 문제로 봤습니다. 리팩터링을 하려면 먼저 유즈케이스 기획이 정해져야 하고, 그 뼈대가 될 현 시점 기준의 권한·멤버십·유료 정책이 필요하다는 점을 담당자에게 팔로우업했습니다. 그리고 다음 장애는 사용자가 알려주기 전에 우리가 먼저 알아야 한다는 것, 즉 감지 채널을 만드는 것이 재발 방지의 첫 과제라는 것을 확인한 케이스입니다.