비즈니스 현실6 분 읽기

빈 문자열 하나로 세 앱이 결제 없이 라이브에 나가 있었습니다

빌드 시점에 키를 읽는 관용구의 폴백이 빈 문자열이었습니다. 값이 없으면 조용히 통과합니다. 컴파일도 테스트도 린트도 다 통과하고, 앱은 정상으로 보이고 결제만 죽습니다.

#revenuecat#android#monetization#verification#reality-check
두 패널 도식. 왼쪽은 빈 문자열 폴백 때문에 키가 없어도 컴파일·테스트·린트가 전부 통과하고 앱은 정상으로 보이지만 결제만 죽는 구조를 보여준다. 오른쪽은 판정을 로컬 설정이 아니라 배포 산출물에서 해야 한다는 점과, 원인이 결제 플랫폼에 안드로이드 앱 자체가 없었다는 점을 보여준다.
폴백이 빈 문자열이면, 설정 누락이 정상 빌드로 위장합니다.

안드로이드 리포들이 빌드 시점에 키를 읽는 관용구를 공유합니다.

fun secret(name: String, fallback: String = ""): String =
    (localProps[name] as String?) ?: System.getenv(name) ?: fallback

플릿 전수 검사를 돌렸습니다. 세 개의 라이브 앱 배포 산출물에 결제 공개키가 아예 없었습니다.

당신의 시크릿 읽기 함수는 값이 없을 때 멈춥니까, 아니면 빈 문자열을 돌려줍니까?

빈 문자열은 모든 게이트를 통과합니다

fallback = ""이므로 값이 없으면 빈 문자열로 조용히 통과합니다.

컴파일 통과. 테스트 통과. 린트 통과. 앱은 정상으로 보입니다. 결제만 죽습니다.

그리고 페이월 문구가 실패를 전부 하나로 뭉갰습니다.

요금제를 불러올 수 없습니다. 연결 상태를 확인해 주세요.

설정 오류가 네트워크 문제로 읽혔습니다. 결제가 조용히 죽는 형태는 돈은 받았는데 프로가 아니었던 적결제가 문제가 아니었던 적에도 있었습니다. 오래 오진할 수밖에 없는 문구입니다. 사용자도, 저도 같은 문장을 읽고 같은 결론을 냈습니다 — 인터넷이 느린가 보다.

빈 키는 증상이고 원인이 따로 있었습니다

키가 왜 없었는지를 팠습니다. 세 앱 모두 결제 플랫폼 프로젝트에 안드로이드 앱 자체가 만들어져 있지 않았습니다. iOS 앱만 있었습니다.

붙여넣을 키가 애초에 존재하지 않았던 것입니다.

iOS 앱을 안드로이드로 포팅할 때 빠지기 쉬운 단계입니다. 코드는 포팅하고, 스토어 리스팅도 만들고, 결제 플랫폼 쪽 앱 등록은 잊습니다. 세 앱 다 그 상태로 출시됐습니다.

당신이라면?

키가 릴리스에 실렸는지 확인해야 합니다. 어디를 보겠습니까?

  • 로컬 설정 파일 — 제가 처음 한 것. 그건 내 기계에 값이 있는지를 보는 것이고, 릴리스에 실려 나갔는지와는 별개다.
  • CI 환경변수 목록 — 설정돼 있다는 것과 그 빌드에 주입됐다는 것은 다르다.
  • 배포 산출물 — 릴리스 번들의 dex 안에 키 패턴이 실제로 들어 있는지 본다.

세 번째만 판정입니다. 나머지는 정황입니다.

여기에 함정이 하나 더 있습니다. 결제 SDK는 오류 안내문 안에 더미 키를 문자열로 들고 있습니다. 산출물에서 키 패턴을 grep할 때 이걸 실키로 세면 안 됩니다. 한 번 잘못 셌습니다.

자가진단 세 줄

  1. 시크릿 읽기의 폴백이 빈 문자열입니까? 그렇다면 설정 누락이 정상 빌드로 위장합니다. 필수 키는 폴백 없이 빌드를 실패시키십시오.
  2. 결제 실패 문구가 원인별로 갈립니까? "연결을 확인해 주세요" 하나로 뭉개면 설정 오류를 네트워크 문제로 오진합니다.
  3. 키 존재 판정을 로컬 설정으로 하고 있습니까? 판정은 산출물에서 해야 합니다. 그리고 SDK가 들고 있는 더미 키를 실키로 세지 마십시오.

솔직한 부분

세 앱이 결제가 불가능한 상태로 라이브에 있었습니다. 얼마나 오래였는지, 그 기간에 결제를 시도한 사용자가 있었는지는 확인할 방법이 없습니다. 시도가 SDK 초기화 전에 실패하므로 기록이 남지 않습니다.

즉 손실을 셀 수가 없습니다. "매출 0"과 "결제 시도 0"과 "결제 불가"가 원장에서 같은 칸에 있었고, 저는 그동안 첫 번째로 읽고 있었습니다.

관련 글