ゲームにおける課金システムについて(自動更新購読編)

こんにちは、アプリボット サーバーサイドエンジニアの森です。

iOS/Android用3Dアクションゲーム『リトルチャンピオンズ』をリリースするにあたり、自動更新購読型の課金を導入しました。

今回は、アプリケーションサーバーでの課金処理と自動更新購読型について紹介します。

1.課金の流れ

iOS(Apple Store) / Android(Google Play)ともに基本的な課金の流れは以下のようになります。

33fa3bf4-48c3-8809-b466-bcb8be70eb05.png

iOS / Androidの両プラットフォームとも、ストアにある商品を購入するとクライアントに購入情報が渡されます。

課金システムではその購入情報をクライアントからアプリケーション側へ送り、情報の検証とその情報を元に課金アイテムの付与などを行います。

2.レシート検証

iOSの検証

iOSでは、購入情報のことをレシートと呼びます。

受け取ったレシートを検証する方法は大きく分けて、Appleサーバーを用いる方法(リモート検証)とローカル検証の2通りあります。

リトルチャンピオンズでは、リモート検証を用いているので、リモート検証の方法について説明します。

Appleの提供している検証サーバーにレシートを送信することで検証を行うことができます。

以下のデータをJSONオブジェクトとして、検証サーバーへHTTP POSTリクエストします。

キー
receipt-data base64エンコードを施したレコードデータ
パスワード 自動更新購読で利用するアプリケーションの共通秘密鍵(16進文字列)

レシートの検証サーバーは実際の課金で使用する本番サーバーと開発などのテストで使用するSandBoxサーバーの2つ存在します。

それぞれ使用しているレシートが異なるため、発行したサーバーとは異なるサーバーへ送信するとエラーとなります。※ステータスコード表の21007, 21008

テストサーバー: https://SandBox.itunes.apple.com/verifyReceipt

本番サーバー : https://buy.itunes.apple.com/verifyReceipt

応答のペイロードはJSONオブジェクトで、以下のキーと値が収容されています。

キー 説明
status ステータスコードです。レシートが有効であれば0が入ります。以下に詳細を示します。
receipt 検証用に送信されたレシートをJSON形式で表したものです。
latest_receipt 自動更新型の購読の際に利用されます。
latest_receipt_info 自動更新型の購読の際に利用されます。

上記のステータスコード(status)の詳細は以下になります。

ステータスコード 説明
0 正常に認証が完了。
21000 App Storeは、提供したJSONオブジェクトを読むことができません。
21002 receipt-dataプロパティのデータが不正であるか、または欠落しています。
21003 レシートを認証できません。
21004 この共有秘密鍵は、アカウントのファイルに保存された共有秘密鍵と一致しません。自動更新型の購読に用いる、iOS 6型のトランザクションレシートの場合のみ。
21005 レシートサーバは現在利用できません。
21006 このレシートは有効ですが、自動更新購読の期限が切れています。ステータスコードがサーバに返される際、レシートデータもデコードされ、応答の一部として返されます。
21007 テスト環境のレシートを実稼働環境に送信して検証しようとしました。これはテスト環境に送信してください。
21008 実稼働環境のレシートを、テスト環境に送信して検証しようとしました。これは実稼働環境に送信してください。
21010 このレシートは認証できません。購入されなかった場合と同様に扱ってください。

参考:https://developer.apple.com/jp/documentation/ValidateAppStoreReceipt.pdf

Androidの検証

Google Playでは、商品を購入すると購入した商品情報にシグネチャがセットで与えられます。

このシグネチャを検証することで、購入情報が正しいかどうかを検討することができます。

購入情報はGooglePlayで事前に取得しているライセンスキーと、その購入情報に付随するシグネチャを用いて検証します。

具体的には、ライセンスキーはBase64エンコードされた公開鍵となっているため、RSAによるシグネチャの検証アルゴリズムを適用することで妥当性を評価しています。

※自動更新購読型の商品は上記方法ではなく、Apple同様に検証サーバへ問い合わせを行うことで妥当性を評価しています。

参考:https://developers.google.com/android-publisher/api-ref/purchases/subscriptions/get

購入に対するレスポンスデータではJSONオブジェクトで、以下のキーと値が収容されています。

キー 説明
autoRenewing 自動更新購読が自動更新されたかどうかを示します。trueの場合は自動更新購読が有効で、次の課金日に自動的に更新されます。falseの場合はユーザーが自動更新購読をキャンセルしたことを示します。 ユーザーは次の課金日までは自動更新購読コンテンツを利用できますが、自動更新を再度有効にするか、手動更新しなければ、次の課金日には自動更新購読コンテンツは利用できなくなります。
orderId トランザクション用の固有の注文識別子です。Googleペイメントの注文IDに対応しています。アプリ内課金SandBox経由でテスト購入した注文の場合、orderId は空欄です。また、プロモーションコードによる購入の場合もorderIdは空欄になります。
packageName 購入が発生したアプリケーション パッケージを表します。
productId アイテムの商品識別子です。 各アイテムには商品IDがあり、Google Play Developer Console 上のアプリ商品リストで設定します。
purchaseTime 商品が購入された時間を、1970 年 1 月 1 日から始まるミリ秒で表しています。
purchaseState 注文の購入状況を表します。 有効な値は 0(購入済み)、1(キャンセル済み)、2(返金済み)です。
developerPayload デベロッパーが指定する文字列で、注文の補足情報が含まれます。
purchaseToken 任意のアイテムとユーザーのペアにおける購入を一意に識別するトークンです。

