ハイライト
App Store サーバー API と App Store サーバー通知 V2 は、JWS 署名データ、originalTransactionId クエリ、通知履歴、およびテスト通知を使用して、サーバーが verifyReceipt から検証可能で回復可能なアプリ内購入状態同期プロセスに移行できるようにします。
主要内容
アプリ内購入サーバーが最も恐れる問題は 2 種類あります。
最初のカテゴリは、分散したデータ ソースです。古いプロセスの依存関係verifyReceipt、サーバーはレシートを取得した後、それを独自に解析する必要があります。in_app、latest_receipt_info、pending_renewal_info、そしてからexpiration_intent、grace_period_expires_date他のフィールドはサブスクリプションのステータスを推測します。
2 番目のカテゴリは、アプリの外部で状態変化が発生した場合です。サブスクリプションの更新、払い戻し、およびユーザーによる自動更新の無効化はすべて、ユーザーがアプリを開いていないときに発生する可能性があります。クライアントのレポートのみに依存すると、サーバーは簡単に遅れをとってしまう可能性があります。
このセッションで提供される移行パスは 2 つの部分に分かれています。 Gabriel が App Store Server API の使用について語ります。originalTransactionIdGet Transaction History や Get All Subscription Statuses などのエンドポイントを呼び出し、返されるデータは JWS (JSON Web Signature、JSON Web Signature) 形式です。 Alex は App Store サーバー通知 V2 について語ります。サブスクリプションのステータスが変化すると、Apple は署名通知をサーバーに積極的に送信します。
ポイントは、移行を個別に実行できることです。 App Store サーバー API は StoreKit 2 をバインドしません。オリジナルの StoreKit、StoreKit 2、Notification V1、Notification V2 には、現在のシステム ステータスに応じて段階的にアクセスできます。古いクライアントは引き続きレシートを送信し、新しいクライアントは署名トランザクションを送信し、サーバーは同じものを使用できます。originalTransactionId新しいクエリと検証プロセスを入力します。
詳細
OriginalTransactionId から新しいサーバー API を入力します
(02:39) App Store サーバー API の複数のエンドポイントの使用originalTransactionIdパスパラメータとして。この ID は、古いレシート、署名トランザクション、署名更新メッセージ、および通知から取得される可能性があります。このようにして、サーバーは、すべてのクライアントが StoreKit 2 にアップグレードされるのを待たずに、最初にクエリ ロジックを移行できます。
(03:28) オリジナル StoreKit の移行プロセスは次のとおりです: アプリのレシートを取得し、電話します。verifyReceipt、デコードされたレシートから収集originalTransactionIdをクリックしてから、トランザクション履歴の取得またはすべてのサブスクリプション ステータスの取得を呼び出します。
概念流程:Original StoreKit 客户端迁移
appReceipt
-> verifyReceipt(appReceipt)
-> decodedReceipt.in_app / latest_receipt_info / pending_renewal_info
-> collect originalTransactionId
-> Get Transaction History(originalTransactionId)
-> signed transactions
-> Get All Subscription Statuses(originalTransactionId)
-> latest signed transaction + signed renewal information
キーポイント:
appReceiptこれは、元のクライアントがサーバーにアップロードしたデータです。 -verifyReceipt(appReceipt)移行の最初のステップでもまだ存在しており、古いレシートをデコードするために使用されます。 -in_app、latest_receipt_info、pending_renewal_info記録に名前が書いてあるoriginalTransactionIdソース。- トランザクション履歴の取得は、このユーザーのトランザクション履歴を返します。結果は署名されたトランザクションです。
- すべてのサブスクリプション ステータスを取得すると、指定されたサブスクリプションの最新の署名済みトランザクションと署名済みの更新情報が返されます。
(04:52) StoreKit 2 には、より直接的なエントリ ポイントがあります。クライアントが検証されたトランザクションを取得すると、次のようになります。originalID、サーバーは、署名されたトランザクションを受信した後、JWS の最上位フィールドでそれを読み取ることもできます。originalTransactionId。
// 概念代码:StoreKit 2 客户端从已验证交易读取 originalTransactionId
let verificationResult = transactionResult
switch verificationResult {
case .verified(let transaction):
let originalTransactionId = transaction.originalID
sendSignedTransactionToServer(transaction)
case .unverified:
break
}
キーポイント:
- この段落は、転写プロセスに従って編集された概念的なコードです。このセッションでは、公式のコード スニペットは提供されません。
-
.verified検証されデコードされた StoreKit 2 トランザクションに対応します。 -transaction.originalIDトランスクリプトで言及されている属性であり、取得するために使用されます。originalTransactionId。 sendSignedTransactionToServer(transaction)署名されたトランザクションが保存と検証のためにサーバーに渡されることを示します。関数名がヒントです。 -.unverified支店を資本の基礎として使用することはできません。
App Store サーバー API の JWT に署名する
(08:18) App Store サーバー API を呼び出す場合、各サーバー リクエストは Authorization ヘッダーに JWT (JSON Web Token、JSON Web Token) を含める必要があります。 JWT は、ヘッダー、ペイロード、署名というピリオドで区切られた 3 つのセクションで構成されます。
ヘッダーにありますkeyId、これは App Store Connect の秘密キー ID と一致する必要があります。アプリに関連する情報をペイロードに入れます。 Apple は、「API リクエストを承認するための API キーの作成」と「API リクエスト用の JSON Web トークンの生成」という 2 つのドキュメントをリソースで提供しています。
概念伪代码:生成 App Store Server API JWT
privateKey = loadPrivateKey(for: keyId)
header = {
keyId: keyId,
algorithm: signingAlgorithm
}
payload = {
issuer: issuerId,
audience: appStoreConnectAudience,
bundleId: appBundleId,
issuedAt: now,
expiration: shortLivedExpiration
}
token = jwtLibrary.sign(
privateKey: privateKey,
header: header,
payload: payload
)
キーポイント:
loadPrivateKey(for: keyId)トランスクリプトの「秘密キーはキー ID に対応する必要がある」という要件に対応します。 -header署名メタデータを保存します。keyIdApp Store Connectの秘密キーと一致させるため。 -payloadアプリケーション関連の宣言を保存します。特定のフィールドは Apple の JWT ドキュメントの対象となります。 -jwtLibrary.sign(...)トランスクリプト内の「JWT ライブラリが公開する署名関数の呼び出し」に対応します。 -token各 App Store サーバー API リクエストの Authorization ヘッダーに含められます。
(10:27) このセッションでは、トークンを使用して Get All Subscription Statuses を呼び出す cURL の例も示しています。公式スニペットはローカルの「コード」タブには表示されず、呼び出し構造のみがここに保持されます。
# 概念命令:把生成的 JWT 放入 Authorization header
curl \
--header "Authorization: Bearer ${token}" \
"<Get All Subscription Statuses endpoint URL with ${originalTransactionId}>"
キーポイント:
${token}前のステップでチェックアウトした JWT です。 -${originalTransactionId}レシート、署名されたトランザクション、または通知内の元のトランザクション ID です。- ドキュメント内の特定のエンドポイント フォームの入力ミスを避けるために、URL のプレースホルダーを使用します。
- このリクエストは、サブスクリプションの最新ステータス、最新の署名トランザクション、署名更新情報を取得するために使用されます。
JWS を検証し、データが App Store から取得されたものであることを確認します
(10:41) App Store サーバー API と通知 V2 は両方とも JWS を返します。 JWS の価値は改ざん防止です。サーバー側の検証に合格した後でのみ、データが App Store からのものであり、送信プロセス中に変更されていないと信じることができます。
(11:20) ヘッダーから検証が始まります。最初のbase64デコードヘッダー、読み取りalg署名アルゴリズムを確認して再度読み取りますx5c証明書チェーン。x5cチェーンは Apple によって発行され、チェーン上の最後のリーフ証明書が JWS の署名に使用されます。
概念流程:验证 signed transaction
jws = receiveSignedTransaction()
header = base64Decode(jws.header)
algorithm = header.alg
certificateChain = header.x5c
verify certificateChain against Apple root certificate
verify jws signature with leaf certificate
if verification succeeds:
store validated transaction data
else:
reject the JWS
キーポイント:
jwsApp Store サーバー API または通知 V2 によって返される署名付きデータです。 -header.alg署名アルゴリズムを指定します。 -header.x5cこれは証明書チェーンであり、証明書の順序に意味があることがトランスクリプトで強調されています。 -certificateChainApple 認証局のルート証明書との信頼関係を確立します。- 検証が失敗した場合は、JWS が改ざんされているか、App Store によって発行されていない可能性があるため、使用しないでください。
(13:35) セッションでは、OpenSSL 検証のアイデアが示されました。Apple ルート CA を信頼し、それを使用して WWDR 証明書を検証し、チェーンを使用してリーフ証明書の検証を続行します。成功した場合はデコードされたデータを使用し、失敗した場合はエラー コードに基づいてトラブルシューティングを行います。
# 概念命令:用 OpenSSL 验证 x5c 证书链
openssl verify \
-trusted AppleRootCA.pem \
-untrusted AppleWWDRCA.pem \
LeafCertificate.pem
キーポイント:
AppleRootCA.pemApple 認証局から取得したルート証明書に相当します。 --trustedルート証明書をトラストアンカーとして使用することを示します。 -AppleWWDRCA.pemトランスクリプトに記載されている WWDR 証明書に対応します。 -LeafCertificate.pemこれは証明書チェーンの最後にあるリーフ証明書であり、JWS の署名に使用されます。- コマンドが成功した後は、証明書チェーンの検証に合格したことを意味するだけです。 JWS 署名は、対応するアルゴリズムに従って JWS ライブラリを使用して検証する必要があります。
verifyReceipt の一般的なクエリを新しい API に置き換えます。
(16:04) 移行は、古いロジックをすべて一度に削除しなくても、段階的に実行できます。 session には、古いフィールド推論を新しいエンドポイントに置き換える、いくつかの高頻度シナリオがリストされています。
概念映射:verifyReceipt 迁移到 App Store Server API
旧流程:
verifyReceipt
-> expiration_intent / grace_period_expires_date / latest_receipt_info
-> 推断订阅状态和最新交易
新流程:
Get All Subscription Statuses(originalTransactionId)
-> status
-> latest signed transaction
-> latest signed renewal information
Get Transaction History(originalTransactionId)
-> filtered, sorted, paginated signed transactions
キーポイント:
expiration_intentそしてgrace_period_expires_date古いレシートのステータスを推測するために使用されるフィールドです。- すべてのサブスクリプション ステータスの取得は直接返されます
statusフィールド、および最新の署名済みトランザクションと更新情報。 - 取引履歴の取得は完全な購入履歴を返し、フィルタリング、並べ替え、ページングをサポートします。
- 新しいエンドポイントは両方とも次のように終わります。
originalTransactionId入り口用に。
(18:06)appAccountToken移行の範囲にも入ります。 StoreKit 2 では、開発者は UUID を提供してトランザクションをユーザーに関連付けることができます。 AppleもオリジナルStoreKitを提供applicationUsername互換性サポートの追加: UUID がここに渡されると、appAccountToken関数を使用し、verifyReceipt、App Store サーバー API、および通知に表示されます。
概念流程:用 UUID 关联 App 用户和交易
userId
-> generate UUID for purchase association
-> StoreKit 2: provide UUID as appAccountToken
-> Original StoreKit: provide UUID as applicationUsername
-> signed transactions / signed renewals / notifications include appAccountToken
-> server maps appAccountToken back to the app account
キーポイント:
- UUID は、アプリ内ユーザーを App Store トランザクションに関連付けるために使用されます。
- StoreKit 2の使用法
appAccountToken。 - オリジナル StoreKit の使用
applicationUsernameUUIDを渡します。 - この値は、署名トランザクション、署名更新メッセージ、および通知に表示されます。
- サーバーはこれを使用して、デバイスまたはログイン ステータスのみに依存するトランザクションの脆弱性を軽減できます。
通知 V2 に接続し、パッシブなクエリからアクティブな同期に変更します。
(19:37) App Store サーバー通知は、ユーザーが特定のアクションを実行したときに Apple がサーバーに送信するメッセージです。一般的なカテゴリには、サブスクリプションの更新と払い戻しの更新が含まれます。アプリがオンラインでないときのギャップを埋めます。たとえば、サブスクリプションの更新が発生すると、トランザクション情報がサーバーに直接送信されます。
(21:20) 設定は App Store Connect から始まります。プロダクションとサンドボックスはそれぞれ URL と通知バージョンを設定できます。 Apple は、V1 ユーザーが最初にサンドボックスで V2 を試し、HTTPS 証明書と Apple パブリック IP アクセスが正常であることを確認することをお勧めします。
概念检查清单:第一次接入 Notifications V2
App Store Connect
-> open App Store Server Notifications section
-> configure Sandbox URL
-> select Version 2 Notifications
-> confirm HTTPS certificate
-> allow Apple public IPs
-> Request a Test Notification
-> Get Test Notification Status if delivery fails
キーポイント:
- サンドボックス URL と本番 URL は個別に設定できます。
- HTTPS 証明書とファイアウォールは、トランスクリプトの失敗の一般的な理由です。
- テスト通知のリクエストは、テスト通知を自動的にトリガーするために使用されます。
- テスト通知ステータスの取得は、たとえば失敗の手がかりを返します。
SSL_ISSUE。 - 通知構成が変更されるたびに、テスト通知がトリガーされる必要があります。
(23:32) 通知自体も JWS です。 JSON 本文を受信した後、サーバーはまず、signedPayload、以前の署名済みトランザクションと同じ一連の手順に従って検証します。検証後は対象のアプリや環境も確認してください。
概念流程:处理 Notifications V2 请求
requestBody
-> signedPayload
-> verify JWS signature
-> decode notification body
-> check appAppleId / bundleId
-> check environment
-> store notification
-> enqueue asynchronous processing
-> return HTTP 200 quickly
キーポイント:
signedPayload通知の JSON 本文で検証されるフィールドです。- JWS 検証手順は署名済みトランザクションと同じです。
-
appAppleIdそしてbundleId通知がアプリに属していることを確認するために使用されます。 -environment本番環境またはサンドボックスの期待と一致するようにしてください。 - App Store の記録タイムアウトや通知の再送信を避けるために、時間のかかる処理は非同期で実行する必要があります。
notificationUUID、signedDate、および History を使用して、再試行と収集の失敗を処理します
(25:19) 通知 V2 の本文はnotificationType、オプションsubtype、notificationUUID、signedDate、appAppleId、bundleId、environment、signedTransactionInfoそしてオプションのsignedRenewalInfo。
でnotificationUUID重量を取り除くために使用されます。 Apple が同じ通知を再試行しても、UUID は変更されません。サーバーがすでにこの UUID を処理している場合は、権利を繰り返し発行したり、アカウンティングを繰り返したり、ユーザーの連絡を繰り返しトリガーしたりしてはなりません。
概念伪代码:通知去重和异步处理
notification = decodeAndVerify(signedPayload)
if notificationUUID already exists:
return HTTP 200
store notificationUUID
store signedDate, notificationType, subtype
enqueue work with signedTransactionInfo and signedRenewalInfo
return HTTP 200
キーポイント:
decodeAndVerify(signedPayload)JWS 検証が最初に完了してからデコードされることを示します。 -notificationUUID already existsトランスクリプトでの重複通知検出に対応します。 -signedDate遅れて到着する再試行通知を識別するのに役立ちます。 -enqueue work時間のかかるロジックを非同期キューに移動します。 -return HTTP 200不必要な再試行を避けるために、できるだけ早く実行してください。
(30:37) サーバーの停止後、Apple は文書化されたポリシーに従って V2 通知を再試行します: 最初の失敗から 1 時間、次に 12 時間、24 時間、48 時間、72 時間。短い障害の場合は、待ってから再試行できます。長い障害は積極的に修正する必要があります。
(33:39) 通知履歴の取得 6 か月間の通知履歴を提供します。回復中に、停止の開始および終了のタイムスタンプが記録され、この期間のみが照会されます。通知が多い場合は、最初にタイプでフィルタリングできます。たとえば、DID_RENEWそしてEXPIRED; 単一ユーザーのトラブルシューティングを行う場合に使用できます。originalTransactionIdフィルター。
概念流程:宕机后补回可能漏收的通知
outageStart, outageEnd = incident window
Get Notification History(
startDate: outageStart,
endDate: outageEnd,
notificationType: highImpactTypes,
originalTransactionId: optionalUserScope
)
-> notificationHistory[]
-> signedPayload
-> firstSendAttemptResult
キーポイント:
outageStartそしてoutageEndクエリの範囲を狭め、繰り返し処理を減らします。 -notificationType最初に、権利と利益に影響を与えるタイプを選択できます。たとえば、DID_RENEW、EXPIRED。originalTransactionIdカスタマー サポート シナリオでユーザーの通知トラックを確認するために使用できます。 -notificationHistory[]の各項目は通知を表します。 -firstSendAttemptResult最初の配信結果を提供します。例:SUCCESSまたはSSL_ISSUE。
サブタイプを使用してサブスクリプションのライフサイクルを理解する
(27:06) 通知 V2 では通知モデルが変更されました。 V1 は、各通知に完全な最近の履歴を含めることを好みます。 V2 は、最新の情報 (最新のトランザクション情報、およびサブスクリプション シナリオの更新保留情報) のみを送信します。複数の通知が結合されて、サブスクリプション ステータスのタイムラインが形成されます。
(28:45) セッションでは、例としてサブスクリプションのライフサイクルを使用します。最初のサブスクリプションが発行されます。SUBSCRIBED + INITIAL_BUY、更新が発行されますDID_RENEW、ユーザーは自動更新をオフにして、DID_CHANGE_RENEWAL_STATUS + AUTO_RENEW_DISABLED、有効期限後に発行されるEXPIRED + VOLUNTARY。
概念时间线:订阅从购买到自愿到期
SUBSCRIBED / INITIAL_BUY
-> user enters renewing state
DID_RENEW
-> subscription renews
DID_CHANGE_RENEWAL_STATUS / AUTO_RENEW_DISABLED
-> user turns off auto-renewal
EXPIRED / VOLUNTARY
-> subscription period ends without renewal
キーポイント:
notificationType発生したイベントの種類を示します。 -subtype理由を追加するか、シナリオを細分化します。 -DID_CHANGE_RENEWAL_STATUS権利は必ずしもすぐに取り消される必要はありませんが、ユーザーをリコールする窓口となります。 -EXPIREDサブスクリプションの有効期限が切れており、通常はアクセスを取り消す必要があることを示します。- V2 は、サブスクリプション ライフ サイクルにおけるより多くの転送状態をカバーし、サーバーは少数のキー タイプからアクセスを開始できます。
重要ポイント
-
**何をすべきか: verifyReceipt ステータスの判断をデュアルトラック クエリに移行します。 ** 実行する価値がある理由: 元の StoreKit クライアントがまだレシートを使用している場合でも、サーバーはレシートからレシートを取得できます。
originalTransactionIdをクリックし、App Store サーバー API を使用して最新のステータスをクエリします。 開始方法: 既存の存在を維持するverifyReceiptエントリをデコードし、すべてのサブスクリプション ステータスを取得するクエリを追加し、古いフィールドと新しいフィールドを推測します。status結果は時間の経過とともに並行して記録されます。 -
**やるべきこと: JWT 署名レイヤーをすべての App Store サーバー API リクエストに追加します。 ** 実行する価値がある理由: セッションでは、すべてのサーバー リクエスト、ヘッダーに対して JWT 認証が明示的に必要です
keyIdApp Store Connectの秘密キーと一致させるため。 開始方法: サーバー側でトークン生成モジュールをカプセル化し、「短期 JWT の生成」と「Authorization ヘッダーを使用した API の呼び出し」の 2 つの機能のみを公開します。 -
**やるべきこと: JWS 検証を統一された入り口にします。 ** 実行する価値がある理由: 署名付きトランザクションと通知 V2
signedPayload同じ種類の JWS 検証フローを使用します。 開始方法: まず証明書チェーン検証を実装し、次にリーフ証明書を使用して JWS 署名を検証します。検証に失敗したデータは直接破棄され、資本システムには入りません。 -
**やるべきこと: 通知 V2 用の高速 ACK + 非同期処理キューを確立します。 ** 実行する価値がある理由: Apple は処理タイムアウトを配信失敗として扱い、再試行します。通知を繰り返すと、冪等性の圧力が高まります。 開始方法: リクエスト エントリは、JWS 検証、アプリ/環境チェック、
notificationUUID重複を排除してキューに登録し、すぐに HTTP 200 を返します。 -
**対処方法: 「ダウンタイム補償」ツールをサブスクリプション サーバーに追加します。 ** 実行する価値がある理由: 通知履歴を取得すると、6 か月分の履歴が表示されます。
firstSendAttemptResultを使用すると、インシデント後に見逃した通知を確認したり、SSL や配信の問題を特定したりできます。 開始方法: インシデント復旧中の開始時刻と終了時刻を、期間、通知の種類、オプションごとに記録します。originalTransactionId履歴をクエリし、既存の JWS 検証および重複排除ロジックを使用して再生します。
関連セッション
- アプリ内購入の新機能 — ここで説明するトランザクション履歴の取得のフィルタリング、並べ替え、テスト通知、および通知履歴の年次更新は、この概要に基づいています。
- StoreKit テストの新機能 — Xcode と Sandbox でサブスクリプション、払い戻し、値上げ、請求の失敗などのアプリ内購入の境界状態をテストする方法について説明します。
- プロアクティブなアプリ内購入の復元を実装する — クライアントの起動時にユーザーがアプリ内購入の権利を持っていることを識別し、サーバーのステータスと同期する方法について説明します。
コメント
GitHub Issues · utterances