태그 구독 결제인앱결제RevenueCatGoogle Play안드로이드
전체 글 보기

구독 결제

구독 결제 붙이기 - 플레이스토어와 RevenueCat 연결 전체 절차

안드로이드 앱에 구독 결제를 붙이는 전 과정을 순서대로 적었습니다. 상품 만들기부터 결제 확인까지, 막히기 쉬운 자리마다 왜 막히는지도 같이 적었습니다.

2026년 8월 21일 7분

앱에 구독 결제를 붙였습니다. Expo SDK 56, 안드로이드 targetSdk 36 기준입니다.

하루가 걸렸는데 코드 때문은 아니었습니다. 대부분은 스토어 설정과 결제 서비스를 연결하는 일이었고, 막히는 자리가 정해져 있었습니다.

그 순서를 그대로 적습니다. 처음 하시는 분이 이 글만 따라가도 되게 쓰려고 했습니다.

같은 작업에서 릴리즈가 세 번 막힌 이야기는 따로 적었습니다.

구독 결제는 왜 혼자 못 하나

구독 결제는 물건을 한 번 파는 것과 다릅니다. 계속 갱신되고, 중간에 끊기고, 다시 살아납니다.

상태가 이만큼 있습니다.

활성 · 무료체험 · 해지했지만 만료 전 · 결제 실패로 유예 중
· 일시정지 · 만료 · 환불 · 요금제 변경 · 재구독

플레이스토어는 이 변화를 알림으로 던지기만 합니다. “그래서 지금 이 사람이 유료 회원인가”로 정리하는 일은 우리 몫입니다.

그 정리를 대신해 주는 서비스가 RevenueCat 입니다. 영수증이 진짜인지 확인하고, 상태를 하나로 접어서 “이 사람은 유료다/아니다”만 알려 줍니다.

⚠️ 안 써도 됩니다. 안드로이드 하나만 팔고 상품도 하나뿐이면 직접 할 만합니다. 저는 나중에 아이폰과 웹에서도 팔 생각이라 썼습니다. 스토어가 늘어나면 각각 다른 방식으로 같은 일을 해야 하는데, 그걸 하나로 묶어 주는 게 이 서비스의 값어치입니다.

준비물부터

시작하기 전에 이 셋이 있어야 합니다.

사업자등록과 결제 프로필. 없으면 상품을 만드는 화면에서 바로 막힙니다. 이게 제일 오래 걸리니 먼저 해 두세요.

앱이 스토어에 올라가 있을 것. 내부 테스트 트랙이면 충분합니다.

서비스 계정. 스토어의 데이터를 프로그램이 읽을 수 있게 해 주는 열쇠입니다. 이미 배포 자동화를 쓰고 계시면 그 계정을 그대로 쓰면 됩니다.

1. 상품 만들기

Play Console → 수익 창출 → 정기 결제 → 정기 결제 만들기

두 개를 만듭니다. 월간 하나, 연간 하나.

⚠️ 상품 ID 는 만든 뒤에 못 바꿉니다. 지우고 다시 만들어도 같은 ID 는 재사용이 안 됩니다. 이름을 정할 때 한 번 더 생각하세요.

저는 이렇게 지었습니다.

com.내회사.내앱.monthly
com.내회사.내앱.yearly

⚠️ ID 안에 monthyear 를 넣으세요. 나중에 코드에서 “이게 월간이냐 연간이냐”를 판단할 때 이 글자를 봅니다. 안 넣으면 판단할 근거가 없어서 나중에 다른 방법을 찾아야 하는데, ID 는 못 바꿉니다.

상품만 만들면 안 팔립니다. 상품 안에 기본 요금제를 따로 만들어야 합니다. 상품이 껍데기라면 요금제가 실제로 팔리는 물건입니다.

상품 → 기본 요금제 추가
  결제 주기: 1개월 / 1년
  가격:     지역별로
  → 활성화

저는 목록에서 월간 옆에 1, 연간 옆에 0 이 적힌 걸 보고서야 연간에 요금제를 안 만들었다는 걸 알았습니다. 그 숫자가 요금제 개수입니다.

해외 가격은 자동 설정을 쓰세요. 한국 가격을 넣고 “다른 국가 가격 자동 설정”을 누르면 나라별로 적당히 맞춰 줍니다. 환율로 그대로 바꾸면 2.15달러 같은 아무도 안 쓰는 가격이 나옵니다.

⚠️ 가격은 나중에 바꿀 수 있습니다. 다만 올리는 건 어렵습니다. 기존 구독자에게 미리 알리고 동의를 받아야 하고, 동의를 안 하면 구독이 끊깁니다. 그러니 너무 싸게 시작하지 마세요.

2. 실시간 알림 통로 만들기

결제 서비스가 스토어의 변화를 알아야 합니다. 그 통로를 만드는 단계입니다.

