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

한 번도 안 사 본 것을 팔고 있었다

웹에서 카드로 결제하는 길을 만들어 두고 한 번도 끝까지 밟아 보지 않았다. 코드는 다 있었고, 상품도 등록돼 있었고, 알림 받는 자리도 있었다. 그래서 「된다」고 여기고 있었다.

손님처럼 한 번 사 봤다. 결제는 한 번에 됐다. 걱정했던 자리 — 결제했다는 소식이 우리 서버에 닿아 구독이 켜지는 것 — 도 통과였다.

그런데 그 한 바퀴를 도는 동안 네 가지가 비어 있다는 걸 알았다. 어느 계정으로 결제하는지 화면에 안 보였고, 어디서 샀는지 안 보였고, 해지해도 확인 메일이 없었고, 해지할 자리가 없었다.

코드를 읽어서는 안 보이는 것들이다. 각각은 다 「있어야 할 화면 요소」인데, 기능 목록에는 그런 줄이 없다. 사는 사람의 자리에 서 봐야 빈 곳이 보인다.

  • 만들어 둔 길은 「되는 길」이 아니다. 한 번 끝까지 밟기 전까지는 밟아 보지 않은 길이다.
  • 밟아 볼 때 고장만 찾지 않는다. 고장이 아니라 없는 것이 더 많이 나온다.

결제 직전에 내가 누구인지 모르겠더라

로그인을 했는데 화면이 「로그인됨」이라고만 했다. 어느 계정으로 들어와 있는지가 없었다.

나는 시험용 계정과 내 계정을 왔다 갔다 하고 있었다. 그 상태로 결제 버튼을 누르려니 손이 멈췄다. 잘못 누르면 엉뚱한 계정에 구독이 붙는다.

매장도 똑같다. 지점이 여럿이면 계정도 여럿이고, 잘못 결제하면 돈은 나가는데 쓰던 자리는 안 열린다.

서버는 이미 그 정보를 보내고 있었다. 화면이 안 띄웠을 뿐이다.

  • 되돌리기 어려운 버튼 앞에서는 「지금 누구로, 무엇에」를 보여 준다. 결제·삭제·전송이 다 그렇다.
  • 서버가 이미 주는데 화면이 안 쓰는 경우가 있다. 없는 기능이 아니라 안 보여 준 기능이다.

샀는데 어디서 샀는지 모르면 끊을 수가 없다

결제를 마치고 보니 화면에 「구독 중」이라고만 떴다. 무엇으로 결제했는지는 없었다.

이게 왜 문제인지는 해지하려 할 때 드러난다. 우리는 파는 길이 둘이다. 웹에서 카드로 사는 길과 스토어에서 사는 길. 해지하는 곳도 그에 따라 다르다. 스토어에서 산 사람은 스토어에서 끊어야 하는데, 어디서 샀는지 모르면 우리 쪽을 뒤지다 「해지가 안 된다」고 결론 낸다.

파는 창구를 늘릴 때는 끝내는 창구도 같이 는다는 걸 생각 못 했다.

  • 들어오는 길이 여럿이면 나가는 길도 여럿이다. 들어온 길을 기록하고 보여 줘야 나갈 길을 안내할 수 있다.
  • 「구독 중」처럼 상태만 보여 주는 화면은 다음 행동을 못 정하게 한다. 상태 옆에 출처를 붙인다.

끊는 것도 혼자 할 수 있어야 한다

해지하려고 보니 할 데가 없었다. 결제사가 보낸 영수증 메일 안에 관리 링크가 하나 있었는데, 그게 유일한 길이었다.

메일을 지웠거나 못 찾는 손님은 우리에게 연락할 수밖에 없다. 그러면 내가 하나하나 처리해야 한다.

며칠 전에 같은 구조를 비밀번호에서 봤다. 잊어버리면 내게 연락해야 했고, 그래서 자동으로 바꿨다. 이번엔 해지다. 다음은 또 뭘지 모르겠다.

공통점이 보인다. 셀프로 들어오게 만들어 놓고 나가는 길만 사람 손에 걸어 두면, 손님이 늘수록 그 사람이 병목이 된다. 게다가 나가는 쪽은 더 급하다. 해지가 늦으면 돈이 더 나가니까.

  • 셀프로 가입하고 셀프로 결제하게 만들었으면 셀프로 끊을 수도 있어야 한다. 짝이 맞아야 한다.
  • 자동화할 때 「들어오는 길」부터 만들게 되는데, 나가는 길이 막혀 있으면 불만은 그쪽에서 터진다.

고장인 줄 알았는데 우리가 정한 규칙이었다

결제사 화면에서 「즉시 취소」를 눌렀다. 그런데 앱에서는 계속 곡이 나왔고, 우리 화면은 「해지 예정」이라고 했다.

버그로 보였다. 즉시라고 했는데 왜 계속 되는가.

규칙을 다시 보니 우리가 그렇게 정해 둔 것이었다. 해지해도 이미 낸 기간까지는 듣게 해 준다고 판매 페이지에 적어 뒀다. 결제사의 「즉시」는 자기네 과금을 멈춘다는 뜻이지 우리 서비스를 끊으라는 지시가 아니다.

고칠 게 없었다. 대신 해지 확인 메일에 「언제까지 들을 수 있다」를 넣기로 했다. 내가 헷갈렸으면 손님도 헷갈린다.

  • 남의 서비스가 쓰는 말과 우리 규칙이 같은 단어를 다르게 쓸 수 있다. 어긋나 보이면 버그를 찾기 전에 규칙을 먼저 확인한다.
  • 내가 쓰면서 헷갈린 자리는 손님도 헷갈린다. 고칠 게 없어도 설명은 필요하다.

