App Store 매출 파이프라인을 뿌리부터 고치고 드릴다운 페이지까지
목차
대시보드 매출 카드 숫자가 뭔가 낮다 싶었다. 감으로 느끼는 게 아니라, 실제 앱스토어 콘솔에서 직접 확인한 숫자와 비교했을 때 차이가 났다. "혹시 환율 계산 오류인가?" 싶었는데, 그게 아니었다. 수집 자체가 문제였다.
하루치만 쌓이고 있던 수집 로직
_lib/appstore-sales-check.py를 열어보니 매번 실행 시 당일 데이터만 가져와서 INSERT하는 구조였다. 중복 방지를 위한 조건도 불완전해서, 재실행하면 같은 날짜가 또 들어가거나 이전 날짜 데이터가 아예 누락되는 케이스가 생기고 있었다. 결과적으로 DB에는 실행 시점 기준 최신 하루치 레코드만 유효하게 남아 있는 상태였고, 그게 대시보드 카드에 그대로 반영되고 있었던 것이다.
수정 방향은 비교적 명확했다. 최근 14일치를 매번 롤링으로 재수집하고, 이미 있는 날짜면 업데이트(UPSERT), 없으면 삽입하는 방식으로 바꿨다. App Store Connect API는 일별 판매 리포트를 날짜 범위로 요청할 수 있고, 중복 적재를 막으려면 날짜 기준 unique constraint 위에서 ON CONFLICT DO UPDATE 처리가 가장 깔끔하다. 14일로 잡은 이유는, 앱스토어 정산 확정이 최대 며칠 후에 수정되는 케이스가 있어서 약간의 여유를 두기 위해서였다. 7일로 해도 실질적으로는 충분하지만, 어차피 비용이 드는 API가 아니니 넉넉하게 잡았다.
이 수정 하나로 DB에 이전에 누락됐던 날짜들이 다 채워졌다. 처음 반영하고 SELECT로 확인했을 때 레코드가 훅 늘어나는 걸 보니, 그 동안 얼마나 구멍이 많았는지 실감됐다.
표시 계층 수정: 하루 합산에서 30일 합산으로
수집 로직을 고쳤으니 이제 표시 로직도 맞춰야 했다. 기존 대시보드 매출 카드는 dashboard-stats.ts에서 "가장 최근 하루" 레코드의 금액을 그냥 가져오는 방식이었다. 이게 틀린 건 아닌데, 매출 지표를 하루 단위로 보는 건 일반적으로 의미가 없다. 오늘이 주말인지 평일인지, 프로모션이 있었는지 없었는지에 따라 일별 편차가 너무 크기 때문이다. 실제로 의사결정에 쓸 숫자라면 30일 누적 합산이 훨씬 맥락에 맞다.
dashboard-stats.ts에서 쿼리를 바꿨다. 최근 30일 범위를 걸고 SUM으로 묶는 방식. page.tsx의 카드 레이블도 "어제 매출"에서 "최근 30일 매출" 형태로 바꿨다. 숫자 자체는 당연히 크게 뛰었는데, 이게 증가한 게 아니라 원래 이 숫자가 맞는 것이다. 그 동안 카드가 과소 표시되고 있었던 거였다.
한 가지 신경 쓴 부분은, 30일 합산이라 해도 현재 월이 시작된 지 얼마 안 됐을 때(예를 들어 월초 3일차)와 월 중반 이후를 같은 레이블로 표시하면 혼란스러울 수 있다는 점이었다. 근데 어드민 대시보드 특성상 보는 사람이 맥락을 아는 내부 스태프라, 일단 단순하게 "최근 30일"로만 표시하기로 했다. 나중에 조건부로 "이번 달 누적"으로 전환하는 옵션을 추가할 수도 있겠지만, 지금 당장 필요한 건 아니었다.
App Store 판매 상세 페이지 신규 구현
수집도 고치고 카드도 고쳤는데, 대시보드 카드에서 클릭해서 들어갈 수 있는 상세 페이지가 없다는 게 계속 마음에 걸렸다. 대시보드는 요약이고, 실제로 "어떤 앱이 몇 건 팔렸고 금액은 얼마인가"를 날짜별로 보려면 별도 페이지가 필요하다.
src/app/admin/(protected)/appstore-sales/page.tsx를 새로 만들었다. 주요 구성 요소는 아래 세 가지였다.
| 섹션 | 내용 |
|---|---|
| 기간 필터 | 최근 7일 / 14일 / 30일 선택 탭 |
| 앱별 합산 카드 | 각 앱의 기간 내 총 매출 + 판매 건수 |
| 유료 판매 내역 테이블 | 날짜별, 앱별, 금액별 정렬 가능한 상세 리스트 |
유료 판매 내역 테이블을 만들 때 고민했던 건, App Store Connect 리포트에는 무료 다운로드와 유료 구매가 섞여서 들어온다는 점이었다. 매출 분석 맥락에서 무료 다운로드 건수는 별도 지표로 보는 게 맞고, 여기 이 페이지에서는 실제 금전 거래가 있는 유료 판매만 필터링해서 보여주는 게 목적에 맞다고 판단했다. 쿼리에서 units_sold > 0 AND developer_proceeds > 0 조건을 걸어서 걸러냈다.
사이드바에도 메뉴를 추가해야 했다. SidebarContent.tsx를 열어서 어드민 메뉴 섹션 안에 "앱스토어 판매" 항목을 넣었다. 기존 메뉴 구조를 보면서 아이콘이나 그룹 위치를 맞추는 게 코드 자체보다 더 신경 쓰이는 부분이었는데, 기존 패턴을 그대로 따라가면 돼서 어렵지는 않았다.
페이지를 다 만들고 나서 CLAUDE.md 문서도 두 번 업데이트했다. 수집 로직 변경 내용을 먼저 반영하고, 드릴다운 페이지 생성 이후 상세 페이지 동작 방식도 추가했다. 이렇게 작업 바로 뒤에 문서를 붙이는 습관은 나중에 "이게 왜 이렇게 동작하지?"라는 질문이 생겼을 때 시간을 아껴준다. 구현 직후가 가장 기억이 선명할 때이기도 하고.
틈새 작업: AutoBackupManager 포트폴리오 데모 추가
앱스토어 작업 중간에 짬을 내서 포트폴리오 사이트에 AutoBackupManager Windows 백업 프로그램 데모를 추가했다. demo/autobackup-manager/index.html을 만들고, index.html 메인 목록에도 올렸다. 썸네일 이미지(thumbs/autobackup-manager.jpg)도 함께 넣었다.
이 작업 자체는 크지 않다. GUI 작동 데모를 웹으로 보여주는 정적 페이지라서 복잡한 게 없다. 다만 포트폴리오 사이트에 항목이 추가될 때마다 메인 인덱스 업데이트를 빠뜨리는 실수를 종종 했어서, 이번엔 한 커밋에 세 파일을 묶어서 올렸다.
farm 앱 상태 변경 문서화
오후 후반부에 intonaire와 gwangclock 앱의 유료 플랜을 무료로 전환했고, intonaire는 13대 클린 재설치까지 진행했다. 이 내용을 CLAUDE.md farm 섹션에 날짜(2026-07-01) 기준으로 기록해뒀다. farm 앱들은 운영 상태가 자주 바뀌는 편이라, 변경 시점을 명시해두지 않으면 나중에 "이게 지금 유료야 무료야" 같은 혼선이 생긴다. 특히 재설치를 13대 전체에 걸쳐 진행하면 환경 설정이 초기화되는 부분도 있어서, "이 시점부터 새로 세팅됐다"는 기준점을 남겨두는 게 중요하다.
오늘 오후의 흐름을 돌아보면, 시작은 단순히 "매출 카드 숫자가 이상하다"는 의심이었는데, 파고들다 보니 수집 - 저장 - 표시 세 계층이 모두 손봐야 하는 상황이었다. 버그 하나를 고치면서 그 위에 없던 기능까지 얹게 된 셈이다. 상세 드릴다운 페이지는 어차피 언젠가 만들어야 할 것이었는데, 수집 로직을 이미 이해하고 있는 상태에서 바로 이어서 만들었더니 맥락 전환 없이 진행할 수 있어서 효율이 좋았다. 파이프라인 전체를 한 세션에서 끝냈다는 게 이날 오후의 가장 큰 수확이다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.