글 검색

제목으로 글을 찾습니다

숙박엔 성인인증이 필요한데, CX 문의가 이것 때문에 많다면?

2026-05-28#ux#react#frontend

예약마다 반복되는 성인인증 퍼널을 역방향 입력 폼 하나로 재설계해, 배포 일주일 만에 본인인증 문의를 절반으로 줄인 기록.

비대면 체크인으로 운영되는 숙박 예약 플랫폼에는 특수한 제약이 있다. 비로그인 게스트의 개인정보를 보관하지 않기 때문에, 게스트는 예약할 때마다 성인인증을 다시 해야 한다. 인증은 알림톡으로 받은 링크에서 시작되는데, 완료까지 화면 4개를 통과해야 했다 — 안내·내외국인 구분, 인증 방식 선택, 정보 입력 폼, 인증번호 입력. 퍼널이 늘어질수록 이탈이 생기고, 이탈한 게스트는 CX팀 문의로 돌아왔다. 같은 유형의 문의가 반복되는 걸 보고 이 퍼널의 재설계를 제안했다.

화면을 고치지 말고 퍼널을 접자#

처음 검토한 건 퍼널별 화면 개선이었다. 문구를 다듬고, 버튼을 키우고. 그런데 각 화면은 이미 단순했다. 문제는 화면의 품질이 아니라 화면의 개수였다. 알림톡을 눌러 들어온 게스트가 원하는 건 하나("내 예약 확인")인데, 그 앞에 선택지와 전환이 네 번 놓여 있었다.

방향을 정하기 전에 진입 데이터를 봤다. 인증의 대부분이 모바일에서 이뤄지고 있었다 — 알림톡으로 시작하는 플로우니 당연한 결과다. 그래서 원칙을 이렇게 잡았다. 모바일 키보드를 기준으로 퍼널 자체를 접는다. 화면 넷을 하나로 합치고, 화면 안에서의 이동도 키보드에서 손을 떼지 않고 끝나게 한다.

개선 전 4개 화면 퍼널과 개선 후 단일 화면
비교

거꾸로 쌓이는 입력 폼#

참고한 건 토스의 가입 화면이다. 토스는 입력을 마치면 다음 질문이 위에서 내려오고, 이미 입력한 값들은 아래로 밀린다. 시선은 늘 화면 위쪽의 새 질문에 있고, 지나온 입력은 스크롤 없이 아래에 남아 언제든 눌러서 고칠 수 있다. 한 번에 하나만 묻는 폼과 전체 필드를 나열한 폼의 장점을 합친 구조다.

이 구조를 인증 폼에 그대로 적용했다. 이름 → 주민등록번호 앞 7자리 → 통신사·휴대폰 번호 → 인증번호 순서로 묻되, 새 입력이 제목 바로 아래에 등장하고 이전 입력은 아래로 쌓인다.

역방향 입력 폼 진행 3단계 — 이름, 주민등록번호,
인증번호

구현의 뼈대는 상태 두 개다. 지금 어디를 묻고 있는지(step)와 어떤 입력들이 화면에 나와 있는지(revealed). 렌더링은 JSX를 역순으로 배치하는 게 전부다.

const [step, setStep] =
  useState<'NAME' | 'RRN' | 'PHONE' | 'CONFIRM'>('NAME');
const [revealed, setRevealed] = useState<string[]>([]);
 
return (
  <>
    {/* 늦게 열리는 입력일수록 JSX에서 위에 놓는다 */}
    {revealed.includes('CONFIRM') && <FormBox>인증 번호…</FormBox>}
    {revealed.includes('PHONE') && <FormBox>휴대폰 번호…</FormBox>}
    {revealed.includes('RRN') && <FormBox>주민등록번호…</FormBox>}
    <FormBox>이름…</FormBox> {/* 항상 맨 아래 */}
  </>
);

FormBox에는 위에서 내려오는 슬라이드 애니메이션을 붙여 "다음 질문이 도착했다"는 감각을 만들었다.

