광고 갈아엎고 투어 서른 칸 채운 날
목차
커밋 로그를 다시 훑어보면서 "이거 다 오늘이었나" 싶다. 성격이 완전히 다른 작업 세 덩어리가 동시에 돌아갔다. AdMob 강제 종료 대응, 파트너 포털 온보딩 투어 전면 완비, 중간중간 터진 버그 수습. 각각 하루 한 건씩만 해도 빡빡한 것들이다.
AdMob pub-3485가 terminated됐다
아침을 이 단어 하나로 열었다. pub-3485 terminated. 구글이 AdMob 계정을 날렸다. iOS 1.5 앱이랑 Android 1.0.5 앱, 두 개가 광고 수익 파이프라인을 동시에 잃었다. 급하게 CAS.ai로 대체 네트워크를 붙이는 마이그레이션에 들어갔다.
iOS 쪽은 Podfile부터 손봐야 했다. CAS 관련 pod를 끌어오는 건 어렵지 않은데, 문제는 시뮬레이터였다. 시뮬레이터용 Base pod가 따로 있다는 걸 처음에 빠뜨렸다가 빌드가 박살 났다. 이게 CLAUDE.md에 "시뮬 Base pod 함정"으로 기록해둔 이유다. 직접 밟기 전까지는 어디에도 없는 함정이고, 밟고 나면 두 번은 안 밟으려고 써두게 된다. Podfile.lock이랑 project.pbxproj까지 바뀐 것 보면 pod 의존성 체계가 꽤 많이 뜯겼다.
Android는 또 다른 함정이 기다리고 있었다. compileSdk36에서 hedvion_ads 패키지를 엮을 때 gradle 빌드가 꼬이는 문제. build.gradle.kts 두 군데를 건드렸고, problems-report.html까지 커밋에 포함됐으니 빌드 경고 분석까지 했다는 얘기다. Android 광고 SDK 마이그레이션은 gradle 레벨에서 뭔가 하나씩 걸리는 게 있어서 클린하게 끝나는 날이 없다. Play 스토어 빌드 버전코드 vc9까지 올렸다.
마이그레이션이 일단락된 뒤에 appads_cas_sync.sh 스크립트를 하나 만들었다. app-ads.txt를 CAS 포맷으로 주간마다 동기화하는 용도다. 수동으로 관리하다가 stale 상태를 한 번 겪으면 이런 거 자동화 안 할 수가 없다. 스크립트 짜는 데 몇 분이고, 안 짜면 나중에 "인증이 왜 안 되지" 하면서 몇 시간을 날리니까.
왜 terminated됐는지는 커밋에 없으니 쓰지 않겠다. 다만 "terminated"라는 단어 하나가 오늘 아침 분위기를 충분히 설명한다.
투어 서른 개 문패, 반나절
오늘 커밋 중 절반 이상이 파트너 포털 온보딩 투어 관련이다. 최종적으로 "포탈 전 메뉴 30종 가이드 투어 완비"까지 갔으니, 진행 중이던 걸 오늘 마무리한 거다. 그래도 오늘 하루에 쏟아진 커밋만 보면 결코 적은 양이 아니다.
시작은 발송업체(SENDER) 전용 투어 세트였다. 웰컴 순회 15스텝에 메뉴 5종. SENDER는 역할 자체가 공급사나 일반 파트너랑 다르기 때문에 투어 플로우도 달라야 한다. 같은 포털을 쓰는데 진입 경로와 핵심 기능이 다른데 공통 투어 하나로 다 때우면 "이게 왜 나한테 나오지?" 하는 경험이 된다. 타입별 투어 세트 분리는 불가피한 방향이었다.
웰컴 순회에 상품·쿠폰 관리 코스를 편입한 커밋도 있다. 판매 3레벨에 8스텝이 추가됐다. 투어는 짧으면 의미가 없고, 길면 사람이 중간에 닫아버린다. 그 사이 어딘가를 찾는 작업인데, 웰컴 순회에 쿠폰 관리까지 넣으면서 총 스텝이 제법 늘어났다. "너무 긴 거 아닌가" 싶은 순간이 있었다. 그래도 파트너 포털이 처음인 사람한테 중요한 기능을 투어에서 빠뜨리면 나중에 CS로 돌아오니까 감수했다.
상품·쿠폰 허브 투어가 2스텝에서 7스텝으로 늘어난 커밋도 있다. 허브 화면에 카드 여러 개가 있는데, 각 카드가 무슨 기능인지 설명 없이 아이콘만 던져놓으면 사람들이 안 들어간다. 카드별 기능 설명 스텝을 붙인 거다. 5스텝이 새로 생겼으니 공을 꽤 들인 화면이다.
partner-tour-steps.js가 이 커밋들에 계속 등장한다. 오늘만 이 파일을 열몇 번 수정한 것 같다. 모든 투어 스텝 정의를 이 파일 하나가 들고 있으니 어쩔 수 없다. 투어 종류가 늘어날수록 이 파일이 거대해지는 게 좀 걱정이다. 나중에 구조를 분리해야 할 것 같은데, 오늘은 일단 동작하는 게 우선이었다.
스포트라이트가 AJAX를 못 쫓아갔다
투어 스포트라이트 버그가 하나 있었다. 스포트라이트가 가리키는 엘리먼트 크기가 고정이 아니라 AJAX로 내용이 로딩되면서 바뀌는데, 투어가 이미 그려진 뒤에 내용이 들어오면 스포트라이트 박스 크기가 맞지 않았다. ResizeObserver로 해결했다. 엘리먼트 크기 변화를 감지해서 스포트라이트를 다시 그리는 방식.
"왜 크기가 안 맞지?" 하면서 한참 들여다봤다. 처음엔 투어 초기화 타이밍 문제인가 싶어서 setTimeout 같은 걸 넣어보다가, 결국 AJAX 로딩이 언제 끝나는지를 투어가 모른다는 게 핵심이었다. ResizeObserver는 타이밍 게싱 없이 실제 크기 변화에 반응하니까 깔끔한 해법이다. partner-tour.js에 들어갔다. partner-tour-steps.js가 스텝 정의라면 이쪽이 투어 엔진 쪽이다.
localStorage에서 DB로
오늘 투어 관련 작업 중 구조적으로 가장 의미 있는 건 이거다. 투어 완료 기록을 localStorage에서 tb_partner_tour_progress 테이블로 옮겼다.
localStorage에 투어 완료 기록을 저장하면 다른 브라우저나 기기에서 로그인할 때 투어가 다시 뜬다. localStorage는 사용자가 직접 날릴 수도 있다. 두 가지 이유 모두 DB 전환의 근거가 된다. Java 쪽 파트너 컨트롤러, 유틸 클래스, SQLMap 쿼리 매퍼가 같이 바뀌었다. contract.jsp와 partnerHeader.jsp도 건드렸다. 투어 완료 여부 체크가 페이지 로딩 시점에 서버에서 내려오는 방식으로 바뀐 거다. 변경 범위가 작지 않은데 오늘 다른 작업들 사이에 이걸 한 커밋으로 넣었다. 그만큼 오늘 하루가 정신없이 돌아갔다는 얘기다.
대리보기가 투어를 막고 있었다
관리자가 파트너 계정으로 대리보기를 할 때 투어 열람이 막히는 문제가 있었다. 파트너 상태가 특정 조건이 아니면 투어를 볼 수 없는 로직이 있었는데, 관리자 대리보기는 상태 무관하게 투어를 볼 수 있어야 한다. 그래야 관리자가 파트너 입장에서 투어가 제대로 동작하는지 확인할 수 있으니까. partnerHeader.jsp에서 대리보기 여부를 체크해서 분기하는 방식으로 해결했다.
웰컴 투어가 페이지 순회형으로 확장되면서 대리보기 세션에서 페이지 이동이 막히는 문제도 생겼다. 이것도 같이 풀었다. 투어 개발할 때 대리보기 케이스를 초반에 안 고려하면 이런 패치가 계속 나온다. 오늘 fix(partner) 커밋이 여러 개 나온 이유 중 하나다.
브라더스 594원 사고와 계좌인증 무한루프
버그 두 개가 오늘 꽤 날카로웠다.
브라더스 594원 사고. 커밋 제목에 금액이 붙어있는 게 인상적이다. 계약 재제출 심사 중에 상위 분배 체인에서 탈락하는 버그였다. 데이터 보정 SQL 파일(CORRECTION_20260728_brothers_dist_594.sql)까지 같이 커밋됐다. 직접 보정이 필요했다는 뜻이다. 금액이 작아도 분배 체인에서 누군가가 받아야 할 돈이 안 간 거니까 그냥 넘어갈 수 없다. 쿼리 매퍼도 수정했으니 재발 방지 로직을 같이 박은 거다. 커밋 이름에 금액을 적어두는 게 나중에 이슈를 추적할 때 확실히 도움이 된다.
kp-pay 계좌인증 쪽에서는 만료된 인증표를 들고 계속 실패하는 루프가 있었다. 인증이 만료됐는데 시스템이 계속 같은 요청을 던지는 구조였던 것 같다. 해지된 계좌 정합 문제도 같이 있었으니 계좌 상태 체크 로직에 구멍이 몇 개였던 셈이다. charge.jsp랑 서버 쪽 지갑 컨트롤러를 같이 건드렸다. 이 김에 가상계좌 당일 발행 불가 사유도 화면에 인라인으로 노출하게 바꿨다. 지금까지는 왜 안 되는지 사용자한테 설명이 없었던 거다. 익일 발행 가능 안내도 같이 넣었다. 설명 하나 추가하는 게 CS 문의를 꽤 줄여준다.
팬아웃에 총판이 없었다
계층 팬아웃 웹훅 발송 대상에 DISTRIBUTOR(총판)가 빠져 있었다. txg 웹훅이 총판 레벨로 안 가고 있었다. SQLMap 쿼리 매퍼에서 대상 조회 범위에 DISTRIBUTOR가 없었던 것. 계층 구조가 복잡한 시스템에서 이런 버그는 "왜 이쪽은 안 오지?" 하는 신고가 오기 전까지 모르는 경우가 많다. 쿼리 하나 잘못 잡으면 특정 계층만 통째로 누락된다.
선물 웹훅도 바꿨다. 선물 구매 시 GIFT_RECEIVED를 수령 대기 없이 즉시 발송하도록. 기존에는 수령 확인 후 보내던 것 같다. 구매 시점에 바로 알림이 가야 수령 측에서 "선물이 왔다"는 걸 즉각 알 수 있으니 흐름상 맞는 방향이다.
FCM 회귀와 iOS 워치독
모바일 쪽도 두 건이 있었다.
이전에 앱 시작 경로를 분리한 작업이 회귀를 만들었다. 기기ID랑 로컬 알림 설정이 날아가고, 토큰을 늦게 재전달하는 케이스도 있었다. main_screen.dart와 fcm_service.dart를 수정했다. 앱 초기화 순서를 바꿀 때 FCM 쪽 초기화 타이밍이 꼬이는 건 자주 나오는 패턴이다. 이번엔 회귀까지 됐으니 좀 찝찝하다.
iOS 런치 워치독 이슈는 앱 시작 경로에 블로킹 초기화가 있으면 iOS가 타임아웃으로 강제 종료시키는 문제다. main_common.dart에서 블로킹 코드를 제거하고, fcm_service.dart도 같이 건드린 걸 보면 FCM 초기화도 비동기로 옮겼다. 런치 워치독 이슈는 실기기에서 가끔씩 크래시가 나는 형태로 나타나기 때문에 재현하기 어렵다. 원인을 블로킹 초기화로 좁히기까지 시간이 좀 걸렸을 것 같다. README.md까지 바뀐 걸 보면 내부 문서도 정리했다.
정산 올림, Ezoic, 잡다한 것들
정산 수수료 건별 올림 계산 근거를 화면에 노출하고 올림이 발생한 건을 필터링하는 기능을 넣었다. 수수료를 건별로 올림 계산하면 합산 금액이 단순 곱셈 결과와 미묘하게 다를 수 있다. 그 차이를 설명할 근거가 화면에 없으면 파트너들이 "계산이 왜 이러냐"고 온다. Java 클래스 두 곳과 쿼리 매퍼 두 개가 동시에 바뀐 걸 보면 서비스 레이어부터 쿼리까지 한 번에 뜯은 거다.
Ezoic Incubator 파일럿도 붙였다. Dex.astro에 CMP 선로드랑 sa.min.js를 끼워 넣었다. Incubator는 트래픽 기준 심사가 있는 단계라서 스크립트를 유지하면서 통과를 기다려야 한다. CLAUDE.md에 "스크립트 유지 필수"를 기록해둔 게 이 이유다. 잊어버리고 다른 작업 중에 스크립트를 날리면 심사가 틀어진다.
파트너 기본등급 생성 시 gift_yn=Y로 페이 송금 기능을 기본 허용하도록 바꿨다. 지금까지는 수동 설정이 필요했던 것 같다. 기본값을 허용으로 바꾸면 "왜 선물을 못 보내지" 같은 CS가 줄어든다.
파트너포털 거래명세서 인쇄본 공급자 표기도 정리했다. 플랫폼으로 통일. 작아 보이지만 인쇄물에 잘못된 공급자명이 나오면 법적 문서 문제가 될 수 있다.
kp-pay 발행불가 디스코드 메모 키워드를 확장하고 회원 표기를 추가했다. 배치 쪽이랑 지갑 컨트롤러, 결제 쪽, 디스코드봇 클래스가 다 같이 바뀐 걸 보면 발행 불가 이벤트가 발생하는 여러 경로에서 통보를 동시에 강화한 거다.
대시보드 체크 모드 참고 카드 설명문 오류 정정도 있었다. 하부 기준으로 바꿔야 하는데 엉뚱한 기준이 적혀 있었다. 설명문 하나가 틀리면 사람들이 잘못된 기준으로 데이터를 해석한다.
psy 쪽 자동 테스트 생성 커밋도 들어왔다. abandoned arcade horror, family escape room, hair salon type 세 개. 이건 자동화라 수동 작업이 아니니 넘어간다.
서른 개 가까운 커밋이다. 뿌듯하기보다는 "이 중에서 내일 뭔가 또 터지겠지" 하는 생각이 먼저 든다. 특히 FCM 회귀 보정 이후로 초기화 순서가 맞는지 한 번 더 확인해보고 싶은데, 오늘 안에는 못 할 것 같다.
댓글 0
첫 댓글 달아줘.