ハイライト
App Store Server API は取引履歴、サブスクリプション状態、返金情報の能動的な照会に対応します。Server Notifications V2 は 20 種類以上のイベントタイプを網羅し、Family Sharing 対応とサンドボックス環境の改善も追加されています。
主要内容
あなたの App にアプリ内課金機能があるとします。ユーザーが購入した後、クライアントは receipt をサーバーへ送り検証します。ただし、ユーザーがオフラインで購入したり、クライアントリクエストが失敗したりすることがあります。サーバー上の購入記録が完全であることをどう保証すればよいでしょうか。
App Store Server API と Server Notifications V2 を使うと、クライアントからの報告を受動的に待つのではなく、データを能動的に取得できます。
詳細
App Store Server API
(01:14)
Server API は RESTful インターフェイスの集合で、サーバーは JWT で認証してから呼び出せます。
取引履歴を照会する:
GET https://api.storekit.itunes.apple.com/inApps/v1/history/{originalTransactionId}
ユーザーの完全な取引履歴を返します。すべての更新、アップグレード、ダウングレード、返金が含まれます。
サブスクリプション状態を照会する:
GET https://api.storekit.itunes.apple.com/inApps/v1/subscriptions/{originalTransactionId}
現在のサブスクリプションの詳細状態を返します。有効期限、自動更新状態、オファー種別などが含まれます。
(02:30)
キーポイント:
transactionIdではなくoriginalTransactionIdで照会しますoriginalTransactionIdはサブスクリプションのライフサイクル全体で変わりません- JWS(JSON Web Signature)形式の署名済みデータを返します
- データが Apple 由来であることを確認するため、署名検証が必要です
Server Notifications V2
(04:00)
V2 の通知タイプは V1 の 10 種類から 20 種類以上に拡張され、サブスクリプションのライフサイクルにおける主要イベントをすべてカバーします。
| 通知タイプ | 説明 |
|---|---|
SUBSCRIBED | 新規サブスクリプション |
RENEWAL | 自動更新の成功 |
DID_CHANGE_RENEWAL_STATUS | ユーザーが自動更新をオンまたはオフにした |
DID_CHANGE_RENEWAL_PREF | ユーザーがサブスクリプションをアップグレードまたはダウングレードした |
EXPIRED | サブスクリプションの期限切れ |
REFUND | 返金 |
REFUND_DECLINED | 返金の却下 |
CONSUMPTION_REQUEST | 消費データのリクエスト |
REVOKE | Family Sharing の取り消し |
(05:15)
キーポイント:
- V2 通知にはより豊富な情報が含まれます
- すべての通知は JWS 形式です
- 署名検証が必要です
- 200 以外を返した場合、Apple は数日以内に再試行します
JWS 署名を検証する
(06:30)
Server API と通知が返すデータはいずれも JWS 形式で、署名検証が必要です。
import CryptoKit
func verifyJWS(_ jwsString: String) throws -> Transaction {
// 1. JWS の 3 つの部分を分離する
let parts = jwsString.split(separator: ".")
guard parts.count == 3 else { throw VerificationError.invalidFormat }
// 2. header を解析し、証明書を取得する
let headerData = base64Decode(String(parts[0]))
let header = try JSONDecoder().decode(JWSHeader.self, from: headerData)
// 3. Apple から公開鍵を取得し、証明書チェーンを検証する
let certificate = try validateCertificateChain(header.x5c)
// 4. 署名を検証する
let signedData = Data((parts[0] + "." + parts[1]).utf8)
let signature = base64Decode(String(parts[2]))
let isValid = certificate.publicKey.isValidSignature(signature, for: signedData)
guard isValid else { throw VerificationError.invalidSignature }
// 5. payload を解析する
let payloadData = base64Decode(String(parts[1]))
return try JSONDecoder().decode(Transaction.self, from: payloadData)
}
(07:45)
キーポイント:
- JWS は header、payload、signature の 3 部分で構成されます
- Apple からルート証明書を取得し、証明書チェーンを検証します
- 署名を検証して、データが改ざんされていないことを確認します
- payload を解析して取引情報を取得します
Family Sharing
(09:00)
iOS 15 はアプリ内課金の Family Sharing に対応します。ユーザーが購入すると、家族メンバーは追加料金なしで自動的にアクセスできます。
Server API では inAppOwnershipType フィールドで区別できます。
PURCHASED:ユーザー本人が購入FAMILY_SHARED:Family Sharing で取得
Family Sharing が取り消されると、サーバーは REVOKE 通知を受け取ります。
(10:15)
キーポイント:
inAppOwnershipTypeは購入と共有を区別しますREVOKE通知は共有が取り消されたことを示します- 共有ユーザーのアクセス権を処理する必要があります
- サブスクリプションと非消耗型アプリ内課金に適しています
サンドボックス環境の改善
(11:30)
サンドボックス環境には新機能が追加されました。
- サブスクリプション加速テスト:サンドボックス内のサブスクリプション期間が圧縮されます(1 年 = 1 時間)
- 返金テスト:サンドボックスで返金フローをシミュレートできます
- Family Sharing テスト:サンドボックスで Family Sharing をテストできます
- オファーテスト:サンドボックスで introductory offer と promotional offer をテストできます
(12:45)
キーポイント:
- サンドボックスのサブスクリプション期間は加速され、更新テストがしやすくなります
- サンドボックスは返金テストに対応します
- サンドボックスは Family Sharing テストに対応します
- テストアカウントは App Store Connect で管理します
重要ポイント
-
Server API で購入状態を能動的に同期する。クライアント報告だけに依存せず、サーバーが定期的に取得してデータの一貫性を保ちます。入口 API:
GET /inApps/v1/history/{originalTransactionId}。 -
Server Notifications V2 で状態変化にリアルタイム対応する。20 種類以上のイベントタイプがサブスクリプションのライフサイクル全体をカバーします。入口 API:サーバー URL を設定して JWS 通知を受信します。
-
すべての JWS データの署名を検証する。中間者攻撃とデータ改ざんを防ぎます。入口 API:Apple ルート証明書 + JWS 署名検証。
-
Family Sharing に対応する。
PURCHASEDとFAMILY_SHAREDを区別し、REVOKE通知を処理します。入口 API:inAppOwnershipType+REVOKE通知。 -
サンドボックスで完全な購入フローをテストする。加速されたサブスクリプション期間を使い、更新、アップグレード、ダウングレード、返金をすばやくテストします。入口 API:App Store Connect サンドボックステストアカウント。
関連セッション
- Support customers and handle refunds — カスタマーサポートと返金処理
- Meet StoreKit 2 — StoreKit 2 の新機能
- Meet in-app events on the App Store — App Store の App 内イベント
コメント
GitHub Issues · utterances