인앱 구독 결제 (In-App Purchase: IAP) 서버사이드 검증 2 - Android

공부는 혼자하는 거·2024년 7월 4일
0

업무

목록 보기
17/17
post-custom-banner

안드로이드 인 앱 결제 검증 절차를 서버사이드 관점에서 간단 정리해보자

GoogleCredential

서비스 계정 키 나열 및 가져오기

GCP 에 들어가서 결제정보에 접속할 수 있는 권한이 부여된 서비스 계정을 하나 만들고 해당 키를 JSON 형태로 하나 만들어서 다운받아주자.

  implementation("com.google.api-client:google-api-client:2.6.0")

해당 JSON 파일을 그대로 사용할 수도 있겠지만, 나같은 경우 yml 파일에 환경변수로 박아넣고, 다운받은 JSON 파일 형태의 문자열을 그대로 변수로 바인딩시켜놓고 사용했다.

이렇게 안 할 거면..

영수증 검증

이제 이 해당 토큰을 가지고, 구독이나 구매에 대해 구글 서버에게 검증을 요청할 수 있다.

구독 검증

Method: purchases.subscriptions.get

App에서 보낸 subscriptionId와 purchaseToken을 가지고, 구독에 대한 정보를 조회하는 API를 호출하면 된다.

 fun verifySubscribe(
        packageName: String,
        productId: String,
        purchaseToken: String
    ): ResponseEntity<String> {


        val url =
            "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/$packageName/purchases/subscriptions/$productId/tokens/$purchaseToken"

        val credential = getGoogleCredentials()

        val headers = HttpHeaders()
        headers.setBearerAuth(credential.accessToken.tokenValue)

        val requestEntity = HttpEntity<String>(headers)

        return RestTemplate().exchange(url, HttpMethod.GET, requestEntity, String::class.java)
    }

성공하면 대략 다음과 같은 JSON 문자열을 바디에 응답해줄 거다.

{
  "acknowledgementState": 0,
  "autoRenewing": false,
  "countryCode": "",
  "developerPayload": "",
  "expiryTimeMillis": "",
  "kind": "",
  "orderId": "",
  "paymentState": 0,
  "priceAmountMicros": "",
  "priceCurrencyCode": "",
  "purchaseType": 0,
  "startTimeMillis": ""
}

여기서 orderId가 고유한 거래 식별자이다. 안드로이드는 애플과 달리 새구독시 새로운 orderId로 온다.

orderId: 고유한 거래 식별자. 각 구독 구매 또는 갱신에는 고유한 식별자가 있어서, 이 거래가 이미 이전에 처리되었는지 여부를 확인하는 데 사용할 수 있습니다. 각 갱신 식별자에는 반복되는 초반부가 있는데, 여기에 두 개의 점과 (0부터 시작하는) 구독 갱신 횟수가 덧붙여집니다. 활성화될 때 구독에 GPA.3382-9215-9042-70164 식별자가 있는 경우, 첫 번째 갱신은 GPA.3382-9215-9042-70164..0으로 식별되고 두 번째 갱신은 GPA.3382-9215-9042-70164..1로 식별되는 식입니다. 이 방법으로 거래 체인을 구축하고 갱신 횟수를 추적할 수 있습니다.

구매 검증

구매 검증도 비슷한 로직이다. 다만 호출하는 url 주소가 다르니 주의해야한다.

Method: purchases.products.get

RTDN

구글 측에서는 사용자가 구독을 취소하거나, 만료가 되는 등 기타 변경사항이 있을 때 우리 서버도 알 수 있게끔 RTDN을 제공해주고 있다. 엔드포인트를 하나 만들어주고, 실제 프로덕션이면 대략 다음과 같은 Json 형식의 데이터가 알 것이다.

{
    "message": {
        "data": "eyJ2ZXJzaW9uIjoiMS4wIiwicGFja2FnZU5hbWUiOiJrci5jby5zb3VuZHBsYXRmb3JtLnNsZWVwIiwiZXZlbnRUaW1lTW",
        "messageId": "12193204485444444",
        "message_id": "12193204485444444",
        "publishTime": "2024-09-06T08:04:08.059Z",
        "publish_time": "2024-09-06T08:04:08.059Z"
    },
    "subscription": "projects/sssss/ssssss/sssss-rtdn"
}

data 필드를 Base64 디코딩해주면 json이 나오는데, 이를 다음과 같은 DTO 로 변환시켜주면, subscriptionId와 purchaseToken을 얻을 수 있다.

이 정보를 가지고 다시 구글 서버에게 문의하면

"https://androidpublisher.googleapis.com/androidpublisher/v3/applications/$packageName/purchases/subscriptions/$subscriptionId/tokens/$purchaseToken"

아래와 같은 영수증 데이터를 얻을 수 있다.

이제 이 orderId를 우리 쪽 DB에 대입해 조회한 다음 업데이트시켜주면 끝~
역시 주의할 점은, RTDN이 오는데 순서가 동기적이지 않다는 것이다. 따라서, eventTimeMillis를 가지고 최신 걸로 판단하면 된다.

RTDN으로 우리 쪽 서버가 못 받았을 시?

RTDN을 했다고 능사가 아니다, 엔드포인트로 구글 서버가 쏴도 잠시 우리 쪽 서버가 중단되어 데이터를 못 받거나 에러가 터져서 정상적으로 응답을 못한 경우, 기본적으로 구글 서버는 우리 쪽에서 응답을 정상적으로 내려주지 않을 시 일정 주기로 계속 동일 데이터를 쏴주긴 한다. 그래서 해당 증상을 보고 고칠 수는 있으나, 더 보완하기 위해 서버에서 추가적으로 상태를 조회할 필요가 있다. 스케줄러를 통해 주기적으로purchases.subscriptions.get api를 호출해 상태를 동기화해줄 필요가 있다.

참고

Real-time developer notifications reference guide

[백엔드] 구글 인앱결제 영수증 검증
인앱 구매 서버 알림
Google Play 결제 시스템
구글인앱결제와 실시간 알림 (RTDN)
Android 인앱 구매, 5부: 서버 측 구매 검증






profile
시간대비효율
post-custom-banner

0개의 댓글