使用Ktor實作Android訂閱-Part3

YungHsin
13 min readMar 31, 2021

--

這一篇要進入使用Ktor來實作出Android訂閱的訂單透過Backend Server(My Server)來進行驗證的部分。

訂單驗證的部分需要使用到Google Devleoper API裡的purchases.subscriptions

文件位置:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.subscriptions

而get方法則是可以驗證使用者訂閱以及會回傳訂閱的相關資訊(Checks whether a user’s subscription purchase is valid and returns its expiry time.)

文件位置:https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.subscriptions/get

有了呼叫API的方法後,就可以準備在Ktor專案加入呼叫外部URL的程式,來進行訂閱的訂單的動作了。

不過這裡需要注意的是,Google Developer API需要先做OAuth登入,而且呼叫驗證API還需androidpublisher的權限,所以要設定OAuth scope。

Requires the following OAuth scope:

*這裡會先講解OAuth登入流程,接著再講解呼叫Google Developer流程,最後的部分才是把這些流程整合到Ktor裡講解。

OAuth登入流程

(1) 首先你要先去Google Play Console的網頁https://play.google.com/console/developers,去找到API存取權的選項,先進行Google Cloud 專案的連結,如果連結好了就會如下,然後點擊OAuth用戶端的右下角超連結“在Google Cloud Console中查看”

(2)接著網頁會進到”API和服務-憑證”的 設定畫面,

(3)打開OAuth2.0 用呼端ID的Google Play Android Developer的選項,進入下一頁,可以看到用戶端編號(ClientID)以及用戶端密碼(ClientSecret),這就是要用來在My Server(Ktor)內,進行OAuth登入需要的資料。

(4)怎麼呼叫Google OAuth登入API的文件你可以參考

我們就把以下的網址client_id填上用戶端編號,然後登入完後,會需要一個redirect_uri是OAuth登入確認後,Google Server會回傳Authorization_code到redirect_uri

https://accounts.google.com/o/oauth2/v2/auth?client_id=用戶端編號(ClientID)&redirect_uri=http://localhost:8080/OAuthCallback&response_type=code&scope=https://www.googleapis.com/auth/androidpublisher&prompt=consent&access_type=offline

(5)按允許後,如果你的redirect uri將會收到Authorize code

http://localhost:8080/?code=xxxxxxx&scope=https://www.googleapis.com/auth/androidpublisher

(6)有了Authorize code後,接下來就要用POST方法來呼叫https://oauth2.googleapis.com/token,並帶入Authorize code、client_id、client_secret以及redirect_uri

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=xxxxxxx(OAuth登入得到的authroize code)&
client_id=用戶端編號(ClientID)&
client_secret=用戶端密碼(ClientSecret)&
redirect_uri=http://localhost:8080&
grant_type=authorization_code

(7)呼叫成功後收到access_token以及refresh_token,(建議是可以存在My Server上,因為access_token的時效只有一小時,一小時過後需要refresh_token再去重新要一次)這樣就算成功的完成OAuth登入了!

{"access_token": "yaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","expires_in": 3599,"refresh_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","scope": "https://www.googleapis.com/auth/androidpublisher","token_type": "Bearer"}

驗證訂閱訂單流程

(1)有了呼叫androidpublisher的access_token後,終於可以回到訂閱的正題了,來呼叫驗證訂單的API了,packageName就是Android APP的package name,subscriptionId就是訂閱商品的id,token則是在Part2的第(9)步,APP訂閱成功後會收到的purchase.purchaseToken

GET /androidpublisher/v3/applications/{packageName}/purchases/subscriptions/{subscriptionId}/tokens/{token}Host: androidpublisher.googleapis.comAuthorization: Bearer yaxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

(2)呼叫成功後,會收到Google Developer API回傳該訂閱的細節,會包含訂閱開始結束時間,以及一些訂閱相關資訊,只要有這些資訊,伺服器就能開通用戶可享有的訂閱服務了。格式如下:

