룸톤 Roomtone TIL 15
화면을 끄면 멈추는 건 메트로놈 때 배운 문제였다
화면을 끄고 웹 서핑을 하면 서너 곡 뒤에 음악이 멈췄다. 다른 앱이 소리를 가져간 건가 싶어 게임 소리를 끄고, 전부 껐다가 다시 켜 봤는데도 세 곡쯤 뒤에 똑같이 멈췄다. 다른 앱 탓은 아니었다.
원인은 포그라운드 서비스였다. 안드로이드는 화면에 없는 앱이 계속 도는 것을 막는데, 계속 돌려면 「지금 뭔가 돌고 있다」는 알림을 띄우고 시스템에 신고해야 한다. 그 신고가 포그라운드 서비스다. 알림을 띄우는 의무를 지는 대신 시스템이 앱을 함부로 끄지 않는다.
이건 앞선 메트로놈 TIL 7에서 이미 배운 내용이고, 룸톤에서도 TIL 1에서 화면을 꺼도 재생되게 하려고 같은 장치를 달았다. 그때는 홈 버튼을 누르자마자 메트로놈이 멈춰서 바로 알아봤다. 이번엔 까먹고 있다가 원인을 듣고 다시 떠올렸다. 헷갈린 이유는 증상이 달라서다. 첫 곡은 멀쩡히 돌다가 몇 곡 뒤에야 멈췄다.
- 룸톤은 신고를 제대로 해 두었지만, 곡과 곡 사이의 아주 짧은 멈춤을 재생 라이브러리(just_audio — Flutter 앱에서 소리를 내는 패키지)가 「일시정지」로 보고 신고를 거둬들였다.
- 다음 곡이 시작돼도 신고를 다시 하지 않았고, 신고가 없는 앱은 몇 분 안에 시스템이 재웠다.
- 일시정지해도 신고를 유지하게 해서 고쳤다. 대가는 음악을 멈춰 둔 동안에도 알림이 남는다는 것이다. 매장에서 하루 종일 트는 앱이라 받아들일 만했다.
한 번 배운 원리도 다른 모습으로 나타나면 못 알아본다. 「백그라운드에서 멈춘다」는 증상이 보이면 언제 멈추든 포그라운드 서비스부터 의심한다.
정기결제를 처음 붙여 보니 모든 단계가 처음이었다
룸톤은 정기결제가 있어야 굴러가는 첫 앱이다. 지금까지 만든 앱은 무료이거나 한 번 사면 끝이었다. 그래서 상품 등록부터 결제가 계정에 붙는 것까지 모든 단계에서 질문이 생겼다.
Creem에는 넘겨줄 물건이 없었다
Creem(해외 세금·정산을 대신 처리하는 결제 대행사)에 상품을 만들면서 두 가지를 물었다. 이게 SaaS인지, 그리고 라이선스 키를 파는 건지.
분류는 SaaS가 맞았다. 서버에서 돌아가는 서비스를 매달 쓰게 해 주는 것이기 때문이다. 하지만 라이선스 키는 아니었다. 라이선스 키는 사용자가 앱에 직접 입력해 잠금을 푸는 코드다. 설치형 프로그램을 팔 때 주로 쓴다. 룸톤은 로그인한 계정 자체가 이용권이라 따로 넘겨줄 키도 파일도 없다. 그래서 전달물 칸은 비우고, 결제 뒤에 보이는 안내에 「구독 관리·해지는 결제 확인 메일의 링크에서」만 적었다.
라이선스 키 없이 결제가 룸톤 계정에 붙는 길
그러자 궁금해졌다. 키도 없는데 Creem에서 낸 돈이 어느 룸톤 계정의 것인지 어떻게 아나. Creem에도 가입해야 해서 같은 계정을 써야 한다면, 차라리 구글 결제만 두면 되는 거 아닌가.
답은 결제를 누가 시작하느냐에 있었다.
- 매장이 판매 페이지에서 룸톤 계정으로 로그인한다.
- 「구독하기」를 누르면 룸톤 서버가 Creem에 결제 화면을 만들어 달라고 요청한다. 이때 「이 결제는 계정 몇 번의 것」이라는 메모(메타데이터)를 붙여 보낸다.
- 매장이 Creem 화면에서 결제한다. Creem 계정은 따로 만들지 않아도 된다.
- 결제가 끝나면 Creem이 룸톤 서버로 알림(웹훅, 일이 생기면 상대 서버가 우리 서버 주소로 보내는 알림)을 보낸다. 알림에는 2번에서 붙인 메모가 그대로 들어 있다.
- 서버가 메모의 계정 번호를 보고 그 계정의 구독을 켠다.
라이선스 키는 사용자가 결제와 계정을 이어 준다. 이 방식에서는 결제를 시작한 쪽이 메모로 둘을 이어 준다. 대신 Creem 상품 링크로 바로 가서 사면 메모가 없어서 누구 결제인지 알 수 없다. 그래서 결제 입구는 반드시 로그인 뒤의 판매 페이지여야 한다.
해 본 적이 없어서 설명만으로는 확 와닿지 않았다. 아직 실제로 결제를 한 바퀴 돌려 보지 않았으니, 그걸 해 봐야 확실히 이해할 것 같다.
가격 하나를 정하니 나라마다 현지 가격이 붙었다
플레이 콘솔에서 구독 상품의 가격을 하나 넣자, 콘솔이 판매하는 나라 전부에 그 나라 통화로 된 가격을 채워 넣었다. 환율만 곱한 숫자가 아니라 각 나라에서 자연스러운 끝자리로 맞춘 가격이었다. 신기했다.
이 기능을 보니 플레이 구독이 달리 보였다. Creem은 달러로만 받아서 월 $8로 정했고, 매장은 결제할 때 자기 카드사 환율로 달러를 낸다. 플레이에서는 한국 매장은 원으로, 일본 매장은 엔으로 익숙한 숫자를 본다. 나라별 세금도 플레이가 알아서 붙인다.
- 나라마다 가격을 따로 정할 수도 있다. 자동으로 채워진 가격은 시작점일 뿐이고, 물가가 다른 시장에 맞춰 손볼 수 있다.
- 환율이 크게 바뀌면 자동 가격과 웹의 달러 가격이 벌어질 수 있다. 두 경로를 같이 운영하면 가끔 맞춰 봐야 한다.
결제 경로를 고를 때 수수료만 봤는데, 가격을 현지화하고 세금을 붙여 주는 일도 경로가 대신해 주는 일이다. 이걸 보고 웹 결제를 꼭 따로 둬야 하는지 다시 따져 보기로 했다.
플레이 서버에 물어볼 자격을 따로 줘야 했다
플레이 구독은 앱 안에서 결제가 끝나도 그걸로 끝이 아니다. 앱이 영수증을 서버에 보내면, 룸톤 서버가 구글에 「이 구매 진짜 맞아?」라고 다시 묻고 나서 구독을 켠다. 앱이 보낸 영수증만 믿으면 조작된 영수증도 통과하기 때문이다.
그러려면 룸톤 서버가 구글에 물어볼 자격이 필요하다. 사람이 아닌 프로그램용 계정인 서비스 계정을 만들고, 그 열쇠 파일(JSON 키)을 서버 비밀값으로 넣는다. 처음 「서비스 계정을 만들고 JSON 키를 내려받으라」는 안내를 봤을 때는 무슨 말인지 몰랐다. 서버에 줄 신분증이라고 이해하니 순서가 보였다.
그런데 테스트 앱에서 구독하자 「구매를 확인하지 못했다」가 떴다. 앱에 보이는 건 502라는 숫자뿐이었고, 실패 이유를 로그에 남기게 한 뒤 다시 해 보니 구글이 「권한 없음」으로 거절하고 있었다. 서비스 계정을 플레이 콘솔에 초대만 했고, 룸톤 앱의 재무 데이터 보기·주문과 구독 관리 권한은 주지 않았던 것이다. 권한을 준 뒤에도 한동안 반영되지 않다가, 구독 상품을 한 번 고쳐 저장하자 통과했다.
- 초대는 들어올 자격이고, 앱별 권한은 무엇을 할 수 있는지다. 둘은 따로 줘야 한다.
- 실패가 숫자 하나로만 보이면 원인을 추측하게 된다. 실패 이유를 남겨 두니 한 번에 짚였다.
확인하지 않은 구매는 환불된다
확인이 실패하던 중에 플레이에서 「개발자가 첫 구매를 확인하기 전에는 요금제 기간을 연장할 수 없다」는 문구를 봤다. 무슨 소리인지 물었다.
플레이에서는 결제가 됐다고 끝이 아니다. 판매자 서버가 「이 구매를 받았다」고 승인(acknowledge)해야 한다. 정해진 기간 안에 승인이 없으면 플레이가 구매를 취소하고 돈을 돌려준다. 앱이 멈췄거나 서버가 죽어 사용자가 돈만 내고 아무것도 못 받는 일을 막기 위해서다.
그래서 구매 확인은 있으면 좋은 기능이 아니라 매출이 남기 위한 조건이다. 서버의 확인 과정이 고장 나면 화면에서 에러가 나는 데서 끝나지 않고, 받은 돈이 되돌아간다.
테스트 구독은 공짜고 5분이면 한 달이 지난다
라이선스 테스터로 등록한 계정은 실제 결제 화면을 거치지만 돈이 나가지 않는다. 신기했다. 실제 카드로 결제하고 환불하는 번거로움 없이 구독 흐름을 끝까지 돌려 볼 수 있다.
게다가 테스트 구독은 시간이 빨리 간다. 월 구독이 5분마다 갱신되고, 해지하면 5분 뒤에 끝난다. 그래서 「구독이 끝나면 30초 미리듣기로 돌아가는가」를 한 달 기다리지 않고 그날 밤 바로 확인했다. 제대로 돌아가긴 했지만 바뀌는 사이 로딩이 한참 걸리는 것도 이때 보였다.
구독 앱에서 버그가 숨기 쉬운 곳은 결제 순간보다 한 달 뒤, 갱신과 만료 때다. 시간을 줄여 주는 테스트 환경이 있으니, 출시 전에 만료까지 한 바퀴 돌려 봐야 한다.
앱에서 웹 결제로 가는 길에도 정식 창구가 있었다
앱 설정에서 웹 판매 페이지로 가는 길을 만들고 싶었다. 그런데 판매 페이지에 구독 버튼이 있어서, 앱에서 거기로 보내면 플레이 밖 결제로 이끄는 것이 된다. 그럼 요금을 탭 뒤에 숨기면 되지 않을까 생각했지만, 몇 번 눌러서 결제에 닿으면 결국 돌려서 이끄는 것이라 위험은 같았다. 그래서 앱에서 넘어가는 곳은 결제로 가는 길이 아예 없는 계정 화면으로 정했다.
돌아가는 길 말고 정면으로 가는 길도 있었다. 한국에서는 플레이의 외부 결제 프로그램에 등록하면 앱에서 웹 결제로 공식적으로 보낼 수 있다. 그래서 등록하기로 했다.
메트로놈 때 배운 태도와 같은 모양이다. 플랫폼이 막아 둔 것을 하고 싶으면, 대개 우회로보다 정식 창구가 있다. 창구는 공짜가 아니다. 포그라운드 서비스는 알림을 띄우는 게 대가였고, 외부 결제 프로그램은 수수료가 없어지지 않고 줄어들 뿐이며 거래를 보고해야 한다(앞선 TIL 12의 표). 우회로는 걸리면 앱이 내려가지만, 창구의 대가는 미리 계산할 수 있다.
요약
- 백그라운드에서 멈추면 언제 멈추든 포그라운드 서비스부터 본다. 한 번 배운 원리도 증상이 다르면 못 알아본다.
- 계정이 곧 이용권인 서비스는 라이선스 키가 필요 없다. 결제를 시작하는 쪽이 계정 번호를 메모로 붙이고, 결제사의 알림이 그 메모를 돌려준다. 그래서 결제 입구는 로그인 뒤여야 한다.
- 플레이는 가격 하나로 나라별 현지 가격과 세금을 채워 준다. 결제 경로가 대신해 주는 일은 수수료 말고도 있다.
- 플레이 구독은 서버가 구글에 다시 물어서 확인한다. 서비스 계정은 초대와 앱별 권한을 따로 줘야 한다.
- 플레이는 승인하지 않은 구매를 환불한다. 구매 확인은 매출의 조건이다.
- 테스트 구독은 공짜이고 5분 주기라서, 갱신과 만료까지 출시 전에 돌려 볼 수 있다.
- 플랫폼이 막은 길은 우회하지 말고 정식 창구의 대가를 계산한다.