こんにちは。NewsPicksエンジニアの takaaki.saito です。
所属しているGrowthチームでは、サービスのGrowth施策を技術面から支えるお仕事をしています。
今回はiOSアプリにオファーコード機能を実装したときのことを振り返り、実装を進める上で工夫したことやハマりどころ等をご紹介します。
実装した当時は、公式ドキュメント以外の情報が少なく、本筋とは別のところでつまづくことがあり苦労しました。本実装を通じて得た知見が、少しでもお役に立てば幸いです!
そもそもオファーコード機能とは
オファーコード機能は、iOSアプリで利用可能なクーポン機能です。
一回限り使用可能な英数字コード(オファーコード)を、AppStoreまたはアプリ内で使用することにより、ユーザは一定期間、無料、または割引価格で有料プランの購読が可能となります。
Appleのアプリ内課金では、もともとプロモーションオファーという別の機能がありますが、これと比較すると、新規ユーザに対してもオファーの提示が可能となったり、メールやチャットツールを通じてコードの配信(オファーの提示)が可能となったりと、ユーザへの訴求面において、オファーコードは汎用性がより高いものとなっています。
※ 比較の詳細は下記URLをご参照ください
https://developer.apple.com/jp/app-store/subscriptions/#offer-types
オファーコードを使用した購読処理の全体フロー
前段で少し触れましたが、オファーコードを使用した購読には2つの方法があります。
- アプリ内でコードの入力・引換えを行う方法
- コード引換えURLを利用して、アプリを経由せず、AppStoreにて直接コード引換えを行う方法
各パターンの全体フローは下記のようになります。
アプリ内引換えパターン
コード引換え画面では、ユーザがコードを手入力する必要があります。
コード引換えが完了した時点では、NewsPicksサーバの購読情報(DB)が更新されていないため、アプリ上はオファーが適用されていない状態です。コード引換えの結果をアプリへ反映させるためには、購読画面において購読情報をリストア(端末に保持されているレシートをもとに購読状態を復元する処理)する必要があります。
アプリ非経由パターン
コードをAppStoreConnectのURLにパラメータとして付与することによって、1回限りのコード引換えURLが作成可能です。
こちらのURLにアクセスするとAppStoreアプリが起動し、そのままAppStoreアプリ内でコード引換えを行います。
アプリ内引換えパターンと比較すると、ユーザ視点ではコードの手入力を省ける利点があります。
※コード引換えURLは下記のような形式をとります
https://apps.apple.com/redeem?ctx=offercodes&id=YourAppAppleID&code=OfferCode
必要な実装
それでは実際に実装していきます。
オファーコード機能を実現するためには、サーバサイド、およびアプリの実装が必要です。
それぞれコード例を交えて説明していきます。
サーバ
offer_code_ref_name
その購読がオファーコードによるものかを判別するためには、レシート検証のレスポンスに含まれる offer_code_ref_name
を参照します。 offer_code_ref_nameには、その購読に適用されたオファー名が設定されます(オファーコード不使用の場合は空になります)。
offer_code_ref_nameについて、サーバ側では下記の対応が必要です。
- レシート検証API (アプリからサーバへレシート検証結果の問い合わせを行うAPI) のレスポンスにoffer_code_ref_nameの追加
- 課金レシートを管理するテーブルに、offer_code_ref_nameカラムの追加
アプリ
コード引換え画面の表示
こちらの実装は非常に単純です。
任意の場所で presentCodeRedemptionSheet
を呼び出すだけで事足ります。
オファーコードはiOS 14.0以降で動作するので、アプリのサポートバージョンが14.0を下回る場合は、下記のように available
を使用して要求バージョンを指定する必要があります。
余談になりますが、プロモーションオファーを実装したときは画面(StoryboardやVC)やそこで必要なAPIを自前で作る必要がありましたが、オファーコードは関連ロジック(画面含む)がすべてStoreKitに内蔵されているため、実装が非常に簡略化されました。
import StoreKit ・ ・ ( 省略 ) ・ ・ if #available(iOS 14.0, *) { SKPaymentQueue.default().presentCodeRedemptionSheet() }
オファーコードによる購読を検知する
購読アクションの検知は、基本的なアプリ内課金と同様に SKPaymentTransactionObserver
を利用します。
今回は購読を検知したタイミングでレシート検証を実行するようにして、その購読がオファーコードを使用したものだった場合は、コード引換え完了ダイアログ(購読画面への導線)を表示するようにしています。
import StoreKit final class OfferCodePaymentTransactionObserver: NSObject, SKPaymentTransactionObserver { ・ ・ ( 省略 ) ・ ・ func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { for transaction in transactions { switch transaction.transactionState { case .purchased: // 購読トランザクションを完了 queue.finishTransaction(transaction) // レシート検証 loadAppStoreReceipt() case .failed: queue.finishTransaction(transaction) default: break } } } private func loadAppStoreReceipt() { // レシート検証APIをコールする verifyReceiptApi( success: { receipt in // offerCodeRefNameの存在チェック guard let receipt = receipt, receipt.status == .active, let _ = receipt.offerCodeRefName else { return } // コード引換え完了ダイアログを表示 showOfferCodeSubscriptionCompleteDialog() }, failure: { _ in // do nothing } ) } }
初回起動時にレシート検証を実行する
アプリ非経由パターンでは、アプリ外でコード引換えが完了するため、アプリを起動したタイミングで、最新の購読状態をレシート検証の結果から取得する必要があります。レシート検証は結構重いので、サーバへの負荷増大を避けるため、初回起動時のみ実行するように工夫しています。
class FirstLaunchViewController: UIViewController { ・ ・ ( 省略 ) ・ ・ override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) showLaunchDialog() } private func showLaunchDialog() { ・ ・ ( 省略 ) ・ ・ // 初回起動時のみ実行 if needsShowSubscriptionRestorableDialog() { // userDefaultsのダイアログ表示フラグを更新 disableShowSubscriptionRestorableDialog() // レシート検証 loadAppStoreReceipt() } } private func loadAppStoreReceipt() { // レシート検証APIをコールする verifyReceiptApi( success: { receipt in // 購読中の課金レシートが存在するか判定 guard let receipt = receipt, receipt.status == .active else { return } // リストア促しダイアログを表示 showSubscriptionRestorableDialog() }, failure: { _ in // do nothing } ) } }
ハマりどころ
Sandbox環境で動作確認できない問題
Appleのアプリ内課金では、開発用にSandboxアカウントを利用できます。
オファーコードの開発で最も苦労したのは、このSandboxを利用して動作確認ができない点です。
Sandboxが利用可能な検証用アプリでコード引換えを実行しようとすると、本番アプリのインストールを要求されてしまいます (常に本番に向いてしまうみたいです)。
※下記は当該事象に関連するdeveloper forumのスレッドになります。2021年8月現在においては、まだ未対応のようです。
developer.apple.com
実際には、全体を通しての動作確認は本番環境を使って行いました。
実装による影響範囲が限定的だったというのもありますが、今思い返してもヒヤヒヤしますね。
おわりに
オファーコードは実装自体は比較的シンプルですが、動作確認が困難という特徴がありました。
オファーコード機能の導入を検討される方に、この記事が少しでもお役に立てば幸いです。