안드로이드 인 앱 결제 검증 절차를 서버사이드 관점에서 간단 정리해보자
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을 제공해주고 있다. 엔드포인트를 하나 만들어주고, 실제 프로덕션이면 대략 다음과 같은 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을 했다고 능사가 아니다, 엔드포인트로 구글 서버가 쏴도 잠시 우리 쪽 서버가 중단되어 데이터를 못 받거나 에러가 터져서 정상적으로 응답을 못한 경우, 기본적으로 구글 서버는 우리 쪽에서 응답을 정상적으로 내려주지 않을 시 일정 주기로 계속 동일 데이터를 쏴주긴 한다. 그래서 해당 증상을 보고 고칠 수는 있으나, 더 보완하기 위해 서버에서 추가적으로 상태를 조회할 필요가 있다. 스케줄러를 통해 주기적으로purchases.subscriptions.get api를 호출해 상태를 동기화해줄 필요가 있다.
Real-time developer notifications reference guide
[백엔드] 구글 인앱결제 영수증 검증
인앱 구매 서버 알림
Google Play 결제 시스템
구글인앱결제와 실시간 알림 (RTDN)
Android 인앱 구매, 5부: 서버 측 구매 검증