タノシムスタジオ所属でサーバーエンジニアをしている吉田です。
アプリゲームによくある課金ですが、最終的にその課金レシートが正しいかどうかをサーバーで検証する必要が出てきます。
IOSとAndroidでレシート検証方法は異なっているのですが、今回はAndroidの方のサーバーでのレシート検証の方に焦点を当てて話ができればなと思います。
サンプルとしてはRubyになっていますが、多言語であっても同様のロジックであれば対応可能です。
レシートの検証の手順については下記の方法で進めていきます。
1. 自分のアプリのレシートかどうかの確認
2. サーバーにその課金情報があるかどうかの確認
自分のアプリのレシートかどうかの確認
まず最初にクライアントから受け取ったレシート情報と署名文字列情報を使って、自分のアプリかどうかの確認を行います。
具体的には、クライアントから受け取ったレシート情報と署名文字列情報をGoogle Playの公開鍵を使って検証します。
下記のソースコードのdigestについては使用するハッシュ関数名を指定します。
また、public_keyはGoogle Playでの公開鍵、signatureはクライアントから受け取った署名文字列情報、receipt_dataはレシート情報を指しています。
digest = OpenSSL::Digest::SHA1.new
OpenSSL::PKey::RSA.new(public_key).verify(digest, signature, receipt_data)
Google Playでの公開鍵についてはコンソールから対象アプリを選んでもらって、左の収益化の一番下にある収益化のセットアップ内にあるライセンスがその鍵になります。
(コンソールがバージョンアップするとこの公開鍵の場所が変わるので厄介ですが…)
サーバーに課金情報があるかどうかの確認
レシートが自分のアプリのものであるを確認したところで、本当に購入したログがGoogle側のサーバー残っているのかの確認を行います。
検証の方法については、検証用のAPIであるAndroid Publisher API を叩くだけなのですが、このAPIを使うにはGoogle Playアプリにアクセスできるアカウントが必要になります。
このアカウントですが個人のもので行ってしまうと、例えばアカウントを失効してしまうと処理が通らなくなってしまいます。なので、ここは手間がかかりますが、Android Publisher API を叩くためのサービスアカウントを作成して、そのアカウント経由で認証する方法をとります。
JWTの作成
JWTとは JSON Web Tokenと呼ばれるもので、ざっくりというとJsonに電子署名を加えてURL-Safeにした文字列 Token のことで、作成するに当たっては下記のものを指定する必要があります
- どのアカウントなのか
- 認証方法
- 使用したいAPI
サービスアカウントをGoogle Play Console で作成します。
この後作成したサービスアカウントのJSONファイルが必要になってくるので、あらかじめダウンロードしておきます。
{
"type": "service_account",
"project_id": "[project-id]",
"private_key_id": "[private_key_id]]",
"private_key": "-----BEGIN PRIVATE KEY-----\n[private_key]\n-----END PRIVATE KEY-----\n",
"client_email": "[service_account_email]",
"client_id": "[client_id]",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/[service_account_email]"
}
この中でJWT作成に必要な情報はclient_emailとprivate_keyの値が必要になるので、JWTに情報を入れます。
payload = {
iss: [client_email],
scope: 'https://www.googleapis.com/auth/androidpublisher',
aud: 'https://oauth2.googleapis.com/token',
exp: Time.now.to_i + 3600,
iat: Time.now.to_i
}
rsa_private = OpenSSL::PKey::RSA.new(private_key)
JWT.encode(payload, rsa_private, 'RS256')
これでアクセスキーを取得するためのJWTが作成できたので、次にこれを使ってアクセスキーを取得します。
アクセスキーの取得
アクセスキーの取得は先ほど作成したJWTをgoogle認証のAPIにリクエストすると取得できます。
JSONの中に必要なアクセスキーがあるので、パースして取得しておきます。
def self.issue_request_token
url = 'https://oauth2.googleapis.com/token'
header = {
'Content-Type' => 'application/x-www-form-urlencoded'
}
params = {
assertion: service_auth_jwt,
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer'
}
body, status_code = post_request(url, header, params)
res = JSON.parse(body)
res['access_token']
end
取得したアクセスキーを使ってAndroid Publisher APIにリクエストを投げる
最後にアプリパッケージ名と、プロダクトID、アプリからもらった検証用のトークンをURLに埋め込んで、ヘッダに先ほど作成したアクセストークンを付与してAndroid Publisher APIにリクエストを投げます。
...
url = ”https://www.googleapis.com/androidpublisher/v3/applications/” + "[アプリパッケージ名]/purchases/products/[プロダクトID]/tokens/[アプリからもらった検証用のトークン]"
...
def self.get_request(url, token = nil)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme === 'https'
req = Net::HTTP::Get.new(uri.path)
req['Authorization'] = "Bearer #{token}" if token
response = http.request(req)
[response.body, response.code.to_i]
end
ここまでの作業で検証結果がAndroid Publisher APIから購入情報のレスポンスが返ってきます。
なので、返ってきたHTTPレスポンスコードと中身をデータを確認して、正しく購入できていると確認できれば、購入後の処理を色々と追加するという形になります。
ちなみにですが、ここまでの記載で行っている作業自体はGoogle Play側のサーバーで購入されたレシートの状態を確認するものになります。
なので、同じレシートデータを検証サーバーにまた投げたとしてもレスポンスとしては同じものが返ってきます。レシート検証後に行う処理をこの後に追加する必要があります。
まとめ
簡単にですが、Androidのレシート検証について触れました。この手の実装については一度実装すると、提供されているAPIに変更がない限りかぎり使い続けることができます。(メンテナンスをしなくても良いという話ではありませんが…)
似たようなところで詰まっている人の助けになれば何よりです。