{
"startTimeMillis": "1615100318454",
"expiryTimeMillis": "1615100736313",
"autoRenewing": true,
"priceCurrencyCode": "TWD",
"priceAmountMicros": "99000000",
"countryCode": "TW",
"developerPayload": "",
"paymentState": 1,
"orderId": "GPA.xxxxxxxxxxxxxxxxxxxxxxxx",
"purchaseType": 0,
"acknowledgementState": 0,
"kind": "androidpublisher#subscriptionPurchase"
}

這個收到回傳物件是SubscriptionPurchase,官方文件可查到他每個欄位的細節。

https://developers.google.com/android-publisher/api-ref/rest/v3/purchases.subscriptions#SubscriptionPurchase

Ktor程式實作

(1) 首先我們要寫一個當OAuth登入後redirect會呼叫http://localhost:8080的部分,因為先會收到OAuth登入的authorize code

fun Route.subscription() {
get("/") {
val authroizationCode = call.parameters["code"]
val scope = call.parameters["scope"]
call.respondText("OAuth Login Success! scope=${scope}, code=${authroizationCode}", contentType = ContentType.Text.Plain)
}
}

OAuth登入後被呼叫http://localhost:8080的畫面:

(2)接著再用authorize code去呼叫Oauth token的api換取access_token跟refresh_token,並順便存到db,作為下次取用時

suspend fun token(
code: String,
client_id: String,
client_secret: String,
redirect_uri: String,
grant_type: String
) {

val client = HttpClient(CIO) {
install(JsonFeature) {
serializer = GsonSerializer()
}
}

val tokenResult = client.post<TokenResult> {
url(OAUTH_TOKEN_URL)
contentType(ContentType.Application.Json)
body = RequestData(code = code, client_id = client_id, client_secret = client_secret, redirect_uri = redirect_uri, grant_type = grant_type)
}

OAuthRepository().add(tokenResult)
}

而接收的回傳TokenResult就是相當於OAuth登入流程的第(7)步。

(3)有了accessToken後,就可以準備進行訂單的驗證部分了,這裡就能寫一個之後給APP呼叫的API,讓APP上傳packageName跟商品id以及購買收到的purchaseToken,交由My Server去呼叫Google Developer API去驗證並順便拿取訂單最後的細節。

給APP呼叫的API — validPurchase

get("/validPurchase") {

val packageName = call.parameters["packageName"]
val subscriptionId = call.parameters["subscriptionId"]
val token = call.parameters["token"]

val SubscriptionPurchase = async {
PurchaseSubscriptionService().get(
packageName!!,
subscriptionId!!,
token!!,
OAuthService().getAccessToken()?.access_token ?: ""
)
}.await()

call.respond(SubscriptionPurchase)
}

My Server繼續呼外部Http Client跟Google Developer API驗證訂單

class PurchaseSubscriptionService {    val ANDROID_PUBLISHER_SUBSCRIPTION_URL = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications"

suspend fun get(packageName:String, subscriptionId:String, token:String, accessToken:String):SubscriptionPurchase{
System.out.println("accessToken:${accessToken}")
return HttpClientUtil.client.get<SubscriptionPurchase> {
url("${ANDROID_PUBLISHER_SUBSCRIPTION_URL}/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}")
header("Authorization","Bearer $accessToken")
}
}
}

呼叫API完後收到訂單結果的畫面:(驗證訂閱訂單流程的第(2)步)

總結

補充一些東西,最後從Google Developer API收到的SubscriptionPurchase物件,其實你可以去運用Google Play Developer API的Client Libraries,它裡頭有source code你可以參考拿來用,就不用自己很辛苦地看文件一行一行打code建立,或是OAuth登入你也可以直接試著用client library去串接,但是我比較喜歡自己先試著用Postman一步一步試出request跟response的結果,再實作到程式裡,就看各位的想怎麼試囉!

SubscriptionPurchase.java原始碼:GitHub位置

Google Developer API Client Libraries and Code Sample:GitHub位置

下一篇Part4將會介紹Google Cloud的Pub/Sub服務怎麼接收訂閱訂單。

--

--