개발 slecs

쿠폰 허용 결제수단 표시

목차

쿠폰 기능을 건드릴 때마다 제일 먼저 드는 생각이 "어디까지 영향 가는 거지?"다. 결제수단 허용 여부라는 개념은 단순해 보이지만, 실제로는 쿠폰 테이블, 결제수단 테이블, 그 둘을 연결하는 중간 테이블이 전부 엮여 있는 경우가 대부분이다. 이번 작업도 마찬가지였다. 쿠폰마다 어떤 결제수단에서만 사용 가능한지 화면에 표시하고, 토글로 켜고 끌 수 있게 하는 기능. 여기에 UI 개선까지 묶여 있어서 프론트와 백이 동시에 바뀌는 커밋이었다.

관련 레이어를 먼저 정리하면 아래 같다.

레이어 파일 유형 역할
퍼시스턴스 MyBatis SQL 매퍼 허용 결제수단 조회/수정 쿼리
JSP 템플릿 x3 목록, 상세, 상품 연결 화면
DDL/DML SQL 스크립트 누락 기능 데이터 삽입
문서 검증 리포트 구현 전 사전 검토 결과

파일 수는 6개지만 레이어가 전부 달라서 맥락 전환이 잦았다. SQL 매퍼 들여다보다가 JSP로 넘어가고, 다시 SQL 스크립트 확인하는 식. 이런 작업에서 집중력이 흐트러지기 쉬운 이유가 여기 있다. 레이어마다 디버깅 방법도 다르고, 에러 메시지가 뜨는 위치도 다르다.

기존 패턴을 따른다는 것의 의미

신규 기능을 추가할 때 "기존 패턴을 따른다"는 말을 자주 쓰는데, 추상적으로 들리기 쉽다. 이번엔 구체적으로 세 가지를 의미했다.

  • 쿼리 ID 네이밍 컨벤션 유지. 기존 매퍼에 있는 ID 패턴을 분석해서 새 쿼리 ID도 같은 규칙으로 지었다. 나중에 다른 사람이 매퍼 파일을 열었을 때 새로 추가된 쿼리가 어색하지 않게.
  • 엔드포인트 분리. 기존 로직에 파라미터 하나 끼워 넣는 방식 대신, 새 쿼리 ID와 별도 진입점을 만들었다. 기존 코드를 건드리면 사이드 이펙트 추적이 어려워진다. 특히 쿠폰 도메인은 결제와 맞닿아 있어서 기존 흐름에서 예상치 못한 변화가 생기면 파장이 크다.
  • JSP 바인딩 방식 통일. 기존 화면이 EL 표현식으로 모델 값을 뿌리고 있으면 새 화면도 같은 방식으로 맞춘다. 팀 내에서 이미 익숙한 패턴을 쓰면 리뷰 때도 쓸데없는 설명을 줄일 수 있다.

MyBatis 매퍼에서 허용 결제수단 목록을 가져오는 쿼리의 전형적인 패턴은 이런 형태다.

<select id="selectAllowedPaymentTypes" parameterType="string" resultType="PaymentTypeVO">
    SELECT pt.payment_type_cd,
           pt.payment_type_nm,
           CASE WHEN cpm.coupon_id IS NOT NULL THEN 'Y' ELSE 'N' END AS allowed_yn
      FROM payment_type pt
      LEFT JOIN coupon_payment_method cpm
        ON pt.payment_type_cd = cpm.payment_type_cd
       AND cpm.coupon_id = #{couponId}
     WHERE pt.use_yn = 'Y'
     ORDER BY pt.sort_order
</select>