구글 클라우드의 메시지 큐를 씁니다. 이름이 어려운데, 스토어가 소식을 넣고 결제 서비스가 꺼내 가는 우편함이라고 보시면 됩니다.

1) console.cloud.google.com → API 라이브러리 → "Cloud Pub/Sub API" 사용 설정
2) Pub/Sub → 주제 → 주제 만들기 (이름은 아무거나, 저는 play-rtdn)

여기서 두 번 막혔습니다.

첫째, 우편함에 넣을 권한을 줘야 합니다. 그런데 줘야 할 대상이 구글이 가진 계정입니다. 내 계정이 아닙니다.

만든 주제 → 권한 → 주 구성원 추가
  google-play-developer-notifications@system.gserviceaccount.com
  역할: Pub/Sub 게시자

⚠️ 여기에 내 서비스 계정을 넣으면 안 됩니다. 스토어가 우편함에 넣는 쪽이고, 넣을 권한이 없으면 스토어가 그 우편함을 거부합니다.

둘째, 내 계정에는 꺼내 갈 권한이 필요합니다. 결제 서비스가 내 계정으로 로그인해서 우편함 목록을 읽기 때문입니다. 이게 없으면 그쪽 화면의 입력칸이 그냥 비어 있고, 왜 비었는지는 안 알려 줍니다.

IAM → 액세스 권한 부여
  내 서비스 계정
  역할: Pub/Sub 편집자

⚠️ 구독자(Subscriber)로는 부족합니다. 결제 서비스가 우편함에 새 구독을 만들어야 하는데 그건 편집자 권한입니다.

권한을 주기 전에 그 계정으로 직접 확인해 보면 빠릅니다.

토픽 목록: 403 User not authorized to perform this action

이 한 줄이 “자동완성이 안 뜬다”보다 훨씬 많은 걸 말해 줍니다.

마지막으로 스토어에 등록합니다.

Play Console → 수익 창출 → 수익 창출 설정
  → 실시간 개발자 알림 → 주제 이름 붙여넣기 → 저장
  → 테스트 알림 보내기

테스트 알림이 성공하면 통로가 선 겁니다.

⚠️ 내용까지 보려면 구독을 하나 만들어야 합니다. 우편함에 넣기만 하고 꺼내 가는 사람이 없으면 편지가 그냥 버려집니다. 확인용으로 만드셨다면 나중에 지우세요. 아무도 안 읽는 구독에 편지가 계속 쌓입니다.

3. 결제 서비스에 연결하기

RevenueCat → 앱 설정 → Google Play
  → 서비스 계정 JSON 업로드
  → 방금 만든 주제 선택

⚠️ 스토어에서 그 계정에 권한을 줬는지 확인하세요.

Play Console → 사용자 및 권한 → 그 서비스 계정
  ✓ 재무 데이터, 주문, 설문조사 응답 보기
  ✓ 주문 및 정기 결제 관리

이게 없으면 나중에 결제는 되는데 결제 서비스가 모르는 상태가 됩니다.

그다음 셋을 만듭니다.

상품 가져오기. 스토어에서 만든 두 상품을 목록에 넣습니다.

권한 묶음(entitlement). “이 사람은 유료다”를 뜻하는 이름입니다. 두 상품을 여기에 붙입니다. 월간을 사든 연간을 사든 같은 권한이 열립니다.

⚠️ 이 이름을 앱 코드에도 똑같이 적어야 합니다. 글자 하나만 달라도 결제는 성공하는데 앱이 안 열립니다. 돈은 빠져나가고 화면은 그대로입니다. 그리고 에러가 하나도 안 납니다. 없는 이름을 찾으니 그냥 못 찾을 뿐입니다.

상품 묶음(offering). 앱에 보여 줄 가격표입니다. 만든 뒤 기본으로 지정해야 합니다.

⚠️ 이걸 빠뜨리면 상품도 있고 가격도 있는데 앱에는 아무것도 안 나옵니다. 제일 자주 빠뜨리는 자리입니다.

그리고 그 안의 항목 이름을 Monthly / Annual 로 맞추세요. 앱 코드가 그 이름으로 찾습니다.

4. 앱에서 확인하기

여기까지 하면 앱에서 가격이 보여야 합니다. 그런데 저는 이 문구를 만났습니다.

이 버전의 애플리케이션에서는 결제를 사용할 수 없습니다.

원인은 설치 경로였습니다. 스토어 결제는 앱이 스토어를 통해 설치된 것이어야 동작합니다. 저는 테스트 배포 도구로 받은 앱을 쓰고 있었고, 게다가 그 버전 번호는 스토어에 올라간 적이 없었습니다.

고치는 법은 이렇습니다.

1) 테스트 계정 등록. 이걸 해야 돈이 안 나갑니다.

Play Console → 설정 → 라이선스 테스트 → 내 구글 계정 추가

⚠️ 개발자 계정이라도 여기에 따로 넣어야 합니다.

2) 내부 테스트 트랙에 올리기. 심사가 없어서 몇 분이면 됩니다.

