개발 slecs

파트너 포털 주문 목록에 결제수단 뱃지 추가

목차

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

첫 댓글 달아줘.