9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

フリューAdvent Calendar 2023

Day 22

AppStore#verifyReceiptの代替対応

Last updated at Posted at 2023-12-21

はじめに

この記事はフリューAdvent Calendar 2023の22日目の記事となります。

フリュー株式会社スマートフォンゲーム部で、Webサーバーエンジニアをしている山根です。
AppStoreの消費型の課金処理で、レシートの検証として
AppStore#verifyReceipt
を使ってきましたが、ついに非推奨になってしまいました。
これに代わる対応について紹介していきたいと思います。

代替案

ドキュメントには、以下3つの代替案が記述されています。

  1. AppStoreServerAPIを使って、Apple署名付きトランザクション情報を取得、検証する
  2. アプリが取得するAppTransactionおよびTransactionの署名付きトランザクション情報を検証する
  3. AppStoreServerNotificationsV2のエンドポイントから署名付きトランザクション情報を取得、検証する

今回は、これらの中で、

  1. AppStoreServerAPIを使って、Apple署名付きトランザクション情報を取得、検証する

について紹介します。

AppStoreServerAPIを使った検証

ここでは、Appleが用意しているJava用のライブラリを使ってKotlinで実装をしていきます。

まず、AppStoreServerAPI#Authorize your API callsに記載されているAPIキーをAppStoreConnectから事前に取得しておく必要があるので、準備しておきます。

準備ができたら、以下のように実装し、AppStoreServerAPIを使えるようにします。

val signingKey = // AppStoreConnectからダウンロードしたAPIキー
val keyId = // AppStoreConnectから取得したKeyId
val issuerId = // AppStoreConnectから取得したIssuerId
val bundleId = // AppのbundleId
val environment = // 対象となる環境(SANDBOX or PRODUCTION)

fun createAppleStoreServerAPIClient(): AppStoreServerAPIClient {
    return AppStoreServerAPIClient(signingKey, keyId, issuerId, bundleId, environment)
}    

今回の題目にあるAppStore#verifyReceiptの代わりとして、
Get Transaction Info
を使ってレシートの情報を取得したいと思います。

// transactionIdは、検証したいレシートに紐づくトランザクションID
fun getTransactionInfo(transactionId: String): TransactionInfoResponse {
    return createAppleStoreServerAPIClient().getTransactionInfo(transactionId)
}

これにより、JSON Web Signature (JWS)形式のトランザクション情報を取得できました。
これをさらに検証し、デコードしていきます。
ここの検証とデコードには、SignedDataVerifierを使っていきます。
これを使うにあたり、Appleが用意しているルート証明書が必要になります。
今回は、Apple Root CA - G3を使っています。
SignedDataVerifierを使うことで、以下のように検証、デコードができます。

val appleRootCAFile = // Appleが用意しているルート証明書
val bundleId = // AppのbundleId
val appAppleId = // AppのId
val environment = // 対象となる環境(SANDBOX or PRODUCTION)

fun signedPayloadVerifier(): SignedDataVerifier {
    return SignedDataVerifier(
        setOf(appleRootCAFile.inputStream()),
        bundleId,
        appAppleId,
        environment,
        true
    )
}

fun getTransactionInfo(transactionId: String): JWSTransactionDecodedPayload {
    // transactionIdに紐づく情報を取得
    val transactionInfo = createAppleStoreServerAPIClient().getTransactionInfo(transactionId)
    return signedPayloadVerifier().verifyAndDecodeTransaction(transactionInfo.signedTransactionInfo)
}

これによって、transactionIdに紐づくレシート情報が取得でき、
AppStore#verifyReceiptの代わりとして使うことができます。

AppStoreServerAPIを使う上での注意点

AppStoreServerAPIは、1時間内に各エンドポイントに送信できるリクエスト数に制限が設定されています。

今回のGet Transaction Info
であれば、1秒間で50リクエストとあるので、
1時間では、180,000リクエストまでとなっています。
(このリクエスト数は、Appleがいつでも変える可能性があるようです。)
そのため、このリクエスト制限に引っかかった場合のハンドリングが必要になります。
リクエスト制限に引っかかった場合は、HTTP 429が返ってきます。
また、Retry-Afterヘッダーに、次にリクエストを送信できるUNIX時間をミリ秒単位で含んでいるため、それらを使ってハンドリングする感じになると思います。

まとめ

AppStore#verifyReceiptの代わりとして、AppStoreServerAPIを使った検証を紹介しました。
今回使っているライブラリは、現時点ではベータ版となっていることから商用利用では注意が必要です。
Appleさんには、AppStore#verifyReceiptの利用停止までには正規版としてリリースしてくれることを願っております。
(正規版がリリースされたようです。)

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?