1
/
5

【開発日誌 #15】マルチプラットフォームiOSアプリのレシート検証を経験しての学びとポイント【アプリ内課金】

Photo by Mike Walter on Unsplash

はじめに

コムデの奥村です。前回に引き続き、Appleのアプリ内課金について書いていきます!

【開発日誌 #13】iOSのサブスクリプション実装!バックエンドのタスクについて | COMMUDE 開発日誌
こんにちは!バックエンドエンジニア兼ディレクターを務めています、奥村と申します。iOSアプリ開発案件における、自動更新サブスクリプションのナレッジが溜まってきましたので書きます。 iOSアプリの課金形態は以下の4種類に分類されます。 消耗型と非消耗型は買い切り型のアイテム、自動更新サブスクリプションと非自動更新サブスクリプションは一定期間の機能開放となります。 App内課金 ...
https://www.wantedly.com/companies/commude/post_articles/377749

サブスクリプション課金のザックリとした流れを書きましたが、今回はレシート検証までの流れについてもう少し書き加えていきます。

また、マルチプラットフォームのアプリの開発の際に嵌った経験をもとにレシート検証のポイントを書いていきます。

レシート検証の流れ

簡単にレシート検証の流れを説明します!

購入

①まずはスマホ端末からAppleサーバへと購入の通知を行います。(送るのは製品情報、AppleID、購入情報等です)
②Appleからレシートが送られてきます。
③レシートを端末に保存します。(レシートはAppleIDと紐付いています)

レシート検証


肝となるレシート検証です。
レシートをAppleサーバへ送ることで、購入の詳細情報が返って来ます。

https://developer.apple.com/jp/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store/

一見、アプリからAppleサーバへ貰ったレシートを送り返せば良いように思えます。しかしこれは以下の理由から避けましょう。

警告
AppからApp StoreサーバのverifyReceipt(英語)エンドポイントを呼び出さないでください。接続のどちらの終端もコントロールすることができず、中間者攻撃を受けやすくなるため、ユーザーのデバイスとApp Storeとの間で信頼できる接続を直接構築することができません

要はレシートを改竄して不正に有料コンテンツを利用されかねないためです。
更新・キャンセル時のハンドリングのしやすさを考えても、必ずサーバ経由でレシートを検証しましょう。

では流れです。

