Reputation: 1792
I am developing a iOS application in swift that uses In App Purchase. I have only one product, which is an auto-renewable subscription, that also provides a 7 days trial. According to most of the documentation I have found, first of all Apple docs, the best strategy to follow is to use a trusted-server for receipt validation. I have set up everything in my project and on my trusted server, and the communication now works, at least without errors. My code in the app is the following:
func performServerToServerValidation() {
guard let receiptUrl = Bundle.main.appStoreReceiptURL,
let receiptData = try? Data(contentsOf: receiptUrl)
else {
return
}
let baseUrlString = "https://<my.trusted.server>/validation/verifyReceipt.php"
let theRequest = AFHTTPRequestSerializer().request(withMethod: "POST", urlString: baseUrlString, parameters: nil, error: nil)
theRequest.timeoutInterval = 20.0
theRequest.httpBody = receiptData.base64EncodedString().data(using: String.Encoding.utf8)
operation = AFHTTPRequestOperation(request: theRequest as URLRequest)
operation?.responseSerializer = AFHTTPResponseSerializer()
var dict: Dictionary<String,Any>!
operation?.setCompletionBlockWithSuccess({ operation, responseObject in
if let responseObject = responseObject as? Data {
do {
dict = try JSONSerialization.jsonObject(with: responseObject, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, Any>
print("Finished connection with the trusted server")
if let receiptBase64String = dict["latest_receipt"] as? String {
let decodedData = Data(base64Encoded: receiptBase64String)!
let newReceipt = Receipt.init(data: decodedData)
print("\(newReceipt!)")
self.validateReceipt(receipt: newReceipt)
self.analyzeInAppPurchasesInReceipt()
}
} catch {
}
}
})
// 5
operation?.start()
}
My code on the server is the following:
<?php
function getReceiptData($receipt)
{
$fh = fopen('showme.txt',w);
fwrite($fh,$receipt);
fclose($fh);
$endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt';
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $receipt);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
curl_close($ch);
$msg = $response.' - '.$errno.' - '.$errmsg;
echo $response;
}
foreach ($_POST as $key=>$value){
$newcontent .= $key.' '.$value;
}
$new = trim($newcontent);
$new = trim($newcontent);
$new = str_replace('_','+',$new);
$new = str_replace(' =','==',$new);
if (substr_count($new,'=') == 0){
if (strpos('=',$new) === false){
$new .= '=';
}
}
$new = '{"receipt-data":"'.$new.'","password":"<my_shared_secret>"}';
$info = getReceiptData($new);
?>
Now comes my problem. I would like to take advantage of the server-in-the-middle, for example to determine if a user has subscribed on another device, by using the server validation. My testing flow is as follows:
1) I have two devices, A and B.
2) I delete any existing app build from both devices. Ok.
3) I create a new sandbox user on iTunesConnect, and I use that to log in on both device's sandbox account in settings. Ok.
4) I launch the app from Xcode on device A, as it is the first launch it does not have a receipt, so it gets one through SKRefreshReceiptRequest. Ok.
5) I launch the app from Xcode on device B, and it also does not have a receipt since it is the first launch, so it gets one through SKRefreshReceiptRequest. Ok.
6) I buy the subscription on device B. Features are delivered immediately. All Ok.
7) Now, I launch the app again on device A. It has a receipt (we launched it at point 4)) but that receipt is outdated because it does not include the product I have purchased on device B. PerformServerToServerValidation is called, but, unfortunately, the returned JSON does not contain a "latest_receipt" field. it only contains the "receipt", "status", and "environment" keys:
So I cannot ascertain if the subscription has already been purchased on another device. I must be missing something important, but shouldn't be one of the advantages of Server receipt validation to have always the latest receipt ? One important thing to know is that if I restore purchases or try to purchase again, then, if I call the server validation code, it works perfectly and has the "latest_receipt" key. But what is the sense of this all ? I would like to use the server validation to avoid to force the user to do a restore purchases or purchase the item again, even if not charged. Thanks for any help.
Upvotes: 0
Views: 272
Reputation: 114826
The purpose of server-side receipt validation is to protect yourself against users injecting a forged receipt. The server simply validates the receipt that you send it. It doesn't use the receipt you send to check if there is a later corresponding receipt available.
The flow you describe, where you need to restore purchases on Device B, is the expected process.
You may be confusing server-side receipt validation with server notifications, where Apple can send your server subscription status updates
Upvotes: 2