이 글은 Today I Learn 시리즈의 222번째 기록입니다. (총 230개)

만나서 계약하는 게 아니라고 했더니 할 일 목록이 다시 줄을 섰다

그동안 문서와 결정에는 「매장을 직접 만나 계약하는 B2B」라는 전제가 깔려 있었다. 오늘 그게 틀렸다고 바로잡았다. 매장이 스토어에서 앱을 찾아 알아서 가입하고 결제해 쓰는 구조다.

한 문장을 고쳤을 뿐인데 그 위에 올라가 있던 판단들이 줄줄이 흔들렸다.

  • 30초 미리듣기가 「있으면 좋은 것」에서 필수가 됐다. 직접 만나면 들려주면 되지만, 혼자 들어온 사람에게는 체험이 곧 판매 창구다.
  • 가입한 매장끼리 데이터를 가르는 일이 「두 번째 고객 받기 전에」에서 출시 차단 요소로 올라갔다. 고객을 한 명씩 들이면 두 번째가 언제 올지 우리가 정하지만, 셀프 가입이면 정할 수 없다. 출시 전에 막았다.
  • 앱 안 결제 동선이 필요해졌다. 「만나서 계약하니 앱에서 결제할 일이 없다」가 앱 밖 판매를 정한 근거였기 때문이다.
  • 스토어 검색에서 이름이 겹치는 것이 신경 쓸 일이 됐다. 직접 파는 구조였을 땐 검색이 의미가 없었다.

기술 결정은 대개 사업 전제 위에 서 있는데, 그 전제는 문서에 한 줄로 적혀 있어서 틀려도 눈에 잘 안 띈다. 전제를 고칠 때는 그 문장을 근거로 삼은 결정이 어디어디 있는지 같이 찾아야 한다. 오늘은 문서와 예전 로그에서 「직접 만나」라는 표현을 전부 찾아 하나씩 다시 봤다.


결제는 두 곳인데 왜 세 갈래냐고 물었다

앞선 전제가 무너지니 결제를 어디서 받을지를 다시 열어야 했다. 앞선 TIL 9에서는 수수료와 세금계산서를 따져 「앱 밖에서 팔면 스토어 결제 규칙에 안 걸린다」로 정했다. 그런데 앱에 누를 수 있는 결제 버튼을 아예 두지 않아도 된다고 본 데에는 「만나서 계약한다」가 함께 깔려 있었다.

선택지를 펼쳐 놓고 「다 하자」고 정했다. 그런데 정리된 표에 「세 갈래」라고 적혀 있어서 되물었다. Creem(해외 세금·정산을 대신 처리하는 결제 대행사) 아니면 앱 안 결제, 두 개 아니냐고.

맞는 말이었다. 돈을 받는 곳은 둘이다.

경로돈을 받는 곳결제를 시작하는 곳수수료
웹 직접Creem웹 페이지0%
앱 → 웹 링크Creem앱의 링크구독 약 10% + 거래 보고
앱 안 결제Google Play10~15%

셋으로 센 이유는 위 두 줄이 같은 Creem 결제인데도 앱에서 출발하면 플레이가 수수료를 떼기 때문이었다. 결제처로 세면 둘, 비용으로 세면 셋이다. 앞선 TIL 9에서 「무엇으로 받느냐가 아니라 어디서 받느냐」라고 적었던 바로 그 차이인데, 표에는 무엇을 기준으로 센 숫자인지가 빠져 있어서 헷갈렸다.

같은 목록도 무엇을 기준으로 세느냐에 따라 개수가 달라진다. 세 갈래를 다 연 이유는 결제처가 셋이어서가 아니라, 매장마다 원하는 게 달라서다. 세금계산서가 필요한 곳은 웹으로, 편한 걸 원하는 곳은 앱 안 결제로 스스로 고르게 했다.


플레이 결제를 넣었더니 약관의 무환불과 부딪혔다

