StoreKit 2 API の新機能と、Apple がアプリ内購入の統合を効率化した方法
Updated: 10月 14, 2023
4 min read
Appleは、最近開催されたWWDC 2021で StoreKit 2の新しいバージョンを紹介しました。これは、iOSでの購入を管理するフレームワークです。アプリ内購入 (in-app purchase) とサブスクリプション (subscription) 機能に対応するアプリのシェアは着実に伸びています。AppleはStoreKit 2をリリースすることで、アプリ内購入のアプリへの統合を大幅に簡素化しました。これを受けて、当社はApp StoreサーバAPIを活用し、サーバーサイド (server-side) でStoreKit 2を導入することを検討しています。
認証のリクエスト
現在のAPIバージョンでは、リクエストを送信するために共有シークレット (shared secret) が必要です。これは、App Store Connectで取得できる秘密の固定文字列です。新しいバージョンのAPIでは、リクエスト認証にJSON Web Token (JWT) 標準を使用します。
キーの生成
まず、リクエストの承認に使用する秘密鍵を作成します。App Store Connectを開き、[ユーザーとアクセス] セクションに移動してから、[キー] タブに移動します。アプリ内購入キーの種類を選択します。新しいキーをダウンロードします。そのIDも必要になるため、App Store Connect APIタブの発行者IDと同じページにコピーできます。
トークンの作成
次の手順として、リクエストの承認に使用されるトークンを作成します。このプロセスはドキュメントで詳しく説明されているため、それほど注意する必要はありません。 Pythonの既存の実装の例を次に示します。新しいリクエストごとに新しいトークンを生成しても意味がないため、ご注意ください。トークンを作成するときは、有効期間 (lifetime) を最大60分に設定します。その期間中は同じトークンを使用します。
import time, uuid
from authlib.jose import jwt
BUNDLE_ID = 'com.adapty.sample_app'
ISSUER_ID = '4336a124-f214-4d40-883b-6db275b5e4aa'
KEY_ID = 'J65UYBDA74'
PRIVATE_KEY = '''
-----BEGIN PRIVATE KEY-----
MIGTAgMGByqGSMBHkAQQgR/fR+3Lkg4...
-----END PRIVATE KEY-----
'''
issue_time = round(time.time())
expiration_time = issue_time + 60 * 60 # 1 hour expiration
header = {
'alg': 'ES256',
'kid': KEY_ID,
'typ': 'JWT'
}
payload = {
'iss': ISSUER_ID,
'iat': issue_time,
'exp': expiration_time,
'aud': 'appstoreconnect-v1',
'nonce': str(uuid.uuid4()),
'bid': BUNDLE_ID
}
token_encoded = jwt.encode(header, payload, PRIVATE_KEY)
token_decoded = token_encoded.decode()
authorization_header = {
'Authorization': f'Bearer {token_decoded}'
}
署名済みのトランザクション
新しいバージョンのAPIでは、すべてのトランザクションが JSON Web Signature (JWS) 標準で返されます。これは、ドットで区切られた3つの部分からなる文字列です。
1. Base64ヘッダー
2. Base64トランザクションのペイロード
3. トランザクションの署名
Base64(header) + "." + Base64(payload) + "." + sign(Base64(header) + "." + Base64(payload))
トランザクションのヘッダー
正規のトランザクションであることを確認するには、ヘッダーが必要です。 algキーには暗号化アルゴリズムが含まれ、x5cキーには証明書チェーンが含まれます。
{
"kid": "AMP/DEV",
"alg": "ES256",
"x5c": [
"MIIEO...",
"MIIDK..."
]
}
トランザクションのペイロード
{
"transactionId": "1000000831360853",
"originalTransactionId": "1000000806937552",
"webOrderLineItemId": "1000000063561721",
"bundleId": "com.adapty.sample_app",
"productId": "basic_subscription_1_month",
"subscriptionGroupIdentifier": "27636320",
"purchaseDate": 1624446341000,
"originalPurchaseDate": 1619686337000,
"expiresDate": 1624446641000,
"quantity": 1,
"type": "Auto-Renewable Subscription",
"appAccountToken": "fd12746f-2d3a-46c8-bff8-55b75ed06aca",
"inAppOwnershipType": "PURCHASED",
"signedDate": 1624446484882,
"offerType": 2,
"offerIdentifier": "basic_subscription_1_month.pay_as_you_go.3_months"
}
Appleはトランザクション形式を変更して拡張しました。今ではスムーズに作業できるようになりました。新しい形式の詳細については、ドキュメントを参照してください。最も重要な変更点については以下に説明します。
● Appleは、システムのユーザーIDを含むappAccountTokenフィールドを追加しました。この IDはUUID形式である必要があり、購入の初期化時にモバイルアプリで設定されます。設定されている場合、このチェーンのすべてのトランザクション (更新、請求の問題など) で返され、どのユーザーが購入したかを把握しやすくなります。
● Appleは、使用されたオファー (存在する場合) に関する情報を含むofferTypeおよびofferIdentifierフィールドも追加しました。 offerTypeフィールドの値は次のとおりです。
● 1 — 紹介オファー (intro offer) (アクティブなサブスクリプションがないか、サブスクリプションの有効期限が切れたユーザーのみが利用可能)
● 2 — プロモーションオファー (promo offer) (現在および期限切れのサブスクリプションでのみ利用可能)
● 3 — オファーコード
プロモーションオファーまたはオファーコードが使用された場合、offerIdentifierキーには使用されたオファーのIDが含まれます。これまでは、サーバーサイドでオファーの使用を追跡することができなかったため、分析の精度が低下していました。今では、分析でオファーコードを使用できるようになりました。
● AppleはinAppOwnershipTypeフィールドに追加しました。これは、ユーザーがプロダクトを購入したか、ファミリーサブスクリプションでプロダクトにアクセスしたかを確認するのに役立ちます。想定される値は次のとおりです。
● PURCHASED
● FAMILY_SHARED
● もう1つの新しいtypeフィールドには、トランザクションのタイプが含まれます。想定される値は次のとおりです。
● Auto-Renewable Subscription
● Non-Consumable
● Consumable
● Non-Renewing Subscription
● cancellation_dateおよびcancel_reasonフィールドには、revocationDateおよび revocationReasonという新しい名前が付けられました。返金の結果としてサブスクリプションが取り消された日付と理由が含まれているため、わかりやすい名前になりました。
● すべてのキーはcamelCase形式で返されます (すべての App StoreサーバAPIリクエストと同様)。
● すべての日付はミリ秒単位のUnixタイムスタンプ形式で表示されます。
ユーザーのサブスクリプションのステータス
現在のユーザーのサブスクリプションのステータスを確認するには、GETリクエストを https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId} に送信します。{originalTransactionId} は、ユーザーの任意のトランザクションチェーンのIDです。これにより、サブスクリプションのすべてのグループのステータスを含むトランザクションを取得します。
{
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"data": [
{
"subscriptionGroupIdentifier": "39636320",
"lastTransactions": [
{
"originalTransactionId": "1000000819078552",
"status": 2,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
]
}
]
}
ステータスキーには、現在のサブスクリプションのステータスが表示されます。これに基づいて、アプリの有料機能へのアクセス権をユーザーに付与する必要があるかどうかを判断できます。想定される値は次のとおりです。
● 1 — サブスクリプションがアクティブです。ユーザーは有料機能を利用できる必要があります。
● 2 — サブスクリプションの有効期限が切れています。ユーザーは有料機能を利用できません。
● 3 — サブスクリプションのステータスは「Billing Retry」です。これは、ユーザーがサブスクリプションをキャンセルしていないものの、支払いで問題が発生したことを意味します。 Appleはカードへの請求を60日間試みます。ユーザーは有料機能を利用できないものとします。
● 4 — サブスクリプションのステータスは「Grace Period」です。これは、ユーザーがサブスクリプションをキャンセルしたわけではなく、支払いの問題が発生したことを意味します。 App Store Connectでは猶予期間が有効になっているため、ユーザーは有料機能を利用できるものとします。
● 5 — 返金の結果として、サブスクリプションがキャンセルされました。ユーザーは有料機能を利用できません。
SignedTransactionInfoキーには、チェーン内の最新のトランザクションに関する情報が含まれています。上記の形式について詳細を確認できます。
サブスクリプションの更新 (subscription renewal) に関する情報
SignedRenewalInfoキーには、サブスクリプションの更新に関する情報が含まれています。
{
"expirationIntent": 1,
"originalTransactionId": "1000000819078552",
"autoRenewProductId": "basic_subscription_1_month",
"productId": "basic_subscription_1_month",
"autoRenewStatus": 0,
"isInBillingRetryPeriod": false,
"signedDate": 1624520884048
}
この情報により、次の支払い期間中にサブスクリプションがどうなるかを把握できます。たとえば、ユーザーが自動更新をキャンセルしたことが判明した場合は、別のサブスクリプションプランに切り替えることを提案したり、プロモーションオファーを提示したりできます。このようなイベントは、サーバー通知 (server notifications) を使用して追跡すると便利です。これについては、追って説明します。
ユーザーのトランザクション履歴
ユーザーのトランザクション履歴を取得するには、https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId} にGETリクエストを送信します。{originalTransactionId} は、ユーザーの一連のトランザクションのID です。これにより、時間別に並び替えたトランザクションの配列を取得します。
{
"revision": "1625872984000_1000000212854038",
"bundleId": "com.adapty.sample_app",
"environment": "Sandbox",
"hasMore": true,
"signedTransactions": [
"eyJraWQiOiJ...",
"joiRVMyNeyX...",
"5MnkvOTlOZl...",
...
]
}
1つのリクエストに含めることができるトランザクションは、最大20件です。それ以上ある場合、hasMoreフラグの値は「true」になります。次のトランザクションページが必要な場合は、revision GETパラメーターを含むリクエストを再度送信します。これには、同じキーの値が含まれます。
サーバーのトランザクション通知
サーバー通知は、新規購入、更新、請求の問題などに関する情報を取得するのに役立ちます。さらに正確な分析を構築して、定期購入ユーザーのステータスを管理しやすくなります。
既存のサーバー通知 (V1) では大部分の問題を解決できますが、不便なこともあります。そのほとんどが、ユーザーの1つのアクションについて複数の通知が届くような場合です。たとえば、ユーザーがサブスクリプションをアップグレードすると、AppleからDID_CHANGE_RENEWAL_STATUSとINTERACTIVE_RENEWALの2件の通知が送信されます。このケースを処理するには、何らかの方法でステータスを保存して、2番目の通知が送信されたかどうかを確認する必要があります。新しいバージョンのサーバー通知 (V2) では、ユーザーの1つのアクションについて1件の通知しか届きません。はるかに便利になりました。
サーバー通知の2番目のバージョンには、OFFER_REDEEMED、EXPIRED、 GRACE_PERIOD_EXPIREDという新しいイベントが含まれています。これにより、定期購入ユーザーのステータス管理がはるかに簡単になります。SUBSCRIBEDおよびPRICE_INCREASEイベントは、最初のバージョンから改善されたイベントです。
通知のタイプ
通知には種類があるため、ユーザーのアクションに関する1件の通知で、何が起こったかを十分に理解できます。
通知のタイプ
{
"notificationType": "SUBSCRIBED",
"subtype": "INITIAL_BUY",
"version": 2,
"data": {
"environment": "Sandbox",
"bundleId": "com.adapty.sample_app",
"appAppleId": 739104078,
"bundleVersion": 1,
"signedTransactionInfo": "eyJraWQiOi...",
"signedRenewalInfo": "eyJraWQiOi..."
}
}
サーバー通知には、前述したJWS形式で、トランザクションと更新に関する情報が含まれています。
サンドボックス環境での作業
購入をテストするには、サンドボックス (Sandbox) 環境のURL (https://api.storekit-sandbox.itunes.apple.com) を使用する必要があります。
サーバー通知の新しいバージョンは、まだテストでは利用できません。利用可能になると、本番環境の通知とサンドボックス環境の通知に異なるURLを指定できるようになります。テストの際には、サンドボックス用にV2、本番用にV1を選択できます。
また、App Store Connectでは次のことができるようになりました。
● サンドボックスユーザーの購入履歴を消去します。つまり、このために新しいアカウントを作成する必要がなくなりました。
● サンドボックスユーザーのストアの国を変更します。
● サンドボックスサブスクリプションの更新期間を変更します。たとえば、毎月の購入を5分ではなく1時間にすることができます。
まとめ
Appleは、サーバーサイドのアプリ内購入とサブスクリプションの操作を大幅に改善しました。個人的に便利だと感じた新機能は、次のとおりです。
● プロモーションオファーとオファーコードの本格的なサポート
● シンプルでわかりやすいサーバー通知
● 簡単なAPI呼び出しで、現在のサブスクリプションのステータスを確認できる
● ユーザーのサンドボックスの購入履歴を消去する
新しいAPIへの切り替えは難しくありません。レシートごとにoriginalTransactionIdを取得するだけで十分です。すでにお使いのデータベースに含まれている場合があります。
いずれにせよ、サブスクリプションをモバイルアプリに統合する上で最も難しい部分は、分析システムを構築して、コストを最適化することです。Adaptyなら、これらの問題を効果的に解決できます。
● 組み込みの分析機能により、メインのアプリの指標をすばやく理解できます。
● コホート分析 (Cohort analysis) は、コスト面で問題があるかどうかを理解するのに役立ちます。
● A/Bテスト (A/B test) により、アプリの収益性が向上します。
● 外部システムとの統合により、トランザクションをアトリビューションおよびプロダクト分析サービスに送信できます。
● プロモーションキャンペーンでオーディエンスを失う事態を減らします。
● オープンソースSDKにより、サブスクリプションをアプリに数時間で統合できます。
これらの機能の詳細を確認して、サブスクリプションをアプリにすばやく実装し、早い段階でアプリを収益化しましょう。