const slideDown = keyframes`
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
`;

'완료'만 눌러도 끝까지 간다#

퍼널을 접은 만큼, 화면 안의 이동도 없앴다. 규칙은 하나다 — 입력이 차면 묻지 않고 다음으로 간다.

  • 이름은 키보드 패널의 '완료'(enterKeyHint="done")가 곧 다음 버튼이다.
  • 주민등록번호는 7자리가 차는 순간 자동으로 다음 단계로 넘어가고, 통신사 선택 바텀시트가 바로 열린다.
  • 휴대폰 번호 11자리와 통신사가 갖춰지면 약관 동의 시트가 열리고, 동의하면 인증번호가 발송된다.
  • 인증번호는 6자리가 차는 순간 자동으로 검증한다. '확인' 버튼을 누를 일이 없다.
// 주민등록번호가 차면 자동 진행
useEffect(() => {
  if (step === 'RRN' && rrn.length === 8) {
    setStep('PHONE');
    setRevealed((prev) => [...prev, 'PHONE']);
    setTimeout(() => phoneRef.current?.openAgencyModal(), 100);
  }
}, [rrn, step]);
 
// 인증번호 6자리가 차면 자동 검증
useEffect(() => {
  if (code.length === 6) onConfirm();
}, [code]);

되돌아가는 것도 키보드로 해결했다. 빈 입력에서 Backspace를 누르면 이전 입력으로 포커스가 돌아간다. 아래에 쌓인 이전 입력을 눌러도 된다.

키보드가 화면을 가리는 문제#

모바일 폼의 마지막 관문은 가상 키보드다. 키보드가 올라오면 뷰포트의 절반이 사라지는데, 하단 CTA 버튼이 키보드에 가려지거나 엉뚱한 위치에 뜨는 문제가 브라우저·OS마다 다르게 나타난다. visualViewport API로 실제 보이는 영역을 구독해서 페이지 높이를 동기화했다.

useEffect(() => {
  const syncWithViewport = () => {
    const { height, offsetTop } = window.visualViewport!;
    pageRef.current.style.height = `${height}px`;
    pageRef.current.style.transform =
      `translateX(-50%) translateY(${offsetTop}px)`;
  };
  window.visualViewport!.addEventListener('resize', syncWithViewport);
  window.visualViewport!.addEventListener('scroll', syncWithViewport);
  document.addEventListener('focusin', syncWithViewport);
  // ...cleanup
}, []);

이렇게 하면 "다음" 버튼이 항상 키보드 바로 위에 붙어 있다. 버튼 텍스트도 상태를 따라간다 — 입력 중엔 '다음', 정보가 다 차면 '인증요청', 인증번호 단계에선 '확인'.

주민등록번호 뒷자리는 화면 밖에 숨긴 1px input이 실제 값을 받고, 화면에는 마스킹 점 7개와 깜빡이는 가짜 커서만 그린다. 값은 받아야 하지만 화면에 남기면 안 되는 입력이라, 표시와 입력을 분리했다.

결과#

  • 인증 완료까지 화면 4개 → 1개. 화면 안에서도 별도 버튼 탐색 없이 키보드 '완료'와 자동 진행만으로 끝까지 간다.
  • 배포 후 일주일간 CX팀의 본인인증 관련 문의가 약 50% 줄었다. 인증 퍼널에서 이탈해 문의로 돌아오던 게스트가 그만큼 스스로 완주하게 됐다는 뜻이다.

화면을 하나씩 다듬는 것과 퍼널 자체를 접는 것은 다른 일이다. 반복되는 문의는 화면의 문제가 아니라 구조의 신호였고, 데이터로 사용 환경(모바일)을 좁힌 뒤에는 설계 기준이 명확해졌다 — 게스트의 손가락이 키보드를 떠나지 않게 할 것.

참고: MDN — VisualViewport API · MDN — enterkeyhint