💰
StoreKit2ではStoreKit1のReceiptは扱えない
結論から言いますと表題の通りStoreKit2ではStoreKit1のReceiptは扱えないようです。
StoreKit1
StoreKit1ではBundle.main.appStoreReceiptURL
に最新のレシートが入っています。
StoreKit2
一方StoreKit2ではSKReceiptRefreshRequestでリフレッシュしても最新のレシートが入ってきませんでした。ここで私はだいぶハマってしまいましたが、以下のtapple様の記事を見つけてStoreKit2ではReceiptを扱ってはいけないのだとわかりました。
StoreKit2での購入の検証
ではStoreKit2での購入の検証はどうすばいいのかと言いますと、StoreKit2でpurchase()した後に得られるoriginalTransactionIdをキーにして検証を行うようです。この検証は私が調査した限りではiOS端末で行うのではなく、サーバーサイドで行います。
検証の詳細は以下のフリュー株式会社様の記事が大変参考になりました。
こちらの記事ではjavaのライブラリで検証していますが、他にもtypescriptやSwift(server side swift?)も用意されています。
私は試しにMacアプリのプロジェクトで以下のようなコードで試してみました。(各定数やファイル名は適宜置き換えてください)
import Cocoa
import AppStoreServerLibrary
class ViewController: NSViewController {
let issuerId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
let keyId = "XXXXXXXXXX"
let bundleId = "xx.xx.xxx.xxxxx.xxx"
let environment = Environment.sandbox
let appAppleId: Int64 = 0000000000
let transactionId = "0000000000000000"
override func viewDidLoad() {
super.viewDidLoad()
guard let path = Bundle.main.path(forResource:"Key_File_Name", ofType:"p8") else {
return
}
let encodedKey = try! String(contentsOfFile: path)
let client = try! AppStoreServerAPIClient(
signingKey: encodedKey,
keyId: keyId,
issuerId: issuerId,
bundleId: bundleId,
environment: environment
)
Task {
let response = await client.getTransactionInfo(transactionId: transactionId)
switch response {
case .success(let response):
print(response)
getJWSTransactionDecodedPayload(transactionInfo: response)
case .failure(let errorCode, let rawApiError, let apiError, let errorMessage, let causedBy):
print(errorCode)
print(rawApiError)
print(apiError)
print(errorMessage)
print(causedBy)
}
}
}
func getJWSTransactionDecodedPayload(transactionInfo: TransactionInfoResponse) {
guard let certPath = Bundle.main.path(forResource: "Cer_File_Name", ofType: "cer") else {
print("証明書ファイルが見つかりません")
return
}
guard let certData = try? Data(contentsOf: URL(fileURLWithPath: certPath)) else {
print("証明書ファイルの読み込みに失敗しました")
return
}
do {
let signedDataVerifier = try SignedDataVerifier(
rootCertificates: [certData],
bundleId: bundleId,
appAppleId: appAppleId,
environment: environment,
enableOnlineChecks: true
)
Task {
let response = await signedDataVerifier.verifyAndDecodeTransaction(
signedTransaction: transactionInfo.signedTransactionInfo ?? ""
)
switch response {
case .valid(let jwsTransactionDecodedPayload):
print(jwsTransactionDecodedPayload)
case .invalid(let error):
print(error)
}
}
} catch {
print(error)
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
実行するとSubscriptionのアイテムの購入した場合以下のような値が返ってきます。(実際の値は伏せています)
JWSTransactionDecodedPayload(
originalTransactionId: Optional("0000000000000000"),
transactionId: Optional("0000000000000000"),
webOrderLineItemId: Optional("0000000000000000"),
bundleId: Optional("xx.xx.xxx.xxxxx.xxx"),
productId: Optional("xx.xx.xxx.xxxxx.xxx.xxxxx"),
subscriptionGroupIdentifier: Optional("0000000000"),
purchaseDate: Optional(2024-07-18 00:39:41 +0000),
originalPurchaseDate: Optional(2024-07-18 00:39:42 +0000),
expiresDate: Optional(2024-07-18 00:42:41 +0000),
quantity: Optional(1),
rawType: Optional("Auto-Renewable Subscription"),
appAccountToken: nil,
rawInAppOwnershipType: Optional("PURCHASED"),
signedDate: Optional(2024-07-19 01:42:36 +0000),
rawRevocationReason: nil,
revocationDate: nil,
isUpgraded: nil,
rawOfferType: nil,
offerIdentifier: nil,
rawEnvironment: Optional("Sandbox"),
storefront: Optional("JPN"),
storefrontId: Optional("000000"),
rawTransactionReason: Optional("PURCHASE"),
currency: Optional("JPY"),
price: Optional(200000),
rawOfferDiscountType: nil
)
これでサーバーサイドで検証できるかと思います。
Discussion