결제 실연동, 위젯 다섯 번, 세 앱이 동시에 끓은 날
목차
오늘 무슨 날이었냐고 물으면 솔직히 "많이 했다"는 말밖에 안 나온다. 커밋 수를 세어보니 서른 개가 넘는다. 그것도 프로젝트가 세 개에 걸쳐서. kp-pay 가상계좌 실연동, DryWave iOS 위젯 vc5, PainTap 출시 직전 마무리, Bloomly 넛지 버그. 각자 다른 맥락, 다른 스택, 다른 PG사 문서. 오전부터 밤 사이를 이걸 오가며 보냈다.
제일 먼저 붙잡고 제일 오래 놓지 못한 건 kp-pay였다.
가상계좌 실연동, 이게 왜 이리 복잡한가
애초에 "PG 붙이는 건 그냥 API 호출 아니냐" 싶지만, 막상 실연동 들어가면 항상 그게 아니다. 오늘도 정확히 그랬다.
kp-pay 가상계좌 실연동 1차는 테스트 키 연결, SMS 본인인증 실서버 대응, CI 미제공 폴백이 한 덩어리로 묶여 있었다. 특히 CI 폴백이 거슬렸다. 실서버가 가상계좌 채번 응답에서 CI 값을 안 내려주는 케이스가 있어서 그걸 방어해야 했는데, 테스트 환경에서 재현이 쉽지 않으니 코드만 보고 판단해야 했다. 이런 상황이 제일 찝찝하다. "아마 이렇겠지"로 짜는 방어 코드.
그다음 이어진 게 충전 KYC 게이트 막다른 길 해소였다. 사용자가 휴대폰 본인인증을 안 마친 채로 충전 화면까지 들어왔을 때, 그냥 막아버리면 돌아갈 곳이 없는 구조였다. /member/kyc로 안내하는 흐름을 추가했다. 작은 것 같아 보여도 이게 없으면 유저가 앱 안에서 그냥 갇혀버린다. UI 흐름에서 "막다른 길"은 제일 먼저 찾아야 하는데, 실연동 과정에서 직접 밟아봐야 발견되는 게 문제다. 설계 단계에서 보이는 것과 실제 손으로 눌러보며 마주치는 것이 다르다.
가상계좌 발급 흐름 전체 완성 커밋에는 휴대폰인증 스텝, 당일 인증생략 처리, 표기 개선이 한꺼번에 묶였다. 당일 인증생략이 쉬워 보이는데 실제론 "오늘 이미 인증했다"는 판단 기준을 어디 두느냐가 문제다. 그 기준이 totalAuthNo와 가상계좌 세트를 바인딩하는 구조로 풀렸다. 별도 픽스 커밋으로 분리된 게 이 당일 인증표-가상계좌 세트 바인딩인데, 처음에 단순히 세션 기반으로 처리하려다 경계 케이스가 생겨서 명시적으로 묶는 방향으로 틀었을 것이다. 커밋이 두 개로 쪼개진 게 그 흔적이다.
안정화 단계에서 기등록 회원 처리, 소액 테스트, 예금주 표기가 거의 동시에 터졌다. 가상계좌를 발급할 때 이미 등록된 회원이 다시 발급 시도하면 어떻게 되는지, kp-pay가 10,000원 미만 입금을 아예 안 받는다는 사실, 예금주 표기가 실서버에서 예상한 형태로 안 나온다는 것. 최소 충전금액 10,000원 강제는 별도 픽스 커밋으로 뽑혔다. kp-pay 실서버 확인 후에 박은 것이라 규칙 문서에 따로 기록까지 남겼다. 이런 건 문서에 박아야 한다. 나중에 "왜 1만원이냐"는 질문이 왔을 때 커밋 로그가 답해준다.
충전 당일 무인증 재발급 완성, 인증 UI 재노출 제거. 이미 당일 인증한 유저에게 또 인증창을 띄우는 건 정말 나쁜 UX인데, 이걸 막으려면 상태 추적이 필요하고 그 상태를 어디서 들고 다닐지 판단이 필요하다. JSP 쪽 charge.jsp도 함께 건드렸다는 걸 보면 서버 응답 플래그에 따라 뷰를 분기하는 방식으로 처리했을 것이다. 사실 이걸 깔끔하게 하려면 서버 상태와 클라이언트 렌더 조건이 정확히 맞아야 한다. 어디 하나 어긋나면 인증창이 다시 뜨거나 아예 안 뜨거나.
가상계좌 홀딩 주석과 화면 문구를 2시간 10분에서 익일 00:30 일괄 정정으로 고친 건 일별 정산 전환 반영이다. 건드린 파일이 payment/web, utl, utl/payment, sqlmap까지 퍼져 있다. 결제 쪽은 이런 디테일이 도처에 박혀 있어서 하나 바꾸면 주석, 화면 문구, SQL 매퍼, 유틸리티 클래스까지 동시에 들여다봐야 한다. 놓치면 화면에서 2시간 10분이라고 안내하는데 실제론 00:30에 처리되는 불일치가 생긴다.
정산 규칙도 오늘 두 번 갱신했다. 인증원가 집계 반영 완료, 파트너 자기출금 미부과 확정. 실연동하면서 실측한 것들을 도메인 규칙으로 못 박는 작업이다. "확정"이라는 단어를 커밋 메시지에 쓸 수 있을 때가 오히려 홀가분하다. 불분명하게 놔두는 게 제일 위험하다.
jeju 문서도 오늘 대거 추가됐다. kp-pay 연동지침 최신화, 휴대폰인증 플랜, 보안감사(0708) 문서, PDF 3종이 한꺼번에 들어갔다. 지급대행 v1.2, 가상계좌출금 v1.7, 인증O ver2.0. PDF까지 커밋으로 박는 건 나중에 "그때 어떤 버전 스펙으로 붙였는지" 추적하기 위해서다. PG 스펙은 시간이 지나면 조용히 바뀌는데, 그 시점의 문서를 함께 묶어두지 않으면 1년 뒤 디버깅할 때 고생한다.
vc5까지 간 DryWave
kp-pay를 하면서 동시에 DryWave도 오늘 큰 마일스톤을 찍었다.
DryWave는 물빼기 앱이다. 스피커에서 특정 주파수 톤을 내보내 물을 뺀다는 콘셉트. zoothe를 포크해서 165Hz 물빼기 톤, 먼지 스윕, 수동 톤 엔진을 얹은 게 시작이었다. 오늘 커밋 목록만 봐도 이 앱이 하루 동안 얼마나 많이 뒤집혔는지 보인다.
버전 카운터가 vc2, vc3, vc4, vc5까지 올라갔다.
vc2에서 Android 1x1 홈 위젯이 들어갔고, iOS 퀵액션도 함께였다. AdMob 실 ID 반영, 리스팅 위젯 홍보 추가. 이 시점에 이미 스토어 출시를 목표로 속도를 내고 있었다는 게 보인다.
vc3에서는 클리닝 진행 중 앱 오픈 광고 스킵을 픽스했다. 위젯에서 진입해 물빼기를 시작했는데 광고가 그 위를 덮어버리는 UX 버그. 실제로 당하면 꽤 짜증나는 경험이다. 광고가 돈이 되지만 이 타이밍에 막아야 한다는 판단은 명확했다. 앱 오픈 인터스티셜을 언제 스킵할지는 항상 논쟁이 되는 지점인데, 핵심 기능 진입 직후라면 답이 정해져 있다.
vc4에서 iOS WidgetKit 1x1 systemSmall 위젯이 들어갔다. drywave://eject 딥링크 연결. iOS 위젯은 Android랑 구조가 다르다. Android는 위젯 클릭 시 인텐트로 직접 액션을 취할 수 있는데, iOS WidgetKit은 기본적으로 앱을 열어야 한다. 딥링크로 열면 그나마 직접 액션 경로로 떨어지는 게 장점이긴 한데, 앱 열리는 딜레이가 있다.
vc5가 오늘의 하이라이트였다. 앱을 열지 않고 위젯 탭만으로 물빼기 실행. Android에서는 EjectToneService를 백그라운드로 올려서 처리하고, iOS에서는 EjectWidget.swift가 직접 대응하는 방식이다. Android 파일 목록에 EjectToneService.kt, EjectWidgetProvider.kt, ic_widget_stop.xml까지 들어간 걸 보면, 위젯에 정지 버튼도 달렸다는 이야기다. 인플레이스 재생이라는 건 그냥 "위젯에서 바로 실행"이 아니라 위젯 안에서 재생/정지 상태 전환까지 된다는 의미고, 이게 생각보다 손이 많이 간다. 백그라운드 서비스 라이프사이클을 위젯 상태와 싱크해야 하니까.
iOS 심사 제출까지 오늘 끝냈다. 버전과 IAP 모두 WAITING_FOR_REVIEW 상태. 처음 IAP 제출할 때 함정이 있다는 기록도 남겼는데, 콘솔 첨부가 유일한 증거라는 코멘트가 있는 걸 보면 스크린샷이나 동영상 없이 콘솔 답안지로 승부를 봐야 하는 상황이었던 것 같다. iOS 첫 IAP 제출은 App Store Connect에서 요구하는 게 묘하게 많아서 한 번씩은 막힌다. 경험자도 매번 새로 확인해야 한다.
라이트모드 홈 검정배경 픽스도 있었다. CalmBackground 래퍼로 감쌌다는 게 커밋에 있는데, 다크모드용 배경을 라이트모드에서 그대로 쓰다가 검정으로 나오는 건 Flutter에서 흔한 실수다. 컬러 테마를 MediaQuery로 분기하지 않고 하드코딩하면 이렇게 된다.
코드리뷰 반영 커밋도 오늘 있었다. tone temp WAV 축적 제거, 완료 문구 잔상 리셋, 루프 유효주파수 무이음새, 슬라이더 clamp. tone_service.dart와 home_screen.dart 두 파일이 대상이었다. WAV 파일이 temp 디렉토리에 쌓이는 건 꽤 심각한 문제다. 기기 저장공간을 조용히 갉아먹으니까. 루프 전환 시 클릭 노이즈는 오디오 앱에서 안 잡으면 리뷰에서 바로 터진다. "소리가 이상해요"는 평점 1점짜리 리뷰 단골 문구다.
appstore-sales cron도 건드렸다. 12:30, 22:30 두 번 실행으로 조정. 애플 리포트가 KST 저녁에 늦게 발행되는 갭을 줄이기 위한 것이다. 2분짜리 작업이지만 필요한 건 필요하다.
PainTap, 오늘 하루 안에 MVP에서 출시 직전까지
PainTap 커밋이 오늘 꽤 많았다. 원탭 통증/증상 기록 앱인데, MVP부터 출시 준비까지 커밋 시퀀스가 하루 안에 다 들어가 있다.
홈 위젯, AdMob, 8개국어 i18n, 위젯 확인 팝업 커스텀 디자인, 공유 기능, 즐겨찾기, 디자인 패스, 프리미엄 IAP, 서명 빌드. 이 모든 게 오늘 커밋에 있다.
위젯 확인 팝업 커스텀 디자인이 눈에 띄었다. WidgetConfirmActivity를 별도 Activity로 빼서 confirm_bg.xml, confirm_btn_cancel.xml, confirm_btn_ok.xml을 다 만들었다. 통증 기록 특성상 실수로 눌리면 의미 없는 데이터가 쌓이니까 확인 팝업이 맞는 판단이다. 시스템 다이얼로그 쓰면 5분인 걸 커스텀으로 다 만드는 데 시간이 배로 드는데, 앱 전반 디자인과 통일하려면 어쩔 수 없다.
8개국어 i18n이 arb 파일 기반으로 들어갔다. Android AndroidManifest.xml, PainTapWidgetProvider.kt, WidgetConfirmActivity 같은 파일들이 한 커밋에 묶인 걸 보면, 위젯 자체도 다국어 지원이 들어갔다는 것이다. 위젯 문자열 다국어는 자주 빠뜨린다.
프리미엄 IAP는 광고 제거 기능이다. DryWave랑 같은 구조. 디자인 전면 교체가 IAP와 같은 커밋에 묶인 게 흥미롭다. IAP 탑재하면서 앱 분위기 전반을 화이트 베이스로 바꿨다는 의미다. 이게 실수인지 의도인지는 모르겠는데, 프리미엄 플로우 넣으면서 앱 전체 톤을 함께 정리하는 건 나쁘지 않은 순서다.
서명 빌드. build.gradle.kts에 서명 설정 들어가고 iOS xcodeproj도 건드렸다. 양 플랫폼 동시 출시 목표다. 스토어 리스팅 8개국어도 오늘 완성됐다. listing.md에 다 들어갔을 것이다.
공유 기능은 최근 7일 요약 텍스트를 뽑아서 공유하는 건데, history_tab.dart와 settings_tab.dart 두 곳이 바뀐 걸 보면 탭 구조도 이 시점에 재정비됐다.
Bloomly, 조용하지 않았던 작은 버그
Bloomly는 1.0.1이었다. App Group 복원으로 위젯 데이터 연동을 살렸고, 저녁 미완료 넛지 알림을 8개국어로 추가했다. 그리고 첫 습관 생성 시 알림 권한 요청이 엉뚱하게 넛지를 발화시키는 버그. app_state.dart에서 조건 분기가 잘못 걸렸을 것이다. 넛지 시각도 20시로 원복했다.
App Group 복원이 커밋 메시지에 들어간 건, 어느 시점에 entitlement가 날아갔거나 설정이 깨진 것이다. iOS에서 App Group 없으면 위젯이 앱 데이터를 못 읽는다. 이걸 실기기 테스트 전에 못 잡으면 위젯이 텅 빈 상태로 리뷰어한테 간다. 이번엔 빨리 잡았다.
문서화와 운영 잡무
settlement-domain.md를 오늘 두 번 갱신했다. 실연동하면서 실측한 규칙을 즉시 문서화하는 습관은 중요하다. 나중에 "이게 왜 이렇게 됐지?" 할 때 커밋 날짜까지 나오니까.
ssul은 발행량을 하루 80건에서 40건으로 절반으로 줄였다. 슬롯당 10건에서 5건으로. 이유는 커밋 메시지에 없지만, 어떤 이유로든 조정이 필요한 상황이 생겼다는 것이다.
psy auto-generated tests는 일별 루틴. family-restaurant-type, haunted-vlog-type, rainy-day-type 세 개. 커버 이미지까지 같이 박혔다.
오늘 하루를 돌아보면
커밋 30개 넘게 박고 나서 깔끔하게 정리된다는 느낌은 없다.
kp-pay 가상계좌는 실연동 루프를 한 바퀴 다 돈 것 같고, DryWave는 vc5까지 올리고 iOS 심사까지 넣었다. PainTap은 오늘 출시 준비를 사실상 완료한 것처럼 보인다. Bloomly는 1.0.1로 소소하게 정리.
찝찝한 건 PainTap과 DryWave를 이렇게 빠르게 밀어붙이면서 놓친 게 없냐는 생각이다. 오디오 관련 메모리 누수가 더 있는지, IAP 영수증 검증 깊이가 충분한지, 위젯이 백그라운드 서비스를 올렸다 내릴 때 배터리에 어떤 영향을 주는지. 이건 실기기에서 며칠 돌려봐야 아는 것들이다.
내일은 kp-pay 가상계좌 실거래 테스트를 더 돌려볼 것 같다. 오늘 10,000원 최솟값을 박았고 소액 테스트도 했다고 하지만, 실제 사용자 시나리오를 따라가면 또 새로운 케이스가 나온다. 익일 00:30 일괄 정산 전환이 실제로 돌아가는 걸 확인해야 한다. 그것도 내일 새벽 이후로.
오늘 한 거 다 맞게 됐는지, 제출한 것들이 통과할지는 내일 봐야 안다.
댓글 0
첫 댓글 달아줘.