答えのほとんどの部分は、Apple のドキュメントに記載されています。しかし、ギャップがあり、objective-c コードは非推奨のメソッドを使用しています。
この Swift 3 コードは、App Receipt を取得し、それを検証のためにアプリ ストアに送信する方法を示しています。必要なデータを保存する前に、必ずアプリ ストアでアプリの領収書を検証する必要があります。アプリ ストアに検証を依頼する利点は、JSON に簡単にシリアル化し、そこから必要なキーの値を引き出すことができるデータで応答することです。暗号化は必要ありません。
Apple がそのドキュメントで説明しているように、好ましいフローは次のようなものです...
device -> your trusted server -> app store -> your trusted server -> device
アプリ ストアがサーバーに戻ると、成功したと仮定して、必要なデータをシリアル化して引き出し、必要に応じて保存します。以下の JSON を参照してください。そして、結果やその他必要なものをアプリに送り返すことができます。
以下ではvalidateAppReceipt()
、実際の例にするために、このフローを使用するだけです...
device -> app store -> device
サーバーでこれを機能させるには、サーバーvalidationURLString
を指すように変更し、必要なものを に追加しますrequestDictionary
。
これを開発でテストするには、次のことを行う必要があります。
- itunesconnectでサンドボックスユーザーが設定されていることを確認してください
- テスト デバイスで iTunes と App Store からサインアウトする
- テスト中、プロンプトが表示されたら、サンドボックス ユーザーを使用します
これがコードです。幸せな道は順調に流れています。エラーと障害点は、単に出力されるか、コメントされます。必要に応じて対処してください。
この部分は、アプリのレシートを取得します。そこにない場合 (テスト中に発生します)、アプリ ストアに更新を要求します。
let receiptURL = Bundle.main.appStoreReceiptURL
func getAppReceipt() {
guard let receiptURL = receiptURL else { /* receiptURL is nil, it would be very weird to end up here */ return }
do {
let receipt = try Data(contentsOf: receiptURL)
validateAppReceipt(receipt)
} catch {
// there is no app receipt, don't panic, ask apple to refresh it
let appReceiptRefreshRequest = SKReceiptRefreshRequest(receiptProperties: nil)
appReceiptRefreshRequest.delegate = self
appReceiptRefreshRequest.start()
// If all goes well control will land in the requestDidFinish() delegate method.
// If something bad happens control will land in didFailWithError.
}
}
func requestDidFinish(_ request: SKRequest) {
// a fresh receipt should now be present at the url
do {
let receipt = try Data(contentsOf: receiptURL!) //force unwrap is safe here, control can't land here if receiptURL is nil
validateAppReceipt(receipt)
} catch {
// still no receipt, possible but unlikely to occur since this is the "success" delegate method
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("app receipt refresh request did fail with error: \(error)")
// for some clues see here: https://samritchie.net/2015/01/29/the-operation-couldnt-be-completed-sserrordomain-error-100/
}
この部分は、アプリの領収書を検証します。これはローカル検証ではありません。コメントの注 1 と注 2 を参照してください。
func validateAppReceipt(_ receipt: Data) {
/* Note 1: This is not local validation, the app receipt is sent to the app store for validation as explained here:
https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW1
Note 2: Refer to the url above. For good reasons apple recommends receipt validation follow this flow:
device -> your trusted server -> app store -> your trusted server -> device
In order to be a working example the validation url in this code simply points to the app store's sandbox servers.
Depending on how you set up the request on your server you may be able to simply change the
structure of requestDictionary and the contents of validationURLString.
*/
let base64encodedReceipt = receipt.base64EncodedString()
let requestDictionary = ["receipt-data":base64encodedReceipt]
guard JSONSerialization.isValidJSONObject(requestDictionary) else { print("requestDictionary is not valid JSON"); return }
do {
let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
let validationURLString = "https://sandbox.itunes.apple.com/verifyReceipt" // this works but as noted above it's best to use your own trusted server
guard let validationURL = URL(string: validationURLString) else { print("the validation url could not be created, unlikely error"); return }
let session = URLSession(configuration: URLSessionConfiguration.default)
var request = URLRequest(url: validationURL)
request.httpMethod = "POST"
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let task = session.uploadTask(with: request, from: requestData) { (data, response, error) in
if let data = data , error == nil {
do {
let appReceiptJSON = try JSONSerialization.jsonObject(with: data)
print("success. here is the json representation of the app receipt: \(appReceiptJSON)")
// if you are using your server this will be a json representation of whatever your server provided
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
} else {
print("the upload task returned an error: \(error)")
}
}
task.resume()
} catch let error as NSError {
print("json serialization failed with error: \(error)")
}
}
このような結果になるはずです。あなたの場合、これはサーバー上で作業するものです。
{
environment = Sandbox;
receipt = {
"adam_id" = 0;
"app_item_id" = 0;
"application_version" = "0"; // for me this was showing the build number rather than the app version, at least in testing
"bundle_id" = "com.yourdomain.yourappname"; // your app's actual bundle id
"download_id" = 0;
"in_app" = (
);
"original_application_version" = "1.0"; // this will always return 1.0 when testing, the real thing in production.
"original_purchase_date" = "2013-08-01 07:00:00 Etc/GMT";
"original_purchase_date_ms" = 1375340400000;
"original_purchase_date_pst" = "2013-08-01 00:00:00 America/Los_Angeles";
"receipt_creation_date" = "2016-09-21 18:46:39 Etc/GMT";
"receipt_creation_date_ms" = 1474483599000;
"receipt_creation_date_pst" = "2016-09-21 11:46:39 America/Los_Angeles";
"receipt_type" = ProductionSandbox;
"request_date" = "2016-09-22 18:37:41 Etc/GMT";
"request_date_ms" = 1474569461861;
"request_date_pst" = "2016-09-22 11:37:41 America/Los_Angeles";
"version_external_identifier" = 0;
};
status = 0;
}