개발 slecs

파트너 포탈 출금 계좌를 승인된 등록계좌로만 제한

목차

파트너 포탈에서 출금 계좌를 관리할 때 "어떤 계좌든 선택 가능한" 상태는 금융 도메인에서 허용 불가다. 별도 승인 절차를 거쳐 등록된 계좌만 출금 대상이 될 수 있어야 하고, 그 제약을 UI에서만 막는 게 아니라 API와 쿼리 레벨까지 내려가서 강제해야 한다.

이번 작업은 그 제약을 partner-portal 영역에 실제로 구현한 것. 변경 파일은 내부 클래스 1개, 뷰/스타일 1개로 결과물 자체는 작지만, 설계 범위는 단순 UI 변경보다 훨씬 넓었다.

왜 쿼리 레벨부터 설계했나

기존 화면과 API는 "승인된 계좌 목록"이라는 개념 자체를 노출하지 않았다. 그냥 파트너의 계좌 전체를 가져오는 구조였는데, 이걸 승인 여부 필터 없이 그대로 출금 선택지로 쓰면 미승인 계좌도 버젓이 선택지에 뜬다.

프론트에서만 필터링하는 방식도 고려했다가 채택하지 않았다. 이유는 두 가지다.

  • 프론트 필터링은 우회 가능하다. AJAX 요청을 직접 조작하거나 파라미터를 수정하면 미승인 계좌 ID를 그대로 서버에 전달할 수 있다.
  • 목록을 한 번 받아서 프론트에서 걸러놓으면, 그 사이에 계좌 승인이 취소돼도 화면에는 여전히 선택 가능한 계좌로 보일 수 있다.

그래서 API가 처음부터 승인된 계좌만 반환하도록 쿼리 레벨에서 필터링하고, 서버 측 처리 시점에도 한 번 더 검증하는 구조로 잡았다. 내부 클래스 변경이 여기에 해당한다.

// 승인된 계좌만 조회
List<Account> accounts = accountRepository
    .findByPartnerIdAndStatus(partnerId, AccountStatus.APPROVED);

// 출금 요청 처리 직전 재검증
if (account == null || !account.isApproved()) {
    throw new InvalidAccountException("승인되지 않은 계좌입니다.");
}

출금 요청이 실제로 들어왔을 때 계좌 상태를 다시 확인하는 이유가 있다. 목록 조회 시점과 요청 처리 시점 사이에 계좌 상태가 바뀔 수 있기 때문이다. 조회할 때 approved였던 계좌가 요청 처리 직전에 취소 처리될 수도 있다. TOCTOU(Time-of-Check to Time-of-Use) 문제인데, 금융 처리 흐름에서는 이 간격을 그냥 넘기면 안 된다. 조회 때 한 번, 처리 때 한 번, 이중 검증이 기본값이 돼야 한다.

구현 흐름과 레이어별 체크

신규 메뉴 추가부터 권한 인터셉터 통과 확인까지, 실제로 건드린 레이어는 다음과 같다.

레이어 변경 내용
백엔드 API 승인 계좌 필터 조건 추가, 요청 처리 시 재검증
내부 클래스 계좌 조회 메서드, 상태 검증 로직
JSP + AJAX 계좌 선택 UI, 비동기 목록 로드
권한 인터셉터 신규 메뉴 경로 등록 및 통과 확인
사이드바 신규 메뉴 활성화 처리

JSP + AJAX 구조는 요즘 기준으로는 오래된 방식이지만, 기존 포탈이 이 스택으로 운영 중이어서 맞췄다. 새 기능을 추가할 때 "이 기회에 현대적인 스택으로 바꾸자"는 생각은 안 하는 편이다. 기술 스택 전환을 기능 추가와 함께 하면 두 가지를 동시에 검증해야 하는 리스크가 생긴다. 어디서 버그가 난 건지도 불명확해진다. 스택은 그대로 두고 기능 로직에만 집중하는 게 맞다고 본다.

권한 인터셉터는 신규 경로를 등록하지 않으면 403이 떨어진다. 당연한 동작이지만 기능 추가할 때마다 빠뜨리기 쉬운 항목이다. 인터셉터 설정 파일에 경로 패턴을 등록하고, 실제로 해당 경로를 호출해서 통과 여부를 눈으로 확인하는 것까지가 체크리스트다.

실시간 갱신 여부도 고민한 항목이다. 승인 계좌 목록을 폴링으로 주기적으로 갱신할지, 아니면 사용자가 화면에 진입할 때 한 번만 조회할지. 계좌 승인/취소가 빈번하게 일어나는 케이스가 아니고, 출금 요청 직전 서버 재검증이 있으니 UI 레벨에서는 진입 시 1회 조회로 충분하다고 판단했다. 불필요한 폴링을 추가하면 서버 부하도 올라가고 UX도 지저분해진다.

검증 방식과 커밋 단위

구현 후 직접 화면에서 동작을 확인했다. 승인된 계좌만 선택지에 노출되는지, 미승인 계좌는 목록에 없는지, 기존 출금 관련 화면의 숫자가 바뀌지 않았는지 cross-check했다.

변경 전에 관련 화면 수치를 메모해두고, 변경 후 같은 케이스로 재확인하는 방식은 이번에도 유효했다. 잔액 합계나 이력 카운트처럼 다른 화면에도 걸쳐 있는 값들이 새 필터 조건 때문에 달라지지 않았는지 확인하는 게 핵심이다. 금융/결제 도메인에서 "대충 맞는 것 같다"는 통과 기준이 될 수 없다. 특정 케이스에서만 이상한 값이 나오거나 화면마다 숫자가 다르면 신뢰 문제로 바로 이어진다.

커밋은 논리적으로 독립된 단위로 쪼갰다. 쿼리 레벨 변경, 내부 클래스 로직, 뷰/스타일, 인터셉터 등록 순으로. 변경 파일이 많지 않으니 한 커밋에 몰아도 되지 않냐는 생각도 들지만, 나중에 특정 변경에서 문제가 생겼을 때 어느 커밋에서 깨졌는지 좁히는 속도가 달라진다. 커밋 메시지에는 "무엇을 바꿨다"보다 "왜 바꿨는지"를 담으려 했다.

기능 하나를 추가하는 작업이 버튼 하나 추가로 끝나지 않는다는 건 이번에도 확인됐다. SQL 필터 조건, 상태 이중 검증, 예외 처리, 화면 렌더링, 권한 경로 등록이 모두 엮여 있고, 어느 하나라도 빠지면 특정 사용자에게 이상한 동작이 나온다. 작업 범위가 작아 보여도 레이어별 체크리스트를 돌리는 이유가 여기 있다.

댓글 0

첫 댓글 달아줘.