앱 안 결제를 붙이고 나서 게시된 약관과 개인정보처리방침을 다시 대조했다. 약관에는 「이미 결제한 금액은 환불하지 않는다」가 적혀 있다. 무환불은 연 선불 분쟁을 막으려고 일부러 넣은 조항이다.

그런데 플레이로 산 구독은 환불을 우리가 정하지 않는다. 구글이 자기 환불 정책에 따라 사용자 요청을 받아 처리한다. 우리 약관이 「환불 안 됨」이라고 해도 플레이 쪽에서는 환불이 일어날 수 있고, 두 문서가 서로 다른 말을 하게 된다.

그래서 무환불 문장을 웹(Creem) 결제에 한정하고, 플레이 결제는 플레이 환불 정책을 따른다고 적어 달라고 약관을 관리하는 쪽에 요청했다. 해지 방법도 경로마다 다르다(웹은 결제 확인 메일의 관리 링크, 플레이는 스토어의 구독 메뉴).

결제 경로를 하나 늘리면 그 경로를 운영하는 회사의 규칙이 우리 약관 안으로 들어온다. 수수료만 보고 경로를 늘리면 안 되고, 환불·해지·개인정보 조항이 그 경로에서도 참인지 같이 확인해야 한다. 같은 이유로 개인정보처리방침에도 새로 모으는 정보(플레이 구매 기록, 동시 재생을 판정하는 기기 번호, 약관 동의 시각)를 넣어야 했다. 스토어 심사는 이 문서와 앱이 실제로 모으는 정보를 대조한다.


소리는 나는데 재생 바가 비어 있었다

추천 재생목록을 틀었더니 하단 재생 바에 「재생 중이 아닙니다」가 떴다. 그렇게 알렸더니 소리가 나긴 하냐고 되물어 왔고, 소리는 난다고 답했다. 이 대답 하나로 원인이 확 좁혀졌다. 재생은 멀쩡하고, 「지금 무슨 곡인지」를 화면에 알리는 쪽만 고장 난 것이다.

원인은 재생 라이브러리(just_audio — Flutter 앱에서 소리를 내는 패키지)의 재생 명령이 이름과 달리 「재생이 시작되면」이 아니라 「곡이 끝나거나 멈추면」 끝나는 명령이었다는 데 있었다. 앱은 그 명령이 끝나기를 기다린 다음에야 지금 곡을 적고 있었고, 그래서 곡이 끝날 때까지 바가 비어 있었다.

더 중요한 건 같은 원인이 조용히 망가뜨리고 있던 게 더 있었다는 점이다.

  • 둘째 곡부터 다음 곡 버튼이 안 먹었다.
  • 첫 곡에서는 일시정지가 안 먹었다.
  • 정해진 시각에 재생목록을 바꾸는 정시 전환 뒤에, 다음 전환 예약이 그 곡이 끝날 때까지 걸리지 않았다.

마지막 것은 예전에 「정시 전환 된다」고 확인했던 기능이다. 그때는 전환되는 순간만 봤고, 전환 뒤에도 다음 전환이 제때 걸리는지는 보지 않았다.

여기서 두 가지를 가져간다. 하나는 증상을 알릴 때 「무엇이 되고 무엇이 안 되는지」를 갈라 말하면 원인이 반으로 줄어든다는 것이다. 「안 돼」와 「소리는 나는데 표시가 안 돼」는 전혀 다른 곳을 가리킨다. 다른 하나는 눈에 띄는 이상한 증상 하나 뒤에 같은 뿌리의 조용한 고장이 여러 개 숨어 있을 수 있다는 것이다. 기능을 확인할 때는 그 순간만 보지 말고, 그다음 동작이 이어서 제대로 도는지까지 본다.


