UnityIAP subscription 対応時の備忘メモ

はじめに

リトルチャンピオンズ では、定期購読型(subscription)課金実装にUnityIAPを使用しました。

今回はその実装を行った際の知見を紹介したいと思います。

subscription以外の実装例については前回の記事 を参照ください。

また、サーバー側の実装についてはこちらの記事 を参照ください。

対象環境

  • Unity 2017.2.1f1
  • UnityIAP 1.15.0

ドキュメント

定期購読型課金 (Auto-Renewing subscription)とは

こうしたサービスやコンテンツは所定の期間で定期購読することができ、満期になると自動的に更新されます。購読期間は、週単位、月単位、四半期単位、年単位など、App によってさまざまです。定期購読によっては、トライアルを割引料金や無料で提供しているものがあります。

定期購読型の仕様

ストアで設定できる項目

iOS Android
価格 価格テーブルから選択(tierと異なる) 税抜きで入力
期間 1週間/1ヶ月/2ヶ月/3ヶ月/6ヶ月/1年 1週間/1ヶ月/3ヶ月/6ヶ月/1年/”季節”
お試し

(1度のみ無料や割引)

期間

価格

都度払い/前払い/無料トライアル

N日無料(3以上)

価格

グループ 同グループ内の優先度設定

アップグレードやダウングレード、クロスグレードがある

なし

※ UnityIAPではお試しは未サポート

消費型と違うこと

  • レシート
    • 1度購入した商品はレシート内に残り続けます
  • リストア
    • 購入復元処理(リストア)
      • 購入期間内であれば復元できなければならなりません(端末移行など)
      • ゲーム内にリストアボタンを設置しなければなりません
  • 自動更新
    • 更新タイミングでプラットフォーム側が決済します
    • アプリ起動中は購入トランザクションが通知されます
      • 購入レシートからサーバからレシート検証すると更新分が含まれています
      • アプリ起動待たずサーバから反映可能です
  • 更新キャンセル
    • ユーザがAppStore/GooglePlayからいつでも購読をキャンセル可能です
    • キャンセルしても購読期間中は有効になります
    • googleはcancelAPIからこちらからキャンセルできるっぽいです(未検証)
  • レシート検証
    • iOSはレシート検証に秘密鍵が必要になります
  • 購入
    • 同一プロダクトは購入中は購入できません
    • 別グループのプロダクトは購入できます
    • グループ内の優先度が高いプロダクトを購入する際はアップグレードになります
      • 購入時、即時反映されます。以前購読中だったものは比例配分で返金されます
    • 優先度が低いプロダクトを購入する際はダウングレードになります
      • iOSは次回更新時に反映されます。それまでは購読中のものが引き続き有効になります
      • Androidは即時反映されます
    • 優先度が同じものの購入の場合はクロスグレードになります
      • 同じ期間の場合、即時反映されます。期間が違う場合は次回更新時に反映されます

実装

購入

購入動線の制御

リトルチャンピオンズでは1つしか定期購読型のプロダクトが存在しなかったため

アップグレード等を考慮した実装まではしませんでしたが、

購入中は、購入動線があってもユーザが混乱するだけなので

アプリ側の購読中判定で購入動線を制御しました。

購入処理自体は消費型と変わりません。(参考:前回の記事)

更新

更新タイミング(決済後の反映)

  1. サーババッチ更新
    • 1日1回、サーバで管理している購入時レシートを再度検証APIでプラットフォーム側に問い合わせ反映します
    • 主にこの契機で反映を考えていたためゲーム内の有効期限は実際の期限より24時間ほどバッファを設けてました
    • この契機で反映した場合、アプリ側から購入リクエストの際に反映済みのステータスコードを返却し裏でトランザクションを閉じています
  2. アプリ側からサーバに購入リクエスト
    • 決済時にアプリ起動中などで1よりも先に反映する場合
    • 通常の購入APIリクエストと同じくレシート情報付きでリクエスト
  3. ユーザがアプリ上から手動リストア
    • 稀なケースだが1, 2にも引っかからなかった場合

トランザクションのリストア

以下呼び出すと未処理のトランザクションがIStoreListener経由で流れ始めます

すべてのトランザクションが流れ終った後、引数のActionが呼ばれます

_appleExtensions.RestoreTransactions(result =>
{
    // リストア完了処理
});

検証環境(Sandbox)

Sandbox環境での自動購読型の注意点

  1. iOSは購読キャンセルができない
    • 普段はAppStoreから購読のキャンセルを行いますが課金テストアカウントではAppStoreに入れないためキャンセルできません
    • Androidは普通にGooglePlayからキャンセル可能
  2. 自動更新
    • 6回の自動更新でその後キャンセルされます
    • テストしていると購入後1回も更新されずキャンセルされているときがあります
      • レシート内のauto_renew_statusで更新されるか確認できます
  3. 更新期間短縮
    • iOS, Androidともに更新期間は短縮されてます
      • 1週間は3分, 1ヶ月は5分など
      • iOS, Androidでほぼ同じですが微妙に違います
      • (少し前まではAndroidは1日と更新テストが面倒でした…)

おまけ

レシート検証

レシート検証はよく使うので書いておきます

iOS

  • ${Receipt} : base64エンコードを施したレシートデータ
  • ${Secret} : App用共有シークレット(iTunesConnectから取得)
curl -H 'Content-Type:application/json' -i -X POST -d '{"receipt-data":"${Receipt}", "password":"${Secret}"}' https://sandbox.itunes.apple.com/verifyReceipt

Android

リフレッシュトークンを取得(1度のみ)

アクセストークンの取得

  • ${RefreshToken} : 1で取得したもの
  • ${ClientId} : Google API OAuth2のClientId
  • ${ClientSecret} : Google API OAuth2のSecret
curl --data "refresh_token=${RefreshToken}" --data "client_id=${ClientId}" --data "client_secret=${ClientSecret}" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token
------------------
{
 "access_token": "xxxxxxxxxxxx",
 "token_type": "Bearer",
 "expires_in": 3600
}

レシート検証

  • ${AppPackage} : アプリのパッケージ
  • ${StoreProductId} : Google管理の商品ID
  • ${Token} : base64化されたレシート文字列
  • ${AccessToken} : 上で取得したアクセストークン
curl https://www.googleapis.com/androidpublisher/v2/applications/${AppPackage}/purchases/subscriptions/${StoreProductId}/tokens/${Token}?access_token=${AccessToken}

リンク

CA EnginnerBlog(iOS)

CA EnginnerBlog(Android)

サブスクリプションのサーバサイド開発で得た知見

Google Developerアプリ内定期購入