LEFT JOIN + CASE WHEN 조합으로 "전체 결제수단 목록 + 이 쿠폰에 허용된 것은 Y 표시" 형태로 가져오는 방식이다. 쿠폰마다 허용 여부를 별도 행으로 관리하는 구조에서 흔히 쓰는 패턴이고, 화면에서 체크박스나 토글로 바인딩하기도 편하다. 두 개의 결과셋을 분리해서 가져온 뒤 애플리케이션 레이어에서 합치는 방법도 있지만, 그렇게 하면 코드량이 늘고 N+1 문제가 생길 수 있다. 단일 쿼리로 처리하는 게 이 케이스에선 더 낫다.

JSP 쪽은 대략 이런 식으로 토글을 그린다.

<c:forEach var="pt" items="${allowedPaymentTypes}">
    <label>
        <input type="checkbox"
               name="allowedPaymentTypeCd"
               value="${pt.paymentTypeCd}"
               <c:if test="${pt.allowedYn eq 'Y'}">checked</c:if> />
        ${pt.paymentTypeNm}
    </label>
</c:forEach>

단순한 코드지만 놓치기 쉬운 부분이 name 속성이다. 서버 컨트롤러에서 받는 파라미터 이름과 정확히 일치해야 한다. allowedPaymentTypeCdallowedPayTypeCode처럼 한 글자만 달라도 바인딩이 통째로 날아간다. 이번에도 그 부분에서 한 번 걸렸다. 매퍼 VO 필드명, 컨트롤러 파라미터명, JSP name 속성 세 군데가 전부 맞아야 데이터가 흐른다. 프론트에서 서버까지 연결이 끊기는 가장 흔한 원인이기도 하다.

검증에서 신경 쓴 것

기능 구현보다 검증 단계에서 더 꼼꼼히 봐야 하는 케이스들이 있다.

빈 결과 처리. 쿠폰에 허용 결제수단이 하나도 설정 안 된 경우, 화면이 빈 리스트를 받았을 때 자연스럽게 "없음" 상태를 보여주는지 확인했다. 이 케이스를 놓치면 undefined 참조 에러나 빈 테이블이 엉뚱하게 렌더링되는 일이 생긴다. 쿠폰 신규 등록 직후 허용 결제수단을 아직 설정 안 한 상태가 바로 이 케이스다.

토글 후 저장-재조회 흐름. 허용 결제수단을 바꾸고 저장한 뒤, 다시 페이지를 열었을 때 바뀐 값이 그대로 반영되어 있는지까지 봤다. INSERT/UPDATE 로직이 의도대로 동작하는지를 브라우저에서 새로고침 한 번으로 확인할 수 있어서 편했다. 여기서 캐시가 끼어들면 이전 값이 보이는 경우가 있으니 하드 리프레시를 습관적으로 한다.

SQL 스크립트 실행 결과 대조. INSERT_MISSING_FEATURES.sql을 돌리고 나서, 삽입된 데이터가 기대한 레코드 수와 값으로 들어갔는지 조회로 한 번 더 확인했다. DML 직접 실행할 때는 "성공" 메시지만 믿으면 안 된다. WHERE 조건이 잘못 걸려서 생각보다 많거나 적게 들어가는 경우가 있고, DUPLICATE KEY 무시 옵션이 붙어 있으면 실제로 삽입된 행이 0인데도 오류가 안 난다.

이번 커밋 전날 작성한 validation-report-2026-03-11.md가 생각보다 도움이 됐다. 구현 전에 어떤 케이스를 챙겨야 하는지 미리 정리해두면, 막상 검증할 때 체크리스트처럼 쓸 수 있다. 매번 그러진 못하지만 이런 규모의 신규 기능엔 해볼 만하다.

회귀 없이 마무리됐고, 연관 기능인 쿠폰 목록-상세-상품 연결 화면 엔드투엔드도 함께 확인했다. slecs 레포 특성상 기존 기능 영향도를 항상 신경 써야 하는데, 신규 쿼리와 진입점을 완전히 분리해서 기존 흐름에 손댄 게 없다. 그 판단이 이번엔 맞았다고 생각한다.

댓글 0

첫 댓글 달아줘.