파트너 포털 주문 목록에 결제수단 뱃지 추가
목차
partner-portal 주문 목록 화면에 결제수단 뱃지를 붙였다. 카드냐 가상계좌냐를 한눈에 알 수 있도록. 바뀐 파일 수만 보면 SQL 매퍼 하나, 뷰·스타일 하나로 끝난 일인데, 그 두 파일까지 도달하기까지 생각할 게 꽤 있었다.
쿼리 먼저 손댄 이유
뱃지 하나 붙이는 게 왜 SQL 매퍼부터 시작해야 하냐고 물으면, 결제수단 정보가 원래 주문 목록 조회 결과에 포함되어 있지 않았기 때문이다. 기존 쿼리는 주문 상태·금액·파트너 식별자 정도만 SELECT하고 있었고, 결제수단 코드는 다른 테이블에 있었다.
선택지는 두 가지였다.
- 기존 목록 API 응답에 JOIN을 추가해서 결제수단 코드를 함께 내려준다.
- 프론트에서 주문 ID 목록을 가지고 별도 엔드포인트를 한 번 더 호출한다.
후자는 N+1에 가까운 구조가 되거나, 최소한 두 번의 네트워크 왕복이 생긴다. 목록 화면은 한 번의 렌더에 정보가 완결되는 게 맞다고 판단해서, 기존 매퍼의 SELECT에 JOIN을 추가하는 방향으로 결정했다. 쿼리가 무거워질 수 있으니 실행 계획부터 확인했고, 결제 관련 테이블에 인덱스가 이미 잡혀 있어서 조인 비용이 크지 않았다.
-- 기존 (결제수단 없음)
SELECT o.order_id, o.status, o.amount, o.partner_cd
FROM orders o
WHERE o.partner_cd = #{partnerCd}
-- 변경 후 (LEFT JOIN으로 결제수단 코드 추가)
SELECT o.order_id, o.status, o.amount, o.partner_cd,
p.pay_method_cd
FROM orders o
LEFT JOIN payment_info p ON p.order_id = o.order_id
WHERE o.partner_cd = #{partnerCd}
LEFT JOIN을 쓴 건 결제 정보가 아직 없는 주문도 목록에 나와야 하기 때문이다. INNER JOIN으로 했다간 결제 전 주문이 화면에서 사라지는 상황이 생긴다. 금융 도메인에서 이런 실수는 "왜 내 주문이 없어졌죠?"로 돌아온다.
뷰·스타일에서 한 것
매퍼가 결제수단 코드를 내려주기 시작하면 JSP에서 분기만 하면 된다.
<c:choose>
<c:when test="${order.payMethodCd == 'CARD'}">
<span class="badge badge-card">카드</span>
</c:when>
<c:when test="${order.payMethodCd == 'VBANK'}">
<span class="badge badge-vbank">가상계좌</span>
</c:when>
<c:otherwise>
<span class="badge badge-unknown">-</span>
</c:otherwise>
</c:choose>
otherwise 분기를 넣은 게 포인트인데, 결제수단 코드가 NULL이거나 매핑 테이블에 없는 값이 들어올 때 화면이 텅 빈 채로 렌더되는 걸 방지한다. 대시 하나를 보여주는 것과 아무것도 안 보여주는 것의 차이는 작아 보이지만, 운영 중에 "여기 왜 아무것도 없어요?"라는 문의가 오면 디버깅 시작점부터 달라진다.
스타일은 기존 뱃지 컴포넌트와 색상 변수를 맞추는 정도였다. 새 CSS 클래스를 마구 만들면 나중에 디자인 일관성을 잡을 때 수습이 힘들어지니까, 기존 뱃지 패턴을 따라가면서 색깔만 분기했다.
설계할 때 체크한 것들
| 항목 | 결정 | 이유 |
|---|---|---|
| 실시간 갱신 여부 | 폴링 없음, 페이지 로드 시 1회 | 결제수단은 생성 후 변경되지 않음 |
| JOIN 방식 | LEFT JOIN | 결제 전 주문도 목록에 포함 필요 |
| NULL 처리 | otherwise로 - 표시 |
빈 화면 대신 명시적 표현 |
| 권한 체크 | 기존 인터셉터 통과 확인 | 별도 엔드포인트 없어서 추가 권한 설정 불필요 |
| 성능 | 실행 계획 사전 확인 | 기존 인덱스로 커버 가능 |
실시간 갱신이 필요 없다는 판단이 제일 쉬웠다. 결제수단은 주문 생성 시점에 고정된다. 카드로 결제한 주문이 나중에 가상계좌로 바뀌는 일은 없다. 그러니 AJAX 폴링이나 웹소켓 같은 걸 고민할 필요가 없었다.
반대로 주문 상태 같은 필드는 실시간성이 중요해서 다른 취급을 해야 한다. 같은 목록 화면 안에서도 필드마다 갱신 전략이 달라지는 게 현실이다.
검증 방식
구현 후에는 직접 화면에서 동작 확인을 했다. 카드 결제 주문과 가상계좌 결제 주문이 각각 뱃지를 올바르게 보여주는지, 결제 전 주문은 -로 나오는지. 그리고 결제 금액 합계가 기존 화면의 숫자와 일치하는지 cross-check.
이 cross-check가 귀찮아 보이지만 JOIN 추가 이후에 집계 숫자가 달라지는 케이스가 실제로 생긴다. 1:N 관계인데 JOIN 조건이 느슨하면 row가 뻥튀기되어 SUM이 올라가는 현상이다. LEFT JOIN인 경우도 결제 정보가 복수 row인 테이블과 붙이면 같은 문제가 생긴다. 이번 케이스는 payment_info가 주문당 1개임을 확인하고 들어갔지만, 그래도 합계 숫자 대조는 빠뜨리지 않았다.
작업 후기
파트너 포털처럼 B2B 내부 서비스에서 기능 하나 추가한다는 게 결국 SQL 집계·상태 처리·예외 방어·화면 렌더링·권한 통과가 전부 엮여있는 작업이라는 걸 이번에도 다시 확인했다. 뷰 파일만 건드리면 될 것 같던 일이 매퍼까지 내려가야 했고, 매퍼를 고치면서 JOIN 방식과 NULL 처리를 같이 결정해야 했다.
금융·결제 도메인에서 숫자 하나가 안 맞으면 신뢰가 무너진다는 건 반복해서 체감한다. "대충 비슷해 보인다"는 기준으로 검증을 마무리하면 반드시 나중에 다시 돌아온다. 꼼꼼함이 선택지가 아니라 기본값이어야 하는 영역이다.
커밋은 매퍼 변경, 뷰 변경, 스타일 분리로 쪼갰다. 나중에 뭔가 깨졌을 때 "어느 커밋에서 틀어졌지?"를 빠르게 좁히기 위해서다. 변경 범위가 작으면 작을수록 bisect도 쉽고, 리뷰어도 맥락을 잡기 쉽다.
댓글 0
첫 댓글 달아줘.