세션이 죽고, 앱 열두 개를 동시에 배포한 하루
목차
아침부터 kp-pay였다.
정확히는 어젯밤에 올라온 문의에서 시작했는데, 앱 웹뷰에서 본인인증이 간헐적으로 씹힌다는 내용이었다. 재현이 안 되면 제일 골치 아픈데, 이건 재현 경로가 명확했다. 앱 웹뷰 특성상 세션이 중간에 끊긴다. 브라우저라면 쿠키가 살아있어서 세션 복구가 되지만, 웹뷰는 그게 안 되는 케이스가 있다. 인증 상태를 서블릿 세션에만 박아두면 그 순간 날아가고, 사용자 입장에선 인증을 다시 처음부터 해야 한다.
해결책은 어렵지 않았다. 세션에서 못 읽으면 Redis로 폴백하도록 로직을 추가했다. 문제는 이게 "어렵지 않은 작업"임에도 왜 이제야 잡혔냐는 거다. 앱 웹뷰 환경에서만 나타나는 버그라 테스트가 빠졌던 것 같다. 어쨌든 수정하고 넘어갔다.
9999 코드가 실패를 의미하지 않는 상황
그런데 kp-pay 본인인증 쪽에서 하나 더 나왔다. 에러코드 9999, "이미 정상인증확인" 응답이 오면 실패로 처리하고 있었던 것이다. 처음에 이 코드를 보면 당연히 오류로 해석한다. 9999라는 숫자가 주는 인상이 그렇다. 그런데 실제로는 이미 정상 인증이 된 건이 다시 확인 요청이 들어왔을 때 kp-pay가 이 코드를 내려주는 방식이었다. 즉, 이미 성공한 건인데 우리 쪽에서 실패로 처리해서 사용자는 인증이 안 됐다는 화면을 보게 됐던 거다.
이건 외부 PG 연동에서 자주 만나는 유형의 버그다. 명세서를 다 읽어야 한다. 숫자만 보고 판단하면 이렇게 된다. 9999를 받았을 때 성공으로 복구하는 처리를 추가하고 커밋했다.
두 개 다 고쳤다고 kp-pay가 끝난 게 아니었다. 오후에 가상계좌 등록 쪽에서 holderName 필드 이슈가 올라왔다. 우리가 넘기던 값이 kp-pay에서 원하는 기준과 달랐고, kp-pay 요청으로 고객명으로 변경했다. 같은 커밋이 두 번 올라간 건 중복 push 흔적이다. 신경 쓰이지만 내용은 동일하다.
그리고 오후 늦게, 출금 전 예금주 조회를 사전 호출하도록 플랫폼 출금과 하위 출금 승인 양쪽에 추가했다. kp-pay가 계좌 등록 시점에 예금주를 검증하는 구조라 이 순서를 지켜야 했는데, 기존에 빠져있던 거다. 이 부분은 실측 규칙을 문서로도 박아뒀다. 다음번에 또 이 흐름을 건드릴 때 처음부터 다시 파악하는 수고를 없애려고.
UNION에서 컬럼 하나가 빠졌다
파트너 입금내역 그룹조회 쿼리가 틀려있었다. UNION으로 여러 서브쿼리를 합치는 구조인데, 한쪽 서브쿼리에 firstReceiverMobile 컬럼이 없었다. UNION은 컬럼 수가 맞아야 하기 때문에 실행 자체가 안 되거나, 컬럼이 어긋나서 엉뚱한 데이터가 붙는 문제가 생긴다. 쿼리 매퍼 들어가서 누락된 컬럼 추가하고 정렬 맞췄다.
이런 버그는 보통 기능 확장하면서 한쪽만 추가하고 나머지를 빠뜨릴 때 발생한다. UNION 쿼리를 수정할 때마다 전체 서브쿼리의 컬럼 수를 맞춰가며 편집하는 게 귀찮긴 하다. 그래도 실수 자체보다 이게 리뷰 단계에서 안 잡혔다는 게 더 마음에 걸린다.
파트너 포탈, 발송 유형이 들어오다
오늘 파트너 쪽 작업이 볼륨 자체로는 제일 컸다. 발송포탈(SENDER) 유형을 파트너 생태계에 정식으로 추가했다. 초대링크 발송, 사업자번호 중복체크, 가이드 분기, 메뉴 세트까지.
기존에 판매(SELLER)와 공급(SUPPLIER) 두 유형으로 돌아가던 걸 SENDER를 끼워넣으면서 포탈유형을 축으로 분기하는 로직이 여러 군데 생겼다. 사업자번호 중복체크도 포탈유형별로 분리했다. 판매 파트너와 발송 파트너가 같은 사업자번호로 각각 가입할 수 있어야 하는 경우가 생기면서 축을 나눈 거다.
초대링크 로직은 유틸 클래스를 하나 더 건드렸다. 기존 초대 URL 생성 로직이 유형을 고려하지 않았는데 SENDER 파라미터를 주입할 수 있도록 수정했다. JSP 쪽 초대 리스트 페이지도 함께 수정했다.
그리고 파트너 가이드. 포탈별로 다른 안내를 보여줘야 하니 가이드 JSP에서 포탈유형에 따라 섹션을 분기하도록 했다. 예치금 관리, 거래명세서, 송금, 회원 출금내역, 출금수수료/최소출금액 안내까지 가이드에 반영했다. 그동안 "예정" 상태로 표시돼 있던 메뉴들을 실제로 활성화했다. 기능은 만들었는데 가이드가 "예정"이라고 남아있으면 파트너들이 기능을 발견 못한다. 이런 stale 상태는 보이면 바로 해제하는 게 맞다.
예치금 이력, 데이터가 말해야 한다
예치금 이력 리스트 개선은 두 방향이었다. 하나는 쿠폰명과 받는사람, 유형을 컬럼에 병기하는 것. 기존엔 이력만 있고 맥락이 없어서 파트너 입장에서 "이게 뭔 거래였지?"를 파악하기 어려웠다. 쿼리 매퍼에서 조인 붙이고 JSP에서 컬럼 추가했다.
받는분 표기도 손봤다. 이모지가 섞여 있었는데 제거했고, 비회원인 경우 이름 대신 전화번호를 표시하도록 정책을 잡았다. 비회원은 이름을 모르는 경우가 많으니 연락 가능한 정보를 보여주는 게 실용적이다.
이 작업이 두 세트 커밋이 올라간 것도 중복 push 흔적이다. 내용은 같은데 커밋 히스토리가 지저분해졌다. 오늘 여러 건에서 이런 일이 있었는데, 빠르게 치고나가다 보니 push 전에 상태 확인을 건너뛴 것 같다.
BM 개편 - 이관 한도, 수수료 순수익, 회원 출금내역
오늘 기능 중 설계적으로 제일 무게감 있는 건 이 세트였다. 관리자가 예치금 이관 한도를 비율로 설정할 수 있도록 했고, 파트너가 회원의 출금내역을 열람할 수 있도록 했으며, 출금수수료가 빠져도 순수익이 보장되는 구조로 정리했다.
이관 한도를 비율로 관리하는 건 고정값으로 박아뒀을 때 운영에서 계속 개발한테 수정 요청이 오는 걸 끊기 위한 거다. 이제 관리자가 직접 조정할 수 있다.
회원 출금내역 파트너 열람은 권한 관리가 중요하다. 파트너가 자기 회원의 내역은 볼 수 있되, 다른 파트너 회원 내역은 못 보는 격리가 되어야 한다. 쿼리에서 파트너 ID 필터를 조건으로 걸었다.
출금수수료 순수익 보장은 회계 로직이라 가장 조심스러웠다. 수수료가 먼저 떼이는 구조에서 순수익 계산이 음수가 나오는 엣지 케이스를 막는 거다. 유틸 클래스를 두 개 건드렸다.
BM 보고서에 이 진척을 두 번 갱신했다. 낮에 한 번, 늦게 한 번. 보고서 파일명에서 14일 밤 작성된 문서임을 알 수 있는데, 오늘 기능들이 완료되면서 상태를 업데이트한 거다. 보고서를 코드 커밋이랑 같은 흐름으로 관리하는 게 좋긴 하다.
판매-발송 매칭 통합, 그리고 메뉴 단일화
판매 파트너와 발송 파트너 간 매칭을 관리하는 화면 작업이 오늘 두 세트 커밋으로 들어갔다. 하나는 관리자 통합 + 포탈 메뉴 단일화 + targetSysId 주입 보정이고, 다른 하나는 매칭 상세 카드와 예치금 송금 화면 매칭 통합이다.
PartnerPortalControllerAdvice가 이 작업의 핵심이었다. 포탈 유형에 따라 세션에 targetSysId를 주입하는 로직인데, SENDER 유형이 추가되면서 기존 분기가 부족했다. 필터 쪽도 함께 수정했다.
메뉴 단일화는 SQL로도 반영했다. UPDATE_20260715_portal_link_menu_merge.sql이 그거다. 개발과 운영 양쪽에 동기화 SQL도 별도로 기록해뒀다. 이런 DDL/DML 변경분을 SQL 파일로 남기는 건 이후에 운영 반영 히스토리를 추적할 때 필수다.
PARTNER_LINK Feature 등록 SQL도 오늘 처리했다. 관리자 매칭 화면이 ENTERPRISE 업셀 게이트에 막혀있던 걸 Feature 등록으로 해소했다. 개발·운영 모두 반영 완료.
그리고 발송포탈 충전권 출금 허용. 기존에 발송포탈에서 출금이 막혀있었는데 오늘 열었다. 거래이력의 잔액 컬럼도 실측 기준으로 수정했다. 유틸 세 개, 쿠폰 쪽, 지갑 쪽, 거래 내역 쪽까지 다섯 파일을 건드린 작업이었다.
강제 업데이트 게이트, 앱 열두 개에 박다
솔직히 오늘 가장 지친 작업이 이거다. 내용은 단순하다. 앱 실행 시 서버에서 내려오는 최소 버전과 현재 버전을 비교해서, 구버전이면 스토어로 강제 이동시키는 게이트다. 뒤로가기 막고, 앱을 계속 쓸 수 없게 한다.
로직 자체는 어렵지 않다. 문제는 이 기능을 관리하는 앱이 여러 개라는 거다. Flutter 앱들, Swift로 짠 iOS 앱 하나, 그 각각에 같은 기능을 심어야 했다. 커밋 목록을 보면 pubspec.yaml, main.dart, app.dart, ExportOptions.plist가 반복해서 나온다. 버전 번호도 각 앱마다 달라서 v1.0.1+2부터 v1.1.6+8까지 들쭉날쭉하다.
각 앱마다:
- 버전 확인 로직 추가
- 다이얼로그 띄우고 스토어 URL 연결
- pubspec.yaml 버전 범프
- iOS는 ExportOptions.plist manual 서명 설정
이걸 앱별로 반복했다. 중간에 재알림 주기를 1분으로 잡는 수정도 들어갔다. 첫 버전에 구멍이 있었다. 앱을 재시작하면 게이트가 풀리는 경우가 있어서 주기를 짧게 박아 우회 경로를 막은 거다.
iOS 쪽은 배포 프로파일을 오늘 날짜로 재발급받아서 project.pbxproj에 반영했다. 서명 파이프라인 문서도 실측 기준으로 정정했다. 로그인 키체인과 프로파일명을 문서에 명시해뒀다. 처음 서명 문제를 겪는 사람이 문서만 보고 처리할 수 있도록.
Swift 앱(Pilmori)도 마찬가지다. 기존에 "업데이트 안내" 수준이었던 게 오늘 강제 게이트로 전환됐다. UpdateCheck.swift 수정하고 v1.3.2로 올렸다.
이 작업이 끝난 뒤에 함대 릴리즈 플레이북을 문서로 남겼다. docs/mobile-app-launch.md에 오늘 한 걸 정리했고, iOS와 Play Store 릴리즈용 스크립트도 만들었다. 다음번에 이런 일이 또 생기면 스크립트로 처리하려고. 솔직히 오늘 같은 방식으로는 하면 안 된다는 생각이 들었다. 앱이 많아질수록 수작업 반복은 실수 경로가 된다.
커밋 히스토리를 보면 같은 제목이 여러 번 반복된다. 이게 여러 리포지터리에 동시에 작업했다는 뜻이다. 각 앱이 별도 레포라서 어쩔 수 없는 부분이지만, 이 구조를 언제 정리할 수 있을지는 모르겠다. 오늘 작성한 릴리즈 스크립트가 그 임시 해소책이다.
다크모드 접근성, 7페이지 WCAG AA 0건
발송포탈 쪽 다크모드 저대비를 전수 해소했다. JSP 한 장, apple.css, SCSS 두 파일. WCAG AA 기준으로 7페이지를 검사해서 위반 0건으로 만들었다. 이게 처음부터 되어 있었어야 하는 건데, 다크모드를 나중에 올린 화면들이 항상 이 문제를 달고 다닌다. 텍스트 색이 배경색과 충분히 대비되지 않으면 저시력자한테 읽히지 않는다. 기능적으로 문제가 없어도 접근성 위반은 위반이다.
반응형 쪽도 손봤다. 관리자 파트너 목록 테이블 min-content를 1912px에서 1553px로 줄였다. 이게 1912px이면 일반 모니터에서 스크롤이 생긴다. 실제로 보이는 컬럼 수에 맞게 줄인 거다. SCSS와 CSS 사본 양쪽에 반영했는데, 브레드크럼 반응형 룰도 CSS 사본에 누락된 게 있어서 함께 보완했다. 컴파일된 CSS가 SCSS와 따로 관리되면 이런 불일치가 자주 생긴다. 이상적인 구조는 아닌데 지금 당장 바꿀 수 있는 상황도 아니다.
시스템 수익 가맹점 출금, WIP 두 개
시스템수익 쪽 가맹점 출금 화면 작업이 오늘 두 번에 걸쳐 WIP 커밋으로 올라갔다. 완성은 못 했다. 원장 렌더 수정, 회원상세 개선, 출금 모달 파셜 JSP 정도가 오늘 들어간 부분이고 나머지는 내일 계속한다. WIP 커밋을 남기는 건 진행분을 잃지 않기 위해서다. 큰 기능 작업 중간에 다른 급한 것들이 끼어들면 어쩔 수 없다.
복지몰(WELFARE) 수익유형은 운영자 수익 화면 세 군데에 라벨과 필터를 추가했다. 대시보드, 원장, 총합 요약 화면. 신규 유형이 생기면 이렇게 여러 화면을 같이 손봐야 한다. 빠뜨리면 그 유형의 수익이 화면에서 누락되거나 필터에서 못 잡는다.
오늘 하루를 관통하는 피로감의 이유
커밋 수가 많다. 정확히 세지 않았지만 60개 가까이 된다. 그 중에 중복 push가 몇 개 섞여있고, WIP 커밋이 있고, 같은 기능을 여러 레포에 반복한 커밋들이 있다. 내용만 보면 파트너 포탈 개편, kp-pay 버그 수정, 앱 강제 업데이트 함대 배포, BM 개편 기능, 접근성 정리, 문서 업데이트까지 여섯 개 축이 오늘 동시에 돌아갔다.
이런 날이 피곤한 이유는 단순히 작업량 때문만은 아니다. 맥락을 계속 바꿔야 한다. kp-pay 에러코드 분석하다가 SCSS 대비값 체크하고, 그러다 Flutter pubspec 버전 올리고, 다시 SQL 쿼리 컬럼 맞추는 식으로. 각 작업이 독립적이라 전 작업의 맥락을 내려놓고 새 맥락을 올려야 한다. 이게 쌓이면 단순 반복이어도 지친다.
심리테스트 자동생성은 오늘도 돌았다. 캠핑 공포 유형, 친구 피드백 스타일, 포장마차 유형 세 개. 이건 배치가 알아서 한 거라 딱히 손 댄 건 없다.
claude-live-refresh 선제갱신 가드를 문서에 박아넣은 것도 오늘이다. AUTH-DEAD 사고가 반복되는 걸 막기 위한 거다. 문서에 박아두지 않으면 다음번에 또 같은 실수가 난다. 경험으로 배운 것들을 문서로 굳히는 루틴 자체는 맞다고 생각한다.
머천트 잔액 관리 화면 웰컴 라벨도 오늘 정정했다. 현행 PG가 kp-pay로 바뀌었는데 화면 텍스트가 구 PG 기준이었다. 이런 stale 텍스트는 내부 도구라도 계속 두면 안 된다.
남은 건 시스템수익 가맹점 출금 화면 완성이다. WIP를 달고 넘어갔으니 내일 가장 먼저 잡는다.
댓글 0
첫 댓글 달아줘.