[스타트업/이슈] Google Play 인앱 결제 API | "insufficient permissions" 오류

문제 결제 서버가 Google Play Developer API로 영수증을 검증할 때 "insufficient permissions"로 계속 실패했다.
시도 서버의 서비스 계정을 Play Console 사용자로 추가했지만 그대로였다.
해결 기존 인앱 상품을 비활성화하고 새로 등록하자 검증이 통과했다.


지난 글에서 만든 서버로 구글 플레이 인앱 결제 영수증 검증 API를 호출하다가 참 황당한 에러를 만났다.

서버 로그에 남은 에러는 이랬다.

error_code: "internal"
error_message: "The current user has insufficient permissions to perform the requested operation."
event_name: "billing.request.failed"

메시지만 보면 명확하다. API를 호출하는 주체에게 권한이 없다. 그런데 원인을 따라가 보니 겉보기처럼 단순하지 않았다. GCP와 Play Console의 권한이 어떻게 얽혀 있는지, 어떻게 풀었는지, 그리고 무엇을 끝내 확인하지 못했는지 정리한다.



1. 로그만으로 원인을 좁히기 어려웠던 이유

먼저 우리 서버가 구글에 어떻게 인증하는지부터 보면, 코드에는 키 파일이 없다. Cloud Run에서 도는 서버가 ADC(Application Default Credentials)로 Cloud Run 서비스가 실행되는 서비스 계정의 자격을 얻고, androidpublisher 스코프로 API를 부른다. 그러니 "current user"는 사람이 아니라 이 서비스 계정이다.

그리고 로그의 error_code: "internal"은 구글이 준 코드가 아니다. 우리 서버의 에러 미들웨어는 직접 정의한 에러(AppError)가 아닌 예외를 전부 internal로 바꿔 기록한다. 구글이 돌려준 HTTP 상태 코드는 이 과정에서 사라지고, 메시지 문자열만 남았다. 로그만 보면 "서버 내부 오류인데 메시지가 권한 얘기"인 상태였고, 결국 단서는 메시지 한 줄뿐이었다.

(나중에 재시도 정책을 정비하면서 internal은 "재시도할 만한 에러"로 분류됐다. 이 설정 오류가 다시 난다면 클라이언트는 앱을 켤 때마다 같은 영수증을 다시 보낼 것이다. 권한 문제는 재시도로 풀리지 않는데도 그렇다.)



2. 첫 번째 시도: 서비스 계정 추가

'서비스 계정에 Play Console 접근 권한이 없다'에 집중했다. 서버가 Cloud Run에서 구글 API를 부르고 있었으므로, 그 서비스 계정을 Google Play Console에 관리자 권한으로 추가했다.
(사실 GCP에 이미 Play Integrity API를 연결해 뒀어서 될 거라고 생각했는데, 이거랑 결제를 검증하는 Android Publisher API(Google Play Developer API)는 또 다른 거였다;;)

대부분의 자료에서는 이렇게 하면 된다고 했다.


이제는 당연히 해결될 것이라 믿고 다시 테스트를 진행했다. 하지만 결과는 동일했다. 여전히 insufficient permissions 오류가 발생했다.




2. 문제의 근본 원인: 계정 이전

그러다 찾게 된 한 블로그...

그리고 시스템 로그와 과거 작업 히스토리를 천천히 되짚어 보았다. 생각해보니 구글 결제 프로필 설정 과정에서 있었던 하나의 이벤트를 발견했다.

이번에 사업자 등록을 진행하며 계정 이전을 진행하였다. 이 과정에서 기존에 등록해 두었던 '인앱 상품' 데이터는 새로운 계정으로 무사히 마이그레이션 되었다. 표면적으로는 아무런 문제가 없어 보였다.

하지만 문제는 보이지 않는 '권한'에 있었다. 구글 플레이 콘솔에 새로 추가한 IAM 계정은 콘솔 자체에 대한 권한은 얻었지만, 계정 이전 전에 생성되었던 기존 Legacy Products에 대한 API 접근 권한까지 온전히 상속받지 못한 상태였다.

즉, 데이터(상품)는 이전되었지만, 결제 검증에 필요한 내부적인 리소스 권한 맵핑은 과거의 결제 프로필에 머물러 있거나 단절된 것이다. 새로운 권한을 아무리 부여해도 시스템은 과거의 식별자와 충돌하며 '권한 없음'을 반환하고 있었던 셈이다.