参考:https://developer.android.com/google/play/billing/billing_reference?hl=ja

3.消費型と自動更新購読型

課金商品には、ある商品を一度のみ購入する「消費(Consumable)型」以外に、月額課金のように購入後一定期間コンテンツが有効となる「自動更新購読(Auto-Renewable Subscription)型」があります。

iOS / Androidどちらにも提供されています。

購入後、一度だけ一定期間コンテンツが有効となるもの(擬似自動更新購読)とは異なります。

上記の場合、購入手段が消費型なので自動更新を行うことができません。

そのため、一定期間経過後にコンテンツは無効となります。(再度購入は可能)

自動更新購読型の特徴

消費型の商品と違い一度購入すると規定の期間、所定のコンテンツが有効となります。

有効期間終了後に次の期間の購入が自動的に行われます。

自動更新はユーザーのアプリケーション使用有無に関わらず、ユーザーが手動で自動更新購読をキャンセルするまで自動更新され続けます(決済が完了できない場合などを除く)。

自動更新をキャンセルした場合でも規定の期間はアプリケーション側でコンテンツを有効にしておく必要があります。

iOS / Androidそれぞれ、設定できる期間が異なります。

自動更新購読の有効期間 Apple Google
1週間
1ヶ月
2ヶ月 ×
3ヶ月
6ヶ月
1年
シーズン × ◯※

※期間を設定し、その期間コンテンツを有効とさせることができる。設定した期間の冒頭で購入が行われます。

自動更新は翌シーズンの期間の冒頭で行われます。

テスト環境での自動更新購読

テスト環境(SandBox環境)で自動更新購読のテストを行う場合、実稼働環境の期間とは異なるため注意が必要です。

iOSの場合、自動更新購読は6回自動更新が行われると、自動的に更新がキャンセル状態になります。

テスト環境における自動更新の間隔

実稼働環境の有効期間 SandBox(Apple) Googleテストアカウント
1週間 3分 24時間
1ヶ月 5分 24時間
2ヶ月 10分
3ヶ月 15分 24時間
6ヶ月 30分 24時間
1年 1時間 24時間
シーズン 24時間

自動更新処理

アプリケーション側のサーバーでは、定期的(1日1回)にストアへ問い合わせを行い、購入が継続している場合にはアプリケーション側へ反映させる必要があります。

iOSは前回の自動更新購読購入時のレシートを使用して検証を行うことができます。

ストア側で自動更新されている場合、レシート検証のレスポンスのトランザクション情報に最新の自動更新購読の情報が追加されます。

① 以前の購入レシート情報で検証サーバーにレシートを送信

② 検証したレシート内のトランザクションで未処理のものを抽出

③ 未処理のもののうち、有効期限のいちばん長いものを使用して反映

Androidは自動更新購読購入時に発行されたpurchaseTokenはキャンセルされるまで変わらないため、サーバに保持している情報を使用して検証を行うことができます。

① 以前の購入レシート情報で検証サーバーへ問い合わせを行う ※検証のレスポンス情報はから最新の自動更新購読情報が得られます。

② ①で得られた自動更新購読を使用して反映

レシートの復元(リストア)

何らかの問題によって自動更新のアプリケーションサーバーへの反映ができていない場合に、ユーザーがアプリケーション内から自動更新購読をゲーム内に反映される機能です。

自動更新購読の機能をアプリケーションに実装する場合、Appleではリストア処理がないと審査時にリジェクトの対象となります。

基本的には自動更新処理と同様に前回の自動更新購読購入情報からサーバーへ問い合わせを行い、アプリケーション側へ反映させます。

補足

Googleの自動更新購読の仕様で有効期間終了前の場合、自動更新をキャンセルして再購入際にはユーザーへの追加請求がありません。

また、自動更新購読の自動更新をキャンセルして再購入する度に、Google Playのシステムの仕様として新しい購入として扱われ、新しいpurchaseTokenとorderIdが発行されます。

そのため、有効期間中に自動更新をキャンセルして再度購入を繰り返すことで複数のアプリケーションに自動更新購読を反映できるという不正行為できてしまいます。

2018年1月よりGoogle Play Billingにおける自動更新購読の不正対策のために、Google Play Developer APIに新たにlinkedPurchaseTokenのフィールドが追加されました。

linkedPurchaseTokenにはキャンセルした自動更新購読のpurchaseTokenがセットされます。

linkedPurchaseTokenを利用することで、不正行為を防ぐことができます。

※linkedPurchaseTokenは自動更新購読有効期間中に自動更新をキャンセルして再度購入した場合のみ付与されます。

参考:https://developers.google.com/android-publisher/api-ref/purchases/subscriptions#resource-representations

4.困ったこと

開発中のため、アプリケーションサーバー側でエラーになってしまった課金処理が完了状態にならずに溜まってしまい、正しくテストできない状態になることが多々あった。

未完了の課金処理を全て完了状態にできるデバッグ用の機能があると良いと思います。

iOSの場合、処理済みの自動更新購読の情報もトランザクション情報に残り続けます。

トランザクション情報から最新の情報を取得するようにしておかないと、正しく更新ができないので注意が必要です。

5.最後に

以前はゲームアプリで自動更新購読が使われることは少なかったですが、最近では活用しているゲームアプリも珍しくなくなってきました。

(数年前まではゲームで自動更新購読を使用しているとリジェクトされることもあったそうです)

提供するサービスの幅も広がりますので、ぜひ自動更新購読を活用してほしいです。

参考:

https://ameblo.jp/principia-ca/entry-12071724382.html

https://ameblo.jp/principia-ca/entry-12071725733.html