개발 slecs

웹포스 결제 모듈 통합과 목 분기 구조 도입

목차

트랙3 결제 모듈을 웹포스 흐름에 통합하는 작업을 한 번에 밀어넣었다. Controller, mock 처리, JSP fetch 연동, 모드 패널까지 한 커밋에 담겼다는 건 그만큼 사전 설계가 어느 정도 정리된 상태에서 한 방에 묶었다는 뜻이기도 하다.


왜 이 작업이 이 타이밍에 묶였나

결제 모듈 통합 작업은 보통 "언제 짜르냐"가 중요하다. 너무 쪼개면 중간 커밋 상태에서 다른 팀원이 빌드를 시도했을 때 깨진 상태를 마주하게 된다. 반대로 너무 크게 묶으면 리뷰가 힘들어지고 롤백 단위가 커진다.

이번 경우는 Controller + mock + JSP fetch + 모드 패널을 한 커밋에 묶은 게 의도적인 선택이었다. ZlgoonWebPosClient.java가 외부 PG 연동 클라이언트고, mock이 함께 들어갔다는 건 실 연동 전에 팀 내 테스트 가능 상태를 먼저 확보한 구조다. 이 순서가 맞다고 봤다.

실제로 이런 통합 작업에서 내가 팀에 항상 강조하는 우선순위가 있다.

  • mock 먼저 — 실 API 붙이기 전에 팀 전체가 UI 흐름을 검증할 수 있어야 함
  • Controller는 얇게 — 비즈니스 로직은 client/service 쪽으로, Controller는 요청 받아서 넘기고 응답 뱉는 역할만
  • JSP fetch는 명시적으로 — 비동기 호출이 어디서 일어나는지 파악하기 쉽게 fetch 호출 위치를 한 곳에 모아두는 게 유지보수에 유리

각 파일이 하는 역할을 다시 짚으면

파일 역할
ZlgoonWebPosClient.java 외부 웹포스 PG 연동 HTTP 클라이언트
내부 클래스 (payment/web) Controller 내부 VO 또는 요청/응답 래핑 클래스
쿼리 매퍼 결제 트랜잭션 저장/조회용 SQL
import.jsp 공통 JS/CSS import — 웹포스 SDK or 스크립트 추가
paymentSheet.jsp 실제 결제 시트 UI 컴포넌트, fetch 호출 포함
api-reference.md 팀 내부 API 스펙 문서 업데이트

api-reference.md가 같이 커밋된 게 눈에 띈다. 코드 변경과 문서 변경을 같은 커밋에 묶는 건 습관으로 만들어야 한다고 생각하는데, 실제로 팀에서 잘 안 되는 부분이기도 하다. 나중에 "이 API 파라미터가 뭐였지?" 하는 질문이 슬랙에 올라오는 걸 막는 가장 단순한 방법이 이거다.


mock 처리 패턴 — 이 구조가 왜 중요한지

웹포스 PG 연동 특성상 실 단말이나 실 계정 없이는 테스트가 불가능한 상황이 많다. 이럴 때 mock을 Controller 레벨에서 분기할 수 있게 두는 패턴이 유용하다.

// 예시 패턴
if (webPosProperties.isMockEnabled()) {
    return WebPosClient.mockResponse(request);
}
return zlgoonWebPosClient.request(request);

isMockEnabled() 같은 플래그를 프로퍼티로 빼두면 개발/스테이징 환경에서는 mock으로, 운영 환경에서는 실 연동으로 자동 전환된다. 이 플래그가 없으면 나중에 mock 코드를 직접 주석 처리하거나 지우는 사고가 생긴다. 팀 리뷰 때 이 부분을 먼저 확인하게 된다.

모드 패널도 같은 맥락이다. 운영자나 QA가 웹포스 동작 모드를 UI에서 바꿀 수 있게 두는 구조인데, 이게 있으면 테스트 사이클이 확 줄어든다.


paymentSheet.jsp의 fetch 구조

JSP에서 fetch를 직접 쓰는 구조는 레거시 환경에서 자주 보인다. 이 패턴에서 주의할 점은 CSRF 처리와 에러 핸들링이다.

// paymentSheet.jsp 내 fetch 패턴 예시
fetch('/api/payment/webpos/request', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content
    },
    body: JSON.stringify(payload)
})
.then(res => res.json())
.then(data => {
    if (data.resultCode !== '0000') {
        handlePaymentError(data);
        return;
    }
    proceedToConfirm(data);
})
.catch(err => {
    // 네트워크 오류와 결제 실패를 구분해서 처리
    handleNetworkError(err);
});

결제 시트에서 fetch 실패가 났을 때 UI가 멈추거나 로딩 스피너가 그냥 도는 케이스가 흔한데, .catch 블록을 반드시 명시적으로 두고 사용자에게 액션 가능한 메시지를 줘야 한다. 코드리뷰에서 이 부분이 빠지면 항상 코멘트를 남긴다.


이번 통합 커밋은 설계 → mock → 실 연동 순서를 지키면서 문서까지 함께 챙긴 구조라는 점에서 팀 관점에서 괜찮은 작업 단위였다. 트랙3 이후 실 단말 연동 단계에서 mock 플래그 제거나 SQL 튜닝이 추가로 나올 텐데, 그때도 같은 흐름으로 가면 된다.

끝.


🛒 이 글과 어울리는 추천 상품

*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.

댓글 0

첫 댓글 달아줘.