④アプリからアプリサーバへレシートを送信します。この時、購入したユーザの認証情報と一緒に送ります。
⑤Appleサーバへレシートを送ります。
 エンドポイントはこちらを参照ください。(https://developer.apple.com/documentation/appstorereceipts/verifyreceipt)
⑥レシートの詳細情報が返ってきます。
⑦アプリサーバでレシートを検証します。④で送られたアプリからの認証情報を元にユーザとレシートの紐付けを行いましょう。
⑧検証結果をアプリへ返します。

前回の記事も開発の一助となれば幸いです。

【開発日誌 #13】iOSのサブスクリプション実装!バックエンドのタスクについて | COMMUDE 開発日誌
こんにちは!バックエンドエンジニア兼ディレクターを務めています、奥村と申します。iOSアプリ開発案件における、自動更新サブスクリプションのナレッジが溜まってきましたので書きます。 iOSアプリの課金形態は以下の4種類に分類されます。 消耗型と非消耗型は買い切り型のアイテム、自動更新サブスクリプションと非自動更新サブスクリプションは一定期間の機能開放となります。 App内課金 ...
https://www.wantedly.com/companies/commude/post_articles/377749

マルチプラットフォームアプリ開発を経験して

通常は1つのAppleIDにつき1つのレシートとなります。(同じproduct_idならば)
そのため、多くのアプリは起動すれば特に認証情報を入力する必要がなく有料コンテンツが利用できる状態となっています。
しかし、他所のWebサイトでアカウントを作成しアプリで使用でき、かつアプリ内でも課金ができるという場合、さらにプラットフォーム間でDBを別管理している場合は、レシートの管理のややこしさが爆発的に上がります。端的に言うとログインフォームを設けるようなアプリがこれに当てはまりまることが多いです。

このようなアプリを取り扱う時は、購入・更新・キャンセルのシナリオが多数あり、その全てを考慮した設計をしなければなりません。ユーザー情報を別DBで扱っている場合、購入・キャンセルの際には他のプラットフォームにもその通知を送る必要があり、iOSでの購入経路とキャンセル経路を把握する必要があります。

このとき大事になってくるのがレシート検証でのoriginal_transaction_idの取り扱いです。
original_transaction_idとは初回サブスクリプション購入のtransaction_idになります初回の購入はtransaction_idとoriginal_transaction_idが同一のものとなります。
自動更新サブスクリプションの場合はresponse.latest_receipt_info内の最新レシートのoriginal_transaction_idを参照するのが良いと思います。

Appleサーバ通知を受け取った際にユーザ情報を更新する

original_transaction_idの最も一般的な使われ方はこちらでしょう。

Appleサーバからレシートの更新データが送られて来た際、original_transaction_idからユーザーを特定します。上の図でいうと⑥から始まるイメージです。④のアプリからユーザー認証情報が送られくる工程が無いないので、Appleから送られてきたレシート情報がどのユーザのものなのか特定するためにoriginal_transaction_idを利用します。

たくさん購入履歴があれば、その数だけレシートが送られてきますが、最新のものを参照するようにしています。この場合はunified_receipt.Latest_receipt_infoから最新レシートのoriginal_transaction_idを取得しています。

Appleサーバ通知V1
https://developer.apple.com/documentation/appstoreservernotifications/responsebodyv1

複数ユーザでアプリ内課金をしようとした場合

同じApple IDで2つのアプリアカウントでの課金を試みた場合、阻止しなければいけません。仮に課金が成功した場合は重複課金となってしまいます。

レシート検証の際にoriginal_transaction_idと紐付いているユーザーを確認しましょう。課金を試みているユーザーと異なるユーザーが紐付いていた場合、エラーを返します。
以下はLaravelでのコード例です。

// Purchasesテーブルから検証レシートのoriginal_transaction_idと同一のものを取り出し、
// 自身と異なるユーザと紐付いていればエラーを返す。
$purchases = Purchase::where('original_transaction_id', $latest_receipt['original_transaction_id'])->get();
$other_user_purchases = $purchases->reject(function($purchase) use($user) {
    return $purchase->user_id == $user->id;
});

if ($other_user_purchases->isNotEmpty()) {
    return response()->json([
        'code' => 402,
        'message' => 'アプリで有料登録できるアカウントは1つのみです',
    , 402);
}

レシート検証のエンドポイントは作っておこう

基本的にレシート検証のタイミングとしては、アイテムを購入した時とAppleサーバ通知が来た時、あとはレシートの期限が切れる時となります。

それに加えて、レシートを受け取れるようなエンドポイントをアプリサーバに作っておきましょう。アプリ外での購入と予期せぬ不具合に備えるためです。
上図の②の段階で購入処理が止まってしまうというパターンになります。課金時にアプリサーバがダウンしていた時もこれに当てはまります。

見落としてしまいがちなポイントですが、実はアプリ外でも課金はできてしまいます
AppleIDにクレジットカード等の購入情報が紐付いていない状態だと、iOS標準の設定アプリに遷移し、そこで支払情報を入力するとそのまま課金処理が終了してしまいます。

AppleIDとユーザアカウントが1対1で紐付いているアプリならば、アプリ起動時に必ずレシートを確認すれば正常にレシート検証ができます。マルチプラットフォームのアプリだと、レシート検証のためにアプリ内のアイテム購入画面に遷移しないとレシート検証をしないような構成とすることが多いです。
この時、アプリ外で購入したユーザーは購入画面に自力でたどり着かないと購入が反映されないことになってしまいます。ユーザを不安にさせないためにも、アプリのどこかでレシート検証APIを叩く箇所を入れておくと良いと思います。

流れとしては、

  1. アプリからレシートをアプリサーバに送信
  2. Appleサーバに問い合わせてレシート検証
  3. original_transaction_idが一致するレシートがないか確認(など状況に応じた様々な処理)
  4. 一致するレシートが無ければ新規にユーザと紐付けて保存

となります。

さいごに

レシート検証の概要と意識すべき点が少しでも分かって頂けていたら幸いです。

Appleの仕様、iOSでの購入処理の仕様、DB設計、API設計、ポーリング、ユーザーの動線、等々...
考慮するべきことがたくさんあり、様々な知識を総動員して設計しないといけませんね。

大変ですが、やはりネイティブアプリはユーザビリティが高く、ユーザーにより多くの感動を与えられるのも事実です!

コムデでは上流設計を含めた濃密な開発経験を、入社してすぐ積むことができます。
興味を持った方はぜひお話ししましょう!

株式会社コムデでは一緒に働く仲間を募集しています
16 いいね!
16 いいね!
同じタグの記事
今週のランキング
株式会社コムデからお誘い
この話題に共感したら、メンバーと話してみませんか?