파트너 포털에 하위 파트너별 수익 기여 카드 추가
목차
partner-portal 대시보드에 하위 파트너별 수익 기여 카드를 추가했다. 건드린 파일은 SQL 매퍼 2개, 뷰+스타일 1개. 숫자만 보면 작은 작업인데, 실제로는 쿼리 설계부터 권한 체크까지 전 레이어를 통으로 짚고 가야 했다.
왜 쿼리부터 손댔나
기존 대시보드 API는 파트너 본인의 총 수익만 집계하고 있었다. 하위 파트너가 생기면서 "내 수익 중 어떤 하위 파트너가 얼마나 기여했는지"를 한눈에 보고 싶다는 요구가 생겼고, 이건 기존 엔드포인트를 재사용하기 어려운 구조였다. 단순히 프론트에서 필터를 추가하는 게 아니라, 집계 단위 자체가 달라지기 때문이다.
그래서 SQL 매퍼를 새로 만들었다. 핵심 로직은 파트너 계층 관계를 JOIN으로 풀어서 각 하위 파트너 ID별로 수익을 GROUP BY 하는 것이다. 대략 이런 형태다.
SELECT
sp.sub_partner_id,
sp.partner_name,
COALESCE(SUM(t.revenue_amount), 0) AS revenue,
COUNT(t.txn_id) AS txn_count
FROM sub_partner_map spm
JOIN sub_partner sp ON spm.sub_partner_id = sp.sub_partner_id
LEFT JOIN txn_log t
ON t.partner_id = spm.sub_partner_id
AND t.status = 'SETTLED'
AND t.settled_at BETWEEN #{startDate} AND #{endDate}
WHERE spm.parent_partner_id = #{partnerId}
GROUP BY sp.sub_partner_id, sp.partner_name
ORDER BY revenue DESC
LEFT JOIN을 쓴 이유가 있다. 하위 파트너가 등록은 됐지만 해당 기간에 거래가 한 건도 없을 때 INNER JOIN이면 그 행 자체가 사라진다. 카드에서 "0원" 으로 보여줘야 하는데 행이 없으면 프론트에서 예외 처리를 따로 해야 하고, 그게 더 지저분하다. COALESCE로 NULL을 0으로 내려보내는 것도 같은 맥락이다.
실행 계획도 미리 확인했다. spm.parent_partner_id 에 인덱스가 없으면 파트너가 늘어날수록 풀스캔이 발생하는 구조라, 기존 인덱스 커버리지를 체크해두고 진행했다.
기능 하나에 얼마나 많은 레이어가 엮이나
매퍼 변경만으로는 화면이 안 뜬다. 순서대로 짚어보면,
| 레이어 | 작업 내용 |
|---|---|
| SQL 매퍼 | 하위 파트너별 집계 쿼리 2개 (기간별, 요약) |
| Service / DAO | 매퍼 호출 및 반환 DTO 정의 |
| Controller | 신규 API 엔드포인트 추가, 응답 JSON 직렬화 |
| 권한 인터셉터 | 해당 URL 패턴이 파트너 권한 그룹에 포함되는지 확인 |
| JSP + AJAX | 카드 UI 렌더링, 비동기 데이터 fetch |
| 사이드바 | 신규 메뉴 항목 활성화 처리 |
권한 인터셉터는 빠뜨리기 쉬운 부분인데, URL만 추가하고 인터셉터 설정을 안 건드리면 관리자가 아닌 일반 파트너 계정으로 직접 URL 접근 시 예상치 못한 응답이 나온다. 이번엔 인터셉터 화이트리스트에 경로 패턴을 추가하고, 파트너 계정으로 직접 로그인해서 해당 메뉴가 보이는지, API 호출이 정상적으로 통과하는지 직접 확인했다.
프론트는 JSP에서 AJAX 방식으로 카드 데이터를 불러오는 구조다. 폴링이냐 이벤트냐를 잠깐 고민했는데, 수익 기여 카드는 실시간성보다 정확성이 우선이라 단순 페이지 로드 시점에 한 번 fetch하는 걸로 정했다. 실시간 갱신이 필요하면 그때 소켓이나 SSE를 붙이면 되고, 지금은 오버엔지니어링이다.
금융 도메인에서 숫자가 틀리면 생기는 일
이 기능에서 제일 신경 쓴 건 "이 카드에 나오는 숫자가 다른 화면의 숫자와 일치하는가"였다. 파트너가 자기 수익을 보는 화면이 이미 있는데, 거기서 보이는 총합과 하위 파트너별로 쪼갠 합이 맞지 않으면 즉시 신뢰 문제가 된다.
검증 방식은 단순했다. 기존 화면에서 특정 파트너의 정산 총합을 메모해두고, 신규 카드에서 하위 파트너별 수치를 모두 더한 값과 비교했다. 오차가 나는 케이스가 처음에 하나 있었는데, 결제 상태값이 SETTLED가 아닌 PENDING 건을 기존 집계에서는 포함하고 있었고 신규 쿼리에서는 제외하고 있어서 생긴 차이였다. 기준을 통일하고 다시 검증했다.
예외 케이스 목록도 따로 정리해뒀다.
- 하위 파트너가 없는 파트너가 접근할 때 - 빈 리스트로 응답, 카드에 "등록된 하위 파트너 없음" 표시
- 조회 기간 내 거래가 0건인 하위 파트너 - 0원으로 표시, 행 자체는 유지
- 비정상 접근(타 파트너 ID로 직접 조회 시도) - 인터셉터 단에서 차단, 빈 응답 반환
NULL이 어디서 올라올 수 있는지 사전에 다 짚어두고 쿼리와 DTO 레벨에서 막아두는 게 훨씬 낫다. 화면 렌더 단에서 NPE나 NaN이 뜨면 그게 더 보기 흉하고 원인 추적도 오래 걸린다.
사내 포털이든 외부 서비스든, 금융 숫자를 다루는 기능은 "동작은 하는데 숫자가 가끔 이상해요"가 제일 위험한 상태다. 확실히 맞거나, 확실히 틀리거나. 애매한 중간이 없어야 한다. 그게 귀찮더라도 크로스체크를 습관처럼 하게 되는 이유다.
작업 단위를 논리적으로 쪼개서 커밋했다. 매퍼 추가, API 엔드포인트 추가, 프론트 연동, 권한 처리 각각 별도 커밋. 커밋 메시지에는 "뷰 추가" 보다 "하위 파트너별 수익 집계 카드 - 기존 정산 집계와 기준 통일"처럼 왜 이렇게 했는지를 담으려 했다. 나중에 git blame으로 코드 줄을 볼 때, 그 커밋 메시지가 컨텍스트 역할을 하기 때문이다.
댓글 0
첫 댓글 달아줘.