初めてのブログです!🌷
さて、突然ではありますが、
課金実装の大変さを実感したので、その経験を軽めの感じでまとめてみようと思います💪
※始めに...この記事ちょこっと長めなのと、課金のかなーり表面的な上澄のようなお話なので、その二点、あらかじめご了承ください🙇♀️
早速課金システムのお話を始めていこうと思います🙇♀️
まず、課金システムといっても大きく分けて下記の2パターンの実装方針があります。
- ストアフロント
- すでに完成している課金システムを組み込む
- 開発コストが抑えられる(システムの組み込みだけ)
- 指定されている決済方法以外使えない(仕様変更に対応しずらい)
- 通常の運用コスト(サーバーや人件費)にプラスでシステムの使用料がかかるため運用コストが上がる
- フルスクラッチ
- ゼロから全て自前実装
- 開発コストがかかる
- AppleやGoogleの仕様内であれば自由な設計ができるので、サービスの仕様変更にも対応しやすい
- 通常の運用コスト(サーバーや人件費)で抑えられる
今回はその視点から課金システムの実装の大まかなざっくり説明と、大変だったポイントをまとめてみます。
課金アイテムがあるスマホアプリを使ったことがある方はなんとなくイメージしやすいかもしれませんが、課金システムは大きく分けて2つの機能を実装する必要があります。
- 都度課金実装
- サブスクリプション実装
それぞれの概要を簡単に説明するとこんな感じになります。💰
【都度課金】
- 買い切りのアイテム
- お金がかかるのはその時のみ
【サブスクリプション】(以下サブスク)
- 定期購読
- 購入したサブスクに設定された期間毎に自動で更新・請求される
続いて、上記課金アイテムの主な処理は下記のようになります👇
【都度課金】
ユーザー購入時:
- ユーザーが課金アイテムを購入
- ストアで購入処理が完了
- レシート情報が受け取れる
- レシート情報を検証(正しくない情報の場合は処理しない)
- 該当のアイテム等をユーザーに付与する
【サブスク】
ユーザー購入時:
- ユーザーが課金アイテムを購入
- ストアで購入処理が完了
- レシート情報が受け取れる
- レシート情報を検証(正しくない情報の場合は処理しない)
- 該当のサブスク情報のステータスをユーザーに付与する
更新時:
- ストア側でユーザーの購入処理が完了
- webhookから更新したレシート情報が送られてくる
- 受け取ったレシート情報を検証する(正しくない場合は処理しない)
- レシート情報から該当のユーザー情報を取得する
- レシート情報をチェックして、正しいステータスに更新する
- この時に解約している場合もある
定期バッチ更新:
- 更新漏れがないかチェック
- 課金ユーザーの最新のレシート情報を取得
- レシート情報を検証
- 受け取ったレシート情報を元にユーザーのステータスと照合
- 正しくないステータスのユーザーがいた場合は正しいステータスに更新する
この説明から分かるように、サブスクの実装は単純に処理することが多いのと、ユーザーのステータスを保持しておかないといけないのでその点も実装上でややこしくなってくるポイントになります。
都度課金の実装については、おおよそシンプルなものになるので、今回は省略して、これ以降の「課金システム」というのは主にサブスクについてのまとめになります。
ます、サブスクはその仕様によって難易度が天と地ほどの差が出てきます。
サブスクの仕様について、パッと思いつく限りのだと
- 1つのプランのみのパターン
- (例:スタンダードプランなど)
- 2つ以上のプランで、グレード(金額)が異なるパターン
- (例:スタンダードプラン、プレミアムプランなど)
- 2つ以上のプランで、グレードは同じだが期間が異なるパターン
- (例:1ヶ月、3ヶ月、6ヶ月、のプランがあるやつ)
難易度的には、体感「1<<<<2<3」みたいな感じかなと🤔
アップグレードやダウングレードなどのプラン変更の概念が絡んでくると一気に実装が難しくなる印象です。
ここから少しだけ実装面のお話になります。(軽ーくなので、詳細は省きます🙇♀️)
【おすすめのサーバー構成】
サーバーの構成は実装が少しだけ楽になるものを記載してます!
※AWSを使うと想定した場合で文言使います
- APIサーバー
- バッチサーバー
- webhookサーバー
- DB
- メイン処理サーバー (SQSとLambdaの連携)
【購入処理】
このサーバー構成がおすすめの理由としては、
- SQSとLambdaを使うとDBの処理をまとめられる!
- 仕様変更などにも対応しやすい
- コード量が少なくなる
- デッドロックを回避できる
となります。
なので、SQSなどのメッセージ処理のサービスを使って、Lambdaなどで処理をまとめてしまうのが圧倒的に楽(なはず)です!
ここで、私が実装した時のストアから受け取れる、レシート情報とwebhookの通知を少しだけ簡単にまとめます🍋(レシート情報は変更が入る可能性が高いので最新の情報は公式HPを参考にしてくだいさい!)
【レシート情報】
- linkedPurchaseToken(一連の購入処理で一意、解約すると変わる)
- purchaseToken(購入ごとに一意。購入ごとに変わる)
- startTimeMillis(購読開始日時。サブスク最初に購入した日時)
- expiryTimeMillis(購読の有効期限。次の更新タイミング)
Apple:
- originalTransactionId(ユーザーごとに一意、アップルアカウントに紐づくので解約しても変わらない)
- transactionId(購入ごとに一意。購入ごとに変わる)
- original_purchase_date(サブスクを最初に購入した日時)
- purchase_date(購読購入/更新日時)
- expires_date(購読の有効期限。次の更新タイミング)
お店毎にレシートの書き方が違うように、課金システムで各ストアから受け取れるレシート情報もGoogleとAppleでレシートの内容が違うのがわかるかと思います....
ユーザーごとのデータも持ち方が変わってくるので、その管理方法のテーブル設計もよく考える必要がありそうです。
次に、サブスクの更新時にwebhookから送られてくる通知情報を軽く説明します。
【webhookの通知情報】
Google:
- (1)SUBSCRIPTION_RECOVERED
- 定期購入がアカウントの一時停止から復帰した。
- (2)SUBSCRIPTION_RENEWED
- サブスクの定期更新時に来る通知。
- 一番良く来る通知の種類。
- アクティブな定期購入が更新された。
- (3)SUBSCRIPTION_CANCELED
- サブスクがユーザーまたはシステム側でキャンセルされた。
- 自発的なキャンセルの場合、ユーザーがキャンセルしたときに送信されます。
- (4)SUBSCRIPTION_PURCHASED
- 新しいサブスクが購入された。
- サブスク解約後、期限が切れてからの再購入もこの通知。
- (5)SUBSCRIPTION_ON_HOLD
- アカウントの一時停止を許可している場合にのみ来る通知。
- サブスクでアカウントが一時停止された。
- (6)SUBSCRIPTION_IN_GRACE_PERIOD
- 猶予期間の設定をしている場合にのみ来る通知。
- サブスクが猶予期間に入った。
- (7)SUBSCRIPTION_RESTARTED
- ユーザーが [Play] > [アカウント] > [定期購入] からサブスクを再開した。
- サブスク解約後、ユーザーが再開した時点でまだ期限切れになっていない場合に来る通知
- (8)SUBSCRIPTION_PRICE_CHANGE_CONFIRMED
- サブスクの料金改訂時に対象ユーザーに通知をした場合に来る可能性がある通知。
- サブスクの料金変更がユーザーによって確認された。
- (9)SUBSCRIPTION_DEFERRED
- サブスクの契約期間が延長された。
- (10)SUBSCRIPTION_PAUSED
- サブスクが一時停止された。
- (11)SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED
- サブスクの一時停止スケジュールが変更された。
- (12)SUBSCRIPTION_REVOKED
- 有効期限前にユーザーがサブスクを取り消した。
- (13)SUBSCRIPTION_EXPIRED
- ユーザーが解約した後、期限切れになったタイミングで来る通知。
- プラン変更でアップグレードをした時に、前のプランがサブスク有効期限切れとしてこの通知も来る。
- サブスクが期限切れになった。
- (20)SUBSCRIPTION_PENDING_PURCHASE_CANCELED
- 保留中の取引 は解約されました。
Apple:
- INITIAL_BUY
- サブスクの初回購入時に来る通知。
- 同じアップルアカウントでは1度しか来ない通知。
- CANCEL
- Appleカスタマーサポートによって返金などの理由で解約されたとき
- または別のサブスクへアップグレードしたとき
- ユーザーが手動で購読の自動更新を停止した場合は来ない。
- RENEWAL
- 過去に更新に失敗した期限切れのサブスの自動更新が成功したとき
- deprecateなので、今は来ない通知のはず。
- INTERACTIVE_RENEWAL
- ユーザーがアプリ内、またはApp Storeからサブスクを更新したとき
- DID_CHANGE_RENEWAL_STATUS
- サブスクの更新ステータスが変更されたとき
- 解約したときにも通知される
- プラン変更の場合はこの通知が来る。
- DID_CHANGE_RENEWAL_PREF
- ユーザーがサブスクリプションプランを変更したとき
- DID_FAIL_TO_RENEW
- 購読中のサブスクの更新時に、決済の問題で更新に失敗したとき
- DID_RECOVER
- 更新に失敗した期限切れのサブスクの自動更新が成功したとき
- PRICE_INCREASE_CONSENT
- サブスクリプションの価格が値上げされるとき
- この通知の際に受け取れるレシート情報で、ユーザーが値上げに同意したかどうかが分かる
- DID_RENEW
- サブスクの定期更新時に来る通知。
- 一番良く来る通知の種類。
- サブスクリプションの自動更新が成功したとき
- REFUND
- App Storeからユーザーへの払い戻しがあった時に来る通知
- sandboxではテスト不可
通知の種類をざっと簡単に書きましたが、googleとappleそれぞれで10個以上の通知の種類があり、それに対応した実装とテストが必要となるため、これも大変な部分になります...😭
では実装面のお話はこれくらいにして、ここから話を少し変えて、実装を経験して大変だったポイントをまとめていこうと思います!
【課金実装での大変なポイント】
- 各ストアで仕様が異なる
- 先ほどのレシート情報の通り、レシートの情報が各ストアで違うので、OSごとにユーザーのデータの持ち方が異なってきます
- 金額設定の最低金額が違う
- Googleは99円
- Appleは160円(2022年に120円から値上げ)
- つまり、iosもandroidも対応する場合は最低金額は160円以上に設定する必要があります
- sandboxが想定通りに動かない
- 同じアカウントを使い回すと、想定通りに動かないことがあリマス
- 払い戻しや支払い失敗などのマイナー系のテストがsandboxではできないです
- テスト用アカウントは基本使い捨て(最新の情報は不明)
- テスト用のアカウントを使い回すとなぜか新規扱いになったり、すでに課金アイテムを購入したことがあるユーザー判定されてしまったりと安定しないことが多かったです
- appleでは、同端末で違うappleアカウントを使っていても、同一ユーザーとみなされてしまうことがあったので、iphoneでのテストは1回テストをしたら一定時間おいてから次のテストをしてました...
- 以前はgoogleアカウントをいくつも作成することができたが、今は1端末1アカウントになっているようなので、この辺りの解決方法はまたそのうち考えようと思います
- googleの残金充当期間についての理解
- サブスクでアップグレードをした際に、前のサブスクのグレードの残金分(アップグレードしなかったら継続されていた期間分)をアップグレードしたプランに充てて、プランを切り替えるもの
- Appleにはない概念(appleはアップグレードしたら残金分は払い戻される)
- そもそも両ストアともの仕様で、アップグレードしたら即時アップグレードプランへの切り替えをしなければならないです
- ※残金充当期間についてはややこしいので、機会があれば別の記事で詳しく説明しようと思います!!!
大変なポイントはまだまだありますが、大きなものであげるとこんな感じでした。
以上が、私が課金システムの実装をして得た知識(ほんの一部)と大変だったポイントのお話でした☺️
今回はフルスクラッチ実装のお話でしたが、フルスクラッチは単純に時間と労力がかかるものなので、既存の課金システムのサービス(もちろん有料)を使える場合は、そちらを使うことも検討してみるといいかもしれません。
想像の倍はかかると思っていてもらってもいいかもしれません😵
テストも含めて、半年〜1年くらいは実装期間見ておくと少し余裕が持てるかもと思いましたが、サブスクの仕様にもよるので一概には言えないところでもあります。
appleだと、レシート情報のバージョンがv1→v2にアップデートされて、v1で使っていたレシート情報や通知が非推奨になっていたり使えなかったりすることが多々あります。
ただ、課金はかなり奥が深く、ユーザーが使う機能に直結する部分なので、とてもやりがいは感じますし、私自身は課金システムにかなり引き込まれてしまいました☺️
また機会があれば課金システムの実装に携わりたいと思いますし、もっと知識を深めていきたいなと思っています!
最後になりますが、ここまで読んでいただいてありがとうございました!🌷
0 件のコメント:
コメントを投稿