카드 번호 검증을 요구사항 한 줄로 적으면 간단해 보입니다. 올바른 카드 번호인지 확인한다는 문장 하나로 끝납니다. 하지만 실제로 확인해야 할 대상은 여러 층으로 나뉘고, 어느 층을 어디에 두느냐에 따라 사용자 경험과 안정성이 함께 달라집니다. 이 글은 검증이 막으려는 것이 정확히 무엇인지부터 시작해, 확인 순서와 흔한 구현 실수를 정리합니다.
검증은 무엇을 막으려는 걸까?
첫 번째 목적은 입력 실수입니다. 한 자리를 잘못 누르거나, 카드에 인쇄된 숫자를 잘못 옮겨 적는 일은 흔합니다. 이런 실수를 결제 요청을 보내기 전에 잡으면 사용자는 기다리지 않고 바로 고칠 수 있습니다.
두 번째 목적은 불필요한 요청을 줄이는 것입니다. 형식이 어긋난 값으로 승인 요청을 보내면 발급사까지 갔다가 거절되고, 그 왕복 시간만큼 사용자가 기다립니다. 형식 검사는 이런 헛된 요청을 줄입니다.
세 번째 목적은 데이터 품질입니다. 형식이 맞는 값만 저장소에 들어가면, 나중에 정산 데이터를 맞추거나 문의를 처리할 때 훨씬 수월합니다.
여기서 분명히 해 둘 것이 있습니다. 이 검증은 결제 가능 여부를 판단하지 않습니다. 계좌가 존재하는지, 한도가 남았는지, 카드가 정지되었는지는 발급사만 알 수 있습니다. 검증이 통과했다는 사실은 형식이 맞다는 뜻일 뿐입니다.
어떤 순서로 확인해야 할까?
순서를 정하는 기준은 하나입니다. 판단이 쉬운 것부터 걸러야 오류 안내가 구체적으로 나옵니다.
- 입력 정리: 공백, 하이픈, 보이지 않는 문자를 걷어냅니다.
- 빈 값 확인: 아무것도 입력하지 않은 상태를 다른 오류와 구분합니다.
- 자릿수 확인: 카드사별로 허용되는 최소와 최대 길이를 함께 봅니다.
- 문자 종류 확인: 숫자만 남았는지 확인합니다.
- 카드사 확인: 시작 숫자 조합으로 카드사를 판별합니다.
- 검산 숫자 확인: 마지막 한 자리가 계산과 맞는지 봅니다.
이 순서를 지키면 오류 문구가 자연스러워집니다. 빈 칸에 자릿수 오류를 띄우거나, 두 자리만 입력했는데 검산 실패라고 알려 주는 화면은 사용자를 헷갈리게 합니다.
그러면 이 순서에서 검산 숫자는 어디에 놓이는 걸까요? 검산은 형식 검사의 마지막 단계입니다. 이 검사가 잡는 것은 잘못 누른 숫자와 대부분의 자리 바꿈이며, 존재하지 않는 계좌나 정지된 카드는 잡지 못합니다. 그래서 검산을 통과했다는 이유로 결제를 진행하면 안 되고, 반대로 검산에 실패했다고 해서 승인 요청을 보내면 안 됩니다.
계산 자체는 공개된 산수입니다. 계산 순서와 자주 틀리는 지점은 Luhn 알고리즘에 따로 정리했습니다.
화면 검사와 서버 검사는 어떻게 나눌까?
화면 검사의 목적은 빠른 안내입니다. 사용자가 입력하는 동안 자릿수와 카드사를 확인해 주면 오타를 즉시 알 수 있습니다. 다만 화면 검사는 우회될 수 있으므로, 그것만으로 안전을 보장할 수는 없습니다.
서버 검사는 최종 방어선입니다. 화면을 거치지 않은 요청도 서버는 똑같이 검사해야 합니다. 자동화 스크립트나 오래된 앱 버전은 화면 검사를 건너뛰기 때문입니다.
두 곳에서 같은 규칙을 써야 결과가 어긋나지 않습니다. 규칙을 한 벌만 두고 양쪽이 그것을 참조하는 구성이 이상적입니다.
| 검사 위치 | 목적 | 놓칠 수 있는 것 |
|---|---|---|
| 화면 | 즉시 안내 | 우회된 요청 |
| 서버 | 최종 확인 | 없음 |
사용자에게 무엇을 알려 줄까?
오류 문구는 사용자가 다음에 무엇을 해야 하는지 알려 주어야 합니다. 잘못되었다는 사실만 알려 주는 문구는 도움이 되지 않습니다.
- 빈 값에는 무엇을 입력해야 하는지 알려 주기
- 자릿수가 모자라면 몇 자리인지 알려 주기
- 카드사를 알 수 없으면 앞자리를 확인해 달라고 안내하기
- 숫자가 아닌 문자가 있으면 그 사실을 짚어 주기
- 검산이 어긋나면 카드에 인쇄된 숫자를 다시 확인해 달라고 안내하기
주의할 점은 오류 문구가 카드 정보를 되비추지 않게 하는 것입니다. 입력한 값 일부를 그대로 보여 주면 화면 캡처나 로그를 통해 값이 새어 나갈 수 있습니다.
개발자를 위한 메모: 흔한 실수 목록
- 자릿수를 하나로 고정하기: 카드사마다 길이가 달라 정상 카드가 막힙니다.
- 숫자가 아닌 문자를 걷어내지 않기: 사용자는 공백과 하이픈을 자연스럽게 입력합니다.
- 검산의 시작 위치를 한 칸 어긋나게 두기: 멀쩡한 번호가 전부 거부됩니다.
- 검산만 하고 카드사 확인을 빼먹기: 앞자리가 어긋난 값을 통과시킵니다.
- 서버에서 다시 검사하지 않기: 화면만 믿는 구조는 우회에 취약합니다.
- 오류 사유를 하나로 합치기: 사용자가 고칠 방법을 알 수 없습니다.
테스트 데이터를 만들 때는 성공 케이스만큼 실패 케이스를 준비해야 합니다. 자릿수 부족, 문자 혼입, 검산 불일치, 미지원 카드사를 각각 재현할 수 있어야 검증 순서가 의도대로 동작하는지 확인할 수 있습니다. 쓸 번호는 테스트용 신용카드 번호에서 고르는 기준을 참고하세요.
번호를 숫자로 다루면 무엇이 깨질까?
카드 번호는 숫자처럼 보이지만 숫자로 다루면 안 됩니다. 정수나 실수로 바꾸는 순간 두 가지가 깨집니다. 첫째, 맨 앞에 오는 0이 사라집니다. 둘째, 값이 커지면서 정밀도가 떨어집니다. 부동소수점이 정확히 표현할 수 있는 자릿수에는 한계가 있어, 긴 번호의 뒷자리가 반올림되거나 다른 값으로 바뀔 수 있습니다. 요청 본문을 다루는 형식에 따라 지수 표기로 변환되는 일도 있습니다. 번호는 처음부터 끝까지 문자열로 두고, 검사할 때만 문자 단위로 다루는 편이 안전합니다.
검증 함수를 직접 만들까 가져다 쓸까?
두 방법 모두 장단점이 있습니다. 공개된 구현을 가져다 쓰면 카드 조직별 대역표를 직접 관리하지 않아도 되지만, 지원하지 않는 자릿수나 새로 생긴 대역이 반영되는 시점이 그 구현에 달려 있습니다. 직접 만들면 규칙을 한 곳에서 관리할 수 있지만, 자릿수를 하나로 고정하는 실수가 들어가기 쉽습니다. 어느 쪽을 고르든 화면과 서버가 같은 구현을 쓰는지가 더 중요합니다. 선택한 이유를 함께 적어 두면 나중에 교체할 때 비교하기 쉬워집니다.
다음 단계
검증 함수가 있다면 자릿수 확인과 카드사 확인이 실제로 들어 있는지부터 확인해 보세요. 그다음 위 실수 목록을 하나씩 대입해 재현되는지 시험하고, 되도록이면 서버 검사에도 같은 규칙이 적용되었는지 점검하세요. 생성기에서 카드사별 번호를 만들어 두면 경계값 시험 데이터를 손쉽게 확보할 수 있습니다. 이때 만들어지는 번호는 구조적으로 유효하지만 실제로 발급된 적이 없어 어떤 결제에도 쓸 수 없습니다.