앱 심사 거절 넷 받고 플랫폼 불도 끈 날
목차
오늘은 두 전선이 동시에 열렸다. App Store 심사 거절 대응과 플랫폼 정산 이슈들. 두 전선이 시간을 나눠 쓴 게 아니라 완전히 겹쳤다. 오전에 Dart 코드 들여다보다가 오후에는 Java CommonMap 버그 고치고, 저녁엔 다시 App Store Connect 열어서 리뷰어 회신 작성하는 식으로. 집중이 흐릿한 하루였지만 이상하게 작업량은 많았다.
거절장이 네 장 쌓인 사람의 아침
2.1(a), 2.1(b), 1.4.1, 4.3(a). 어젯밤에 확인하고 오늘 아침에 다시 봐도 네 개가 맞다. 앱 심사 거절이 한 번에 여러 이유로 날아오는 건 처음이 아닌데, 이번엔 종류가 다양했다.
2.1(a)는 iPad에서 카메라 PPG를 실행하면 앱이 죽는 문제였다. HRV 측정이 카메라 후면 플래시를 광원으로 쓰는 방식인데, 일부 iPad 모델은 후면 카메라에 토치가 없다. 그 상황을 전혀 고려하지 않았던 거다. ppg_frame_source.dart에서 토치를 켜려는 시도 자체가 예외로 터지고, 그게 measure_controller를 타고 올라가 앱이 튕겼다. 고친 방향은 단순하다 - 플래시 없는 기기임을 감지하면 토치 제어 코드를 우회하고, 측정을 거부하는 대신 가능한 범위에서 측정을 진행한다. Info.plist도 건드렸는데 카메라 사용 목적 문구 쪽이었다. screenshots_harness.dart도 수정 목록에 있는 걸 보면 스크린샷 테스트 시나리오에도 해당 기기 케이스를 추가한 것 같다.
2.1(b)는 Pro 언락이 재현 안 된다는 거였다. 앱 재시작 후 구매 복원이 안 된다는 취지. pro_gate.dart랑 measure_screen.dart를 고쳤는데, 핵심은 purchaseStream을 첫 번째 await 이전에 구독해야 한다는 것이었다. 스트림을 늦게 붙이면 앱 런치 시점에 이미 날아간 트랜잭션을 재생할 때 이벤트를 못 잡는다. 전형적인 비동기 타이밍 버그다. 고치고 나서 찜찜한 건 "이게 왜 이제야 나왔지"였는데, 아마 리뷰어가 기기를 새로 복원한 상태에서 테스트했을 거다. 그런 시나리오에서만 타이밍 경쟁이 눈에 띌 정도로 벌어지니까. ExportOptions.plist랑 pubspec.yaml이 같이 바뀐 건 이 수정과 묶인 배포 준비 작업일 것이다.
1.4.1은 방법론 문서 관련이었다. HRV 측정의 과학적 근거를 명시하라는 요구인데, 이건 코드 문제가 아니라 심사 트랙의 문제다. Pulsora-HRV-Methodology.pdf를 만들어서 첨부했고, asc_review_info.py는 리뷰어에게 보낼 답변 자료를 정리하는 스크립트였다. hrv-methodology.md와 resolution-reply-20260826.md까지 작성했다. 코드보다 글이 더 많은 작업이라 시간이 은근히 걸렸다. 이 트랙은 이제 내 손을 벗어났다. 리뷰어가 검토하고 판단할 때까지 기다리는 것 외에 할 수 있는 게 없다.
4.3(a)는 별개 앱 건이었다. 제목 8개 로케일 브랜드형으로 교체, ASC 반영 완료, RC 회신 발송. 상대적으로 깔끔하게 처리됐다. store/listing.md 한 파일이랑 CLAUDE.md 히스토리 기록 정도.
거절 대응이 네 건 겹치니까 각각 어디까지 왔는지 상태 관리가 중요해진다. 하나 답변하고 다른 거 코드 고치고 또 다른 거 문서 쓰는 식으로 컨텍스트 스위칭이 잦아서 오전이 통으로 흘러갔다.
판매장려금이 바운티가 된 날
오후로 넘어오면서 플랫폼 쪽으로 집중했다. 이번 주에 확정된 결정인데 - 사용자 노출 명칭에서 "판매장려금"을 "바운티"로 바꾸기로 했다. 세무나 증빙 서류에서 쓰는 법적 명칭은 판매장려금 그대로 유지하고, UI와 관리자 화면에서만 바운티로 표기하는 방향이다.
결정 자체는 단순해 보이지만 코드 범위가 생각보다 넓었다. settlement/web/ 쪽 컨트롤러들, batch/settlement/ 쪽 배치 클래스들, utl/ 공용 유틸리티, BalanceChangeType.java, ExcelConfigRegistry.java까지 손이 갔다. ExcelConfigRegistry는 엑셀 다운로드 컬럼 헤더 설정이 박혀 있어서 거기도 교체가 필요했다. BalanceChangeType은 잔액 변경 유형을 enum이나 상수로 관리하는 클래스일 텐데, 거기서 레이블 문자열을 들고 있으니 당연히 고쳐야 한다.
.claude/rules/settlement-domain.md도 같이 업데이트했다. 이 파일은 AI 어시스턴트가 이 도메인 작업할 때 참조하는 규칙서 같은 거다. 용어 혼선이 생기면 나중에 더 번거로우니까 명칭 결정이 나는 시점에 바로 문서화해두는 편이다. 이중 명칭 체계 - 내부적으로는 판매장려금, 사용자에게는 바운티 - 가 명확히 문서화되어 있어야 다음 번에 누가 작업할 때 왜 이렇게 돼 있는지 의아해하지 않는다.
system-revenue 쪽 AGENT_REBATE 라벨 등록도 이 작업의 연장선이다. 수익 원장 화면이랑 합계 요약 화면에서 해당 코드 값에 대한 화면 레이블이 등록되어 있어야 "판매장려금 지급"이 제대로 표기된다. 이게 빠지면 코드 값 그대로 뜨거나 공백으로 뜬다. 배포 후에 발견됐으면 꽤 눈에 띄었을 거라 배포 전에 잡아서 다행이다.
바운티 화면 안내문은 커밋이 두 세트씩 찍혔다 - style이랑 fix가 각각 두 번. 처음에 안내문 문장별 줄바꿈만 추가했다가, 렌더링 확인하니까 .alert 블록 안에 인라인 태그들이 flex 컨테이너에서 조각나고 있었다. 줄바꿈 추가한다고 될 문제가 아니라 인라인 요소들을 div로 감싸야 했다. 이런 건 브라우저에서 직접 봐야 보인다. 미리 보지 않고 커밋하면 꼭 이런 상황이 생기는데, 오늘도 어김없이 그랬다.
AUDIT_MISS, 그게 가장 심장 쫄렸던 순간
정산 쪽 audit 누락 버그가 오늘 가장 당황스러웠다.
AUDIT_MISS라는 표현이 커밋 제목에 있는데, 운영자가 정산을 확정할 때 감사 로그가 남지 않는 문제였다. 정산 확정은 돈이 움직이는 시점이라 audit 기록은 선택이 아니다. 그게 빠져 있었다는 게 발견된 거다.
원인은 CommonMap 태그 키 문제. eGovFramework 기반 프로젝트에서 CommonMap은 요청 파라미터를 담는 컨테이너 역할을 한다. SQL Mapper에서 파라미터를 넘길 때 키 이름이 정확히 맞아야 하는데, 키 하나가 틀리면 조건이 null로 평가되거나 insert가 의도와 다르게 동작한다. batch/settlement/ 두 클래스랑 쿼리 매퍼를 수정했고, CORRECTION_20260826_system_revenue_audit_backfill.sql로 누락된 이력을 소급 적재했다.
소급 SQL 작성은 항상 긴장된다. 운영 데이터에 직접 insert하는 거니까. 범위를 어디까지 잡을지, 중복은 없는지, 키 충돌은 없는지. 스크립트 이름에 날짜를 박아두는 이유가 있다 - 나중에 언제 어떤 이유로 직접 적재했는지 추적하기 위해서다.
발견 경위는 커밋만 봐서는 알 수가 없는데, 전자계약 게이트 발동 이력 작업을 하면서 audit 관련 코드를 훑다가 발견했을 수도 있고, 운영자가 확정 후 이력 탭을 확인하다 비어 있어서 보고했을 수도 있다. 어느 쪽이든 오늘 발견해서 오늘 fix와 backfill까지 마무리한 건 다행이다. 한 가지 아직 찜찜한 것은 누락이 언제부터였는지 정확히 확인이 됐는지 내일 한 번 더 확인해볼 필요가 있다.
kp-pay 브릿지와 가상계좌 원가 소급
kp-pay 통합인증 쪽 작업도 오늘 나갔다. 앱 내에서 kp-pay 인증 화면으로 직접 이동하던 방식을 브릿지 페이지를 거치는 방식으로 전환했다. kp-auth-bridge.jsp가 새로 추가된 게 그 브릿지 페이지다.
직접 앱 URL scheme로 이동하면 kp-pay 측 규격이 바뀔 때마다 클라이언트 코드를 수정해야 한다. 브릿지 페이지를 두면 규격 변경을 서버사이드에서 흡수할 수 있다. 커밋 제목에 "규격 변경 자동 추종"이라고 적은 게 그 의도다. charge.jsp가 두 경로(co/wallet/, co/common/wallet/)에 있고 이커머스 PG 플랫폼 쪽 payCharge.jsp까지 같이 수정됐으니, 충전 플로우 전체에 걸쳐 브릿지를 끼워넣은 거다. 그리고 컨트롤러도 수정됐으니 서버에서 브릿지 URL을 동적으로 구성하는 로직이 들어갔을 거다.
같은 맥락에서 kp-pay 가상계좌 POPUP 발행 시 인증 원가 추정 기록도 추가됐다. NOTI 시점에 110원+165원을 적재하는 로직이고, CORRECTION_20260826_vact_popup_auth_backfill.sql과 함께 커밋됐다. 원가 추정 데이터가 기존 건들에 대해 누락되어 있었던 거다. 가상계좌 POPUP 방식이 최근에 확정된 단가라서 소급이 필요했을 거고, jeju-vact-popup-transition-todo.md가 같이 업데이트됐으니 전환 작업 진행 상황을 기록해두는 문서가 있는 것 같다.
연동 가이드 ver2.3 규격서도 오늘 문서화했다. PDF 원본이랑 .claude/docs/jeju/ 밑에 마크다운으로 정리했다. PDF 파일명이 한글로 돼 있어서 git에서 인코딩된 이름으로 보이는 그거다. 규격서가 나왔을 때 바로 내부 문서로 정리해두지 않으면 나중에 어느 버전 기준으로 구현한 건지 추적이 안 된다. 이번엔 적용 현황까지 같이 기록했다.
전자계약 게이트 이력 탭
tradeControl.jsp에 발동 이력 탭을 추가하는 작업도 오늘 나갔다. 특정 조건을 충족하면 거래에 전자계약 서명을 강제하는 게이트인데, 그 게이트가 발동됐을 때 이력을 기록하고 관리자가 조회할 수 있게 하는 기능이다.
ap/trade/web/ 컨트롤러, 공용 유틸 클래스, SQL 쿼리 매퍼, JSP 뷰까지 풀 스택으로 손이 갔다. 이런 이력 조회 기능은 구조 자체는 단순한데 조회 조건이랑 컬럼 구성을 요구사항에 맞게 맞추는 데 시간이 간다. "발동 이력 탭 통합 조회"라고 한 걸 보면 기존에 다른 탭들이 있었고 거기에 이력 탭을 끼워넣은 거다.
나머지 자잘한 것들, 그래도 다 중요했다
관리자 테마 폰트 옵션에서 SCORE Dream을 S-CoreDream으로 교정했다. 폰트 이름이 실제 CDN 식별자와 달라서 폰트가 안 먹히는 버그였을 거다. form.jsp 한 파일이지만 폰트 로드가 실패하면 테마 설정이 의미 없어지니까 작아도 중요한 수정이다. 이런 건 한 번 틀린 채로 코드에 박히면 오래간다.
1:1문의 화면에서 전화 문의 항목 제거하고 이메일 문의만 남긴 건 운영 결정이 반영된 거다. 파일 첨부 안내 문구도 추가됐다. contact.jsp 한 파일 수정이지만 사용자가 문의할 때마다 보는 화면이라 결정이 나면 바로 반영해두는 게 낫다.
psy 쪽은 일일 자동 생성 테스트 3건이 올라왔다 - friend-disapprove-partner, hanok-midnight-horror, olive-young-type. 커버 이미지 webp와 content JSON이 같이 생성됐다. chore 커밋이고 자동화 파이프라인에서 나온 것이라 따로 손을 댄 건 아니다.
app card status sync는 pitlog랑 zipwise가 심사 중임을 index.html에 반영한 거다. 상태를 추적하는 인덱스 페이지 같은 거 관리하고 있는 것 같다.
오늘은 양이 많았다. App Store 거절 대응 네 건, 플랫폼 정산 명칭 전환, audit 핫픽스와 소급, kp-pay 브릿지 전환과 원가 기록 소급, 게이트 이력 탭, 그 외 자잘한 수정들.
가장 오래 머릿속에 남는 건 역시 audit 누락이다. 고쳤고 소급도 했지만 돈 흐름에 관련된 감사 로그가 얼마 동안 빠져 있었는지, 그 범위가 제대로 확인됐는지 내일 다시 확인해야 한다. App Store 쪽은 2.1(a)랑 2.1(b)가 한 번에 통과되면 좋겠지만 리뷰어가 또 다른 걸 잡아낼 수도 있다. 판매장려금에서 바운티로 전환은 오늘부로 코드베이스에 정착됐다. 이 이중 명칭이 앞으로 얼마나 유지될지는 모르겠지만 적어도 지금 결정된 방향이 어디에 있는지는 명확하게 남겨뒀다.
댓글 0
첫 댓글 달아줘.