3) 내부 테스트 링크로 참여해서 스토어에서 설치. 다른 경로로 받은 앱은 먼저 지우세요.

이렇게 하니 가격이 떴습니다.

5. 서버도 알게 하기

앱은 결제 서비스에 직접 물어보므로 여기까지만 해도 화면은 열립니다. 그런데 서버는 아직 아무것도 모릅니다.

제 경우 서버가 유료 여부를 봐야 하는 자리가 이미 있었습니다. 사진을 분석해 주는 기능인데, 호출마다 비용이 나가서 무료 회원과 유료 회원의 한도를 다르게 뒀습니다. 서버가 모르면 돈 낸 사람이 무료 한도로 막힙니다. 화면에는 “유료”라고 적혀 있는데도요.

그래서 결제 서비스가 우리 서버로 소식을 보내게 합니다.

RevenueCat → Integrations → Webhooks
  URL:            https://내주소/api/revenuecat-webhook
  Authorization:  Bearer 아무_문자열

서버 환경변수
  RC_WEBHOOK_SECRET = 같은_문자열

⚠️ 환경변수를 넣은 뒤에는 다시 배포해야 합니다. 배포할 때 값이 구워지기 때문에, 값만 넣고 배포를 안 하면 예전 배포는 계속 값이 없는 상태입니다.

저는 여기서 10분을 헤맸습니다. 푸시를 했는데도 안 바뀌길래 이상해서 보니, 그 커밋에 서버 폴더 변경이 하나도 없어서 빌드 자체를 건너뛴 것이었습니다.

응답 코드로 어디가 문제인지 가를 수 있게 해 두면 편합니다.

500  환경변수가 아직 안 실렸다 (다시 배포해야 함)
401  값은 실렸고 열쇠만 다르다 (정상)

6. 구독 상태를 확인하는 세 곳

다 붙이고 나면 상태가 사는 곳이 셋이 됩니다. 셋이 어긋날 수 있고, 그때 어디가 틀렸는지 알아야 합니다.

어디무엇보는 법
스토어진짜 구독Play Console 주문 관리
결제 서비스정리된 상태대시보드에서 사용자 검색
우리 서버소식을 받아 적은 값데이터베이스 직접 조회

⚠️ 결제 서비스에서 사용자를 찾을 때는 우리 회원 번호로 찾습니다. 앱이 로그인할 때 그 번호를 결제 서비스에 알려 주도록 해 두면, 두 시스템에서 같은 사람을 같은 이름으로 부를 수 있습니다.

테스트 구독은 빨리 갱신됩니다. 실제로는 한 달인 것이 몇 분 만에 갱신되고, 몇 번 반복한 뒤 만료됩니다. 그래서 갱신과 만료를 실제로 확인할 수 있습니다. 한 달을 기다릴 필요가 없습니다.

해지 테스트는 이렇게 합니다.

폰의 플레이스토어 앱 → 프로필 → 결제 및 정기 결제 → 정기 결제 → 해지

⚠️ 해지해도 만료일까지는 유료입니다. 해지 즉시 잠기면 그게 잘못된 겁니다. 이미 낸 돈만큼은 써야 합니다.

막혔을 때 원인을 가르는 법

이 과정에서 막힌 자리가 여럿이었는데, 공통점이 있었습니다. 어느 것도 원인을 가리키는 에러를 내지 않았습니다. 값이 없거나 이름이 안 맞으면 그냥 조용히 지나갔습니다.

그래서 이렇게 확인했습니다.

권한이 의심되면 그 계정으로 직접 찔러 봅니다. 화면이 “안 된다”고만 할 때, 같은 요청을 명령줄에서 보내면 403 같은 실제 이유가 나옵니다.

서버 문제가 의심되면 같은 조회를 서버에서 돌려 봅니다. 앱은 권한이 없는 것과 데이터가 없는 것을 같은 모양으로 실패합니다. 권한 검사를 우회하는 자리에서 같은 걸 돌리면 그 둘이 갈립니다.

응답 코드를 다르게 만들어 둡니다. 위의 500과 401처럼, 실패마다 다른 번호를 주면 나중에 그 번호 하나로 원인을 압니다.

아직 못 해 본 것

결제를 끝까지 한 번도 안 해 봤습니다. 가격표까지는 떴지만, 실제로 사고 그게 서버까지 도달하는 걸 본 적은 없습니다. 거기까지 가기 전에는 전부 가정입니다.

그리고 무료 회원과 유료 회원을 어떻게 가를지도 아직 안 정했습니다. 지금 제 코드에는 방식이 두 개 들어 있습니다. 하나는 체험 기간이 끝나면 앱 전체를 잠그는 것이고, 다른 하나는 계속 쓰게 하되 한도를 두는 것입니다. 둘은 같이 못 씁니다. 그걸 정하는 게 다음 일입니다.

이전 글 한 줄 고쳤는데 git은 295줄이 바뀌었다고 합니다