결제사가 안 보내는 메일은 우리가 보내야 한다

해지하고 나니 아무 메일도 안 왔다. 결제사는 구독이 시작될 때만 메일을 보내고 끝날 때는 안 보낸다.

돈이 걸린 일인데 확인이 없다. 손님은 해지됐는지 확신할 수 없고, 다음 달에 또 빠져나갈까 봐 불안해한다. 그러면 문의가 온다.

다른 서비스는 어떻게 하나 봤더니 해지 메일을 보내고 있었다. 들어 있는 게 넷이었다 — 해지됐다는 확인, 더는 청구하지 않는다는 것, 언제까지 쓸 수 있는지, 다시 구독하는 길.

  • 결제를 남에게 맡겨도 손님과의 대화까지 맡겨지는 건 아니다. 그쪽이 안 보내는 자리는 우리가 채운다.
  • 돈이 움직이는 일은 끝났다는 신호를 반드시 준다. 조용히 끝나면 안 끝난 줄 안다.

물어봐 주는 사람이 없으면 모른 채 지나간다

전날 일이다. 다른 쪽에서 「방침에 적어 둔 보관기간 둘을 서버가 실제로 지우느냐」고 물어 왔다.

확인했더니 하나는 돌고 있었고 하나는 코드에 아예 없었다. 방침에는 「1년 뒤 지운다」고 적혀 있는데 그 일을 하는 코드가 없었다.

아직 1년이 안 지나서 어긋난 건 없었다. 묻지 않았으면 내년에야 알았을 것이고, 그때는 이미 약속을 어긴 상태다.

글로 적는 쪽과 그걸 실행하는 코드를 만드는 쪽이 다르면 이렇게 벌어진다. 적는 순간에는 적은 사람이 할 일이 끝나고, 만드는 쪽은 적힌 줄 모른다.

  • 밖에 약속으로 내보내는 문장에는 그걸 실행할 자리가 붙어 있는지 같이 본다. 적는 것과 도는 것은 다른 일이다.

요약

  • 만들어 둔 길은 한 번 끝까지 밟기 전까지 밟아 보지 않은 길이다. 밟아 보면 고장보다 없는 것이 더 많이 나온다.
  • 되돌리기 어려운 버튼 앞에서는 「지금 누구로, 무엇에」를 보여 준다.
  • 들어오는 길이 여럿이면 나가는 길도 여럿이다. 들어온 길을 보여 줘야 나갈 길을 안내할 수 있다.
  • 셀프로 들어오게 만들었으면 셀프로 나갈 수도 있어야 한다. 나가는 쪽이 더 급하다.
  • 어긋나 보이면 버그를 찾기 전에 우리 규칙을 먼저 본다. 같은 단어를 다르게 쓰고 있을 수 있다.
  • 돈이 움직이는 일은 끝났다는 신호를 반드시 준다. 남이 안 보내면 우리가 보낸다.
  • 밖에 내보내는 약속에는 그걸 실행할 자리가 붙어 있는지 같이 본다.

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 11 192 | 메트로놈 Simple Metronome TIL 10 193 | 오픈데이 Openday TIL 6 194 | BuildingHub TIL 3 195 | ScorePlayer TIL 4 196 | Obsidian vault에서 코드만 빼기 — directory junction 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 | 오픈데이 Openday TIL 11 220 | 룸톤 Roomtone TIL 11 221 | 소리꽃 KeyBloom TIL 31 222 | 궁 미로 PalaceMaze TIL 4 223 | 룸톤 Roomtone TIL 12 224 | 로그스톤 샵 LogStone Shop TIL 9 225 | 룸톤 Roomtone TIL 13 226 | 로그스톤 샵 LogStone Shop TIL 10 227 | 룸톤 Roomtone TIL 14 228 | 소리꽃 KeyBloom TIL 32 229 | 룸톤 Roomtone TIL 15 230 | 룸톤 Roomtone TIL 16 231 | 로그스톤 샵 LogStone Shop TIL 11 232 | 룸톤 Roomtone TIL 17 233 | 룸톤 Roomtone TIL 18 234 | 메트로놈 Simple Metronome TIL 12 235 | 콘티온 ContiOn TIL 12 236 | 소리꽃 KeyBloom TIL 33 237 | 온실 GreenHouse TIL 3 238 | 로그스톤 샵 LogStone Shop TIL 12 239 | 룸톤 Roomtone TIL 19 240 | 모래게임 Sandrop TIL 35 241 | 여러 세션을 INBOX 하나로 굴리기 242 | 궁 미로 PalaceMaze TIL 5 243 | CS2 Practice Host TIL 1 244 | 룸톤 Roomtone TIL 20 245 | 여둘까 Office Layout TIL 14 246 | 로그스톤 샵 LogStone Shop TIL 13 247 | 룸톤 Roomtone TIL 21 248 | 여둘까 Office Layout TIL 15 249 | 모래게임 Sandrop TIL 36 250 | CS2 Practice Host TIL 2 251 | CS2 Practice Host TIL 3 252 | 룸톤 Roomtone TIL 22 253 | CS2 Practice Host TIL 4 254 | 룸톤 Roomtone TIL 23 읽는 중 255 | 모래게임 Sandrop TIL 37