요약

  • 사업 전제 한 줄이 바뀌면 그 위의 기술 결정들이 연달아 흔들린다. 전제를 고칠 때는 그 문장을 근거로 삼은 결정을 같이 찾는다.
  • 앞선 결론이 기대던 기둥이 무너지면 결론도 다시 연다. 「앱 밖에서만 판다」에는 수수료·증빙 말고 「만나서 계약한다」도 함께 깔려 있었다.
  • 같은 목록도 기준에 따라 개수가 다르다. 결제처로 세면 둘, 비용으로 세면 셋이다.
  • 결제 경로를 늘리면 그 경로를 운영하는 회사의 규칙이 약관 안으로 들어온다. 환불·해지·개인정보 조항이 새 경로에서도 참인지 확인한다.
  • 증상은 「무엇이 되고 무엇이 안 되는지」로 갈라 말하면 원인이 좁혀진다.
  • 이상한 증상 하나 뒤에 같은 뿌리의 조용한 고장이 여럿 있을 수 있다. 확인은 그 순간이 아니라 다음 동작까지 본다.

Series: Today I Learn

1 C++ 자료형(Data Type) 2 MD5 vs pHash 3 C++에서 함수의 선언과 정의 4 Tkinter padx, pady 5 메모리와 포인터 변수 6 Call by Value, Call by Reference, Call by Pointer 비교 7 const 8 Gemfile — Jekyll 프로젝트의 의존성 파일 9 kramdown-parser-gfm — Jekyll의 GFM 파서 10 파서(Parser) 11 AHU vs OHU 12 I might try it vs I'll try it 뉘앙스 차이 13 SESSION_EXPIRE_AT_BROWSER_CLOSE=True 14 configuration key 15 Git stash vs discard 16 subprocess.Popen으로 Windows 탐색기에 명령어를 전달 17 Post 잔디 분석하기 18 Google Sheets Sync 최적화 19 DSL (Domain Specific Language)과 GPL (General Purpose Language) 20 마크다운 표 그리는 방법 21 쿼리 파라미터(Query Parameter). 기존 QR코드 재활용 22 Django 보안 취약점 점검 및 수정 23 OOP Object-Oriented Programming 객체 지향 프로그래밍 24 Fernet 대칭 암호화 25 Jekyll 코드블록 안의 Liquid 태그 26 습관만들기 Pawbit TIL 1 27 insertOnConflictUpdate vs DoUpdate(target) 28 세션 필터 29 아코디언(Accordiaon) UI를 펼친상태로 만들기 30 input의 step 31 습관만들기 Pawbit TIL 2 32 습관만들기 Pawbit TIL 3 33 Word Cloud 34 Google Sheets를 데이터 버스로(with AppSheet) 35 Django 모델 텍스트 필드 자동 수집 패턴 36 localStorage로 섹션 토글 상태 유지 37 순차 ID 생성(`select_for_update()` + `max()` 조합) 38 역참조 검색과 distinct() 39 xlsx 다운로드와 로딩 오버레이 충돌 40 Android 파일 공유 MIME 타입 41 AssetManifest — Flutter 빌드 타임 asset 목록 런타임 조회 42 크레센도 Phaser + TypeScript TIL 1 43 크레센도 Phaser + TypeScript TIL 2 44 크레센도 Phaser + TypeScript TIL 3 45 크레센도 Phaser + TypeScript TIL 4 46 크레센도 Phaser + TypeScript TIL 5 47 여둘까 Office Layout TIL 1 48 습관만들기 Pawbit TIL 4 49 여둘까 Office Layout TIL 2 50 여둘까 Office Layout TIL 3 51 크레센도 Phaser + TypeScript TIL 6 52 UTF-8 BOM과 PowerShell 파일 쓰기 53 소리꽃 KeyBloom TIL 1 54 습관만들기 Pawbit TIL 5 55 여둘까 Office Layout TIL 4 56 소리꽃 KeyBloom TIL 2 57 소리꽃 KeyBloom TIL 3 58 메트로놈 Simple Metronome TIL 1 59 소리꽃 KeyBloom TIL 4 60 메트로놈 Simple Metronome TIL 2 61 소리꽃 KeyBloom TIL 5 62 습관만들기 Pawbit TIL 6 63 크레센도 Phaser + TypeScript TIL 7 64 메트로놈 Simple Metronome TIL 3 65 소설 타임라인 관리 도구 Lumior TIL 1 66 습관만들기 Pawbit TIL 7 67 정적 블로그 SEO 정비와 Pagefind 검색 도입 68 크레센도 Phaser + TypeScript TIL 8 69 메트로놈 Simple Metronome TIL 4 70 습관만들기 Pawbit TIL 8 71 안드로이드 AudioTrack 연속 재생, 실측 피커 정렬, 카메라 토치 플래시 72 메트로놈 Simple Metronome TIL 5 73 모래게임 Sandrop TIL 1 74 습관만들기 Pawbit TIL 9 75 메트로놈 Simple Metronome TIL 6 76 모래게임 Sandrop TIL 2 77 모래게임 Sandrop TIL 3 78 습관만들기 Pawbit TIL 11 79 습관만들기 Pawbit TIL 10 80 메트로놈 Simple Metronome TIL 7 81 모래게임 Sandrop TIL 4 82 모래게임 Sandrop TIL 5 83 습관만들기 Pawbit TIL 12 84 모래게임 Sandrop TIL 6 85 소리꽃 KeyBloom TIL 6 86 모래게임 Sandrop TIL 7 87 모래게임 Sandrop TIL 8 88 소리꽃 KeyBloom TIL 7 89 모래게임 Sandrop TIL 9 90 소리꽃 KeyBloom TIL 8 91 모래게임 Sandrop TIL 10 92 소리꽃 KeyBloom TIL 9 93 콘티온 ContiOn TIL 1 94 소리꽃 KeyBloom TIL 10 95 콘티온 ContiOn TIL 2 96 습관만들기 Pawbit TIL 13 97 콘티온 ContiOn TIL 3 98 룸톤 Roomtone TIL 1 99 모래게임 Sandrop TIL 11 100 소리꽃 KeyBloom TIL 11 101 여둘까 Office Layout TIL 5 102 모래게임 Sandrop TIL 12 103 모래게임 Sandrop TIL 13 104 콘티온 ContiOn TIL 4 105 모래게임 Sandrop TIL 14 106 소리꽃 KeyBloom TIL 12 107 습관만들기 Pawbit TIL 14 108 모래게임 Sandrop TIL 15 109 모래게임 Sandrop TIL 16 110 소리꽃 KeyBloom TIL 13 111 온실 GreenHouse TIL 1 112 콘티온 ContiOn TIL 5 113 모래게임 Sandrop TIL 17 114 소리꽃 KeyBloom TIL 14 115 온실 GreenHouse TIL 2 116 콘티온 ContiOn TIL 6 117 모래게임 Sandrop TIL 18 118 콘티온 ContiOn TIL 7 119 모래게임 Sandrop TIL 19 120 소리꽃 KeyBloom TIL 15 121 룸톤 Roomtone TIL 2 122 모래게임 Sandrop TIL 20 123 모래게임 Sandrop TIL 21 124 소리꽃 KeyBloom TIL 16 125 소리꽃 KeyBloom TIL 17 126 소리꽃 KeyBloom TIL 18 127 여둘까 Office Layout TIL 6 128 소리꽃 KeyBloom TIL 19 129 소리꽃 KeyBloom TIL 20 130 콘티온 ContiOn TIL 8 131 콘티온 ContiOn TIL 9 132 로그스톤 상표 셀프 출원 TIL 1 133 소리꽃 KeyBloom TIL 21 134 로그스톤 샵 LogStone Shop TIL 1 135 소리꽃 KeyBloom TIL 22 136 로그스톤 샵 LogStone Shop TIL 2 137 룸톤 Roomtone TIL 3 138 소리꽃 KeyBloom TIL 23 139 모래게임 Sandrop TIL 22 140 모래게임 Sandrop TIL 23 141 소리꽃 KeyBloom TIL 24 142 로그스톤 샵 LogStone Shop TIL 3 143 모래게임 Sandrop TIL 24 144 소리꽃 KeyBloom TIL 25 145 모래게임 Sandrop TIL 25 146 MiniMacro TIL 1 147 룸톤 Roomtone TIL 4 148 모래게임 Sandrop TIL 26 149 콘티온 ContiOn TIL 10 150 Astro가 무엇인지, 왜 옮기는지 151 룸톤 Roomtone TIL 5 152 룸톤 Roomtone TIL 6 153 모래게임 Sandrop TIL 27 154 소리꽃 KeyBloom TIL 26 155 MiniMacro TIL 2 156 로그스톤 샵 LogStone Shop TIL 4 157 메트로놈 Simple Metronome TIL 8 158 모래게임 Sandrop TIL 28 159 여둘까 Office Layout TIL 7 160 룸톤 Roomtone TIL 7 161 메트로놈 Simple Metronome TIL 9 162 블로그 Astro 이관 TIL 163 여둘까 Office Layout TIL 8 164 로그스톤 샵 LogStone Shop TIL 5 165 모래게임 Sandrop TIL 29 166 오픈데이 Openday TIL 1 167 로그스톤 샵 LogStone Shop TIL 6 168 모래게임 Sandrop TIL 30 169 오픈데이 Openday TIL 2 170 모래게임 Sandrop TIL 31 171 Unreal Engine MCP TIL 1 172 모래게임 Sandrop TIL 32 173 소리꽃 KeyBloom TIL 27 174 콘티온 ContiOn TIL 11 175 오픈데이 Openday TIL 3 176 로그스톤 샵 LogStone Shop TIL 7 177 모래게임 Sandrop TIL 33 178 소리꽃 KeyBloom TIL 28 179 오픈데이 Openday TIL 4 180 여둘까 Office Layout TIL 9 181 오픈데이 Openday TIL 5 182 ScorePlayer TIL 1 183 모래게임 Sandrop TIL 34 184 BuildingHub TIL 1 185 ScorePlayer TIL 2 186 여둘까 Office Layout TIL 10 187 BuildingHub TIL 2 188 ScorePlayer TIL 3 189 Unreal Engine MCP TIL 2 190 궁 미로 PalaceMaze TIL 1 191 메트로놈 Simple Metronome TIL 10 192 메트로놈 Simple Metronome TIL 11 193 오픈데이 Openday TIL 6 194 BuildingHub TIL 3 195 Obsidian vault에서 코드만 빼기 — directory junction 196 ScorePlayer TIL 4 197 여둘까 Office Layout TIL 11 198 오픈데이 Openday TIL 7 199 URL은 바뀔 수 있는 것에 묶지 않는다 200 돌이키기 힘든 변경은 작게 먼저 확인한다 201 도구는 기능이 아니라 내 일에 맞는지로 고른다 202 궁 미로 PalaceMaze TIL 2 203 궁 미로 PalaceMaze TIL 3 204 소리꽃 KeyBloom TIL 29 205 오픈데이 Openday TIL 8 206 룸톤 Roomtone TIL 8 207 BuildingHub TIL 4 208 ScorePlayer TIL 5 209 로그스톤 샵 LogStone Shop TIL 8 210 여둘까 Office Layout TIL 12 211 오픈데이 Openday TIL 9 212 BuildingHub TIL 5 213 MiniMacro TIL 3 214 룸톤 Roomtone TIL 10 215 룸톤 Roomtone TIL 9 216 소리꽃 KeyBloom TIL 30 217 오픈데이 Openday TIL 10 218 여둘까 Office Layout TIL 13 219 룸톤 Roomtone TIL 11 220 소리꽃 KeyBloom TIL 31 221 궁 미로 PalaceMaze TIL 4 222 룸톤 Roomtone TIL 12 읽는 중 223 로그스톤 샵 LogStone Shop TIL 9 224 룸톤 Roomtone TIL 13 225 로그스톤 샵 LogStone Shop TIL 10 226 룸톤 Roomtone TIL 14 227 소리꽃 KeyBloom TIL 32 228 룸톤 Roomtone TIL 15 229 룸톤 Roomtone TIL 16 230 룸톤 Roomtone TIL 17