3. 해결: 초기화와 재등록

해결 방법은 위 글에 나온 대로 초기화에 가까운 접근이었다. (앱 이전 정상적으로 해준다며...구글...)

기존에 등록되어 있던 인앱 상품들을 모두 비활성화(또는 삭제)하고, 현재의 올바른 권한이 세팅된 상태에서 상품을 처음부터 새로 등록했다. 새로 등록된 상품은 현재의 결제 프로필과 새롭게 추가된 IAM 계정의 권한 하에 온전하게 생성된다고 한다.

상품을 재등록한 후, 다시 영수증 검증 API를 호출했다.

결과는 성공이었다. internal 에러가 사라지고 정상적인 검증 응답 데이터가 반환되었다.



아, 그리고 '라이센스 계정' 등록을 해줘야 실구매 없이 테스트 결제가 가능하다!
그냥 테스트용 앱이라고 테스트 결제만 된다고 생각했다가 실제로 돈이 빠져나간다



4. 다시 겪는다면

  • 외부 API 에러를 우리 에러로 바꿀 때, 상대가 준 HTTP 상태와 에러 사유를 로그 필드로 따로 남긴다. 이번에는 메시지 한 줄에 기대야 했다.
  • 권한을 바꾼 시각과 테스트한 시각을 적어 둔다. 반영 지연 가설을 가르려면 이 간격이 필요했다.
  • 상품 전체를 재등록하기 전에, 기존 상품 하나를 수정한 뒤 재시도해 본다. 그것만으로 풀리면 원인은 반영 쪽에 가깝다.


마치며

이번 이슈를 통해 클라우드 인프라와 플랫폼 서비스(BaaS)를 다룰 때 명심해야 할 중요한 교훈을 얻었다.

첫째, 플랫폼 간의 연동(GCP - Play Console)에서 발생하는 권한 문제는 단순히 UI 상의 체크박스를 켜고 끄는 것으로 끝나지 않을 때가 많다. 특히 '계정 이전'이나 '결제 프로필 변경'과 같은 굵직한 메타 데이터의 변화가 있을 때는, 리소스 간의 내부 참조(Reference)가 깨질 수 있음을 항상 염두에 두어야 한다.

둘째, 원인을 알 수 없는 권한 오류가 발생했을 때, 권한을 계속해서 높이는 것(Escalation)은 정답이 아니다. 리소스 자체가 가진 상태(State)와 권한이 부여된 시점(Lifecycle)을 의심해 보아야 한다.

단순한 상품 재등록으로 끝난 문제였지만, 그 이면에 숨겨진 분산 시스템의 권한 동기화 문제를 엿볼 수 있었던 의미 있는 시간이었다.




추천글

[스타트업/기술] Node.js & Google Play 인앱결제 | 결제 시스템 아키텍처 설계 (Part 1)

[스타트업/기술] Node.js & App Store 인앱결제 | 결제 시스템 아키텍처 (Part 2)

[스타트업/기술] 인앱결제 Part 3 | 검증에 성공한 뒤에 시작되는 문제들


hyeon_B

안녕하세요! AI 기술을 이용해 더 나은 세상을 만들어 나가고 싶은 과기원생 Hyeon이라고 합니다. 저는 앞으로 인공지능 시대에는 지식을 '활용'하는 능력이 중요해질 것이라고 생각합니다. 대부분의 일들은 인공지능이 뛰어난 모습을 보이지만, 인공지능은 데이터로 부터 연관관계를 학습하기 때문에 지식들을 새로 통합해서 활용하는 능력이 부족합니다. 인공지능이 뉴턴 전에 만들어졌다면 사과가 떨어지는 이유에 대답하지 못했을 것이고, 아인슈타인 전에 만들어졌다면 중력이 어떻게 생기는지 설명하지 못했을 것입니다. 따라서 앞으로 우리는 '본질'을 탐구하고 그 본질로부터 다른 곳에 적용하며 인공지능을 현명하게 활용해야 할 것입니다. 함께 인공지능 시대를 준비합시다!

댓글 쓰기

다음 이전

POST ADS1

POST ADS 2