How to set up a server for IAP receipt validation

I'm currently working on implementing an auto-renew subscription IAP into my app. Doing lots of research I've discovered I need a server for the app to talk to which talks to the app store which responds to the server which responds to the app which the app than analyzes to either lock or unlock content. No one says how to setup a server for this purpose. I'm pulling my hair out trying to figure this out. Can someone PLEASE HELP ME GET THIS FIGURED OUT.

Rather than creating your own server, you can decode the receipt yourself in the app or you can send the receipt directly to the Apple servers from the device. (The latter is not completely secure from man-in-the-middle hacks unless you do various things with date stamping.)

What would you recommend? I'm not really afraid of hackers for what my app does. The app I'm making will be free but have limited use. Once you suscribe all features will be unlocked for month-year (depending on what you choose).

Try to do the decoding in the app as described in this document:

https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW2


If that's too hard then send the receipt to the Apple servers directly from the app itself.

So, first off thanks PBK for the link. I'm still trying understand what's going on right now though. I have more questions than answers. For instance, consider this:


NSData *receipt; // Sent to the server by the device

// Create the JSON object that describes the request
NSError *error;
NSDictionary *requestContents = @{
  @"receipt-data": [receipt base64EncodedStringWithOptions:0]
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
  options:0
  error:&error];

if (!requestData) { / ... Handle error ... */ }

// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:requestData];

// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
  completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
  if (connectionError) {
  / ... Handle error ... */
  } else {
  NSError *error;
  NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
  if (!jsonResponse) { / ... Handle error ...*/ }
  / ... Send a response back to the device ... */
  }
}];


At the top there's a commented line stating that "receipt" of type NSData is sent to a personal server. But where is their line of code for that? What am I missing or misunderstanding? Another thing is this answer I found from Yasin Aktimur on Stack Overflow:


func receiptValidation() {
        let SUBSCRIPTION_SECRET = "mysecret"
        let receiptPath = Bundle.main.appStoreReceiptURL?.path
        if FileManager.default.fileExists(atPath: receiptPath!){
            var receiptData:NSData?
            do{
                receiptData = try NSData(contentsOf: Bundle.main.appStoreReceiptURL!, options: NSData.ReadingOptions.alwaysMapped)
            }
            catch{
                print("ERROR: " + error.localizedDescription)
            }
            let base64encodedReceipt = receiptData?.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
          
            let requestDictionary = ["receipt-data":base64encodedReceipt!,"password":SUBSCRIPTION_SECRET]
          
            guard JSONSerialization.isValidJSONObject(requestDictionary) else {  print("requestDictionary is not valid JSON");  return }
            do {
                let requestData = try JSONSerialization.data(withJSONObject: requestDictionary)
                let validationURLString = "https:/
                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)")
                            /
                        } catch let error as NSError {
                            print("json serialization failed with error: \(error)")
                        }
                    } else {
                        print("the upload task returned an error: \(error ?? "couldn't upload" as! Error)")
                    }
                }
                task.resume()
            } catch let error as NSError {
                print("json serialization failed with error: \(error)")
            }
        }
    }


This returns the complete Json file. If I replace the sandbox iTunes URL with the production one ("https://buy.itunes.apple.com/verifyReceipt"), what's the difference? I haven't tried it because I don't know what'll happen. There's a reason for Apple's sandbox! Now why exactly do I need a personal server? If the receipt is stored on the device and sent directly to Apple, why do I need it? What does it do? I'm simply a newbie who's in way over his head. I'm trying to wrap my head around what's happening. Can someone please explain how this works?

The description is in the document I refenced above.


As described in the document:

    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData=[NSData dataWithContentsOfURL:receiptURL];


> If I replace the sandbox iTunes URL with the production one ("https://buy.itunes.apple.com/verifyReceipt"), what's the difference?

Your app is signed on the device with either a sandbox signature (in development when loaded from Xcode and in TestFlight - if you use that) or a production signature (when your app is downloaded from the App Store after it is finished and approved. Based on that signature, a request to IAP goes to sanbox (and costs you nothing when you test-buy it) or to production (and costs someone something). The receipt that comes back is encoded for one or the other environment and needs to be decoded by the appropriate website. If you send a sandbox receipt to production it will generate a 21007 error.

That's make total sense. I don't know why I didn't see that, thanks. Now, to the matter of a personal server. What does that do? I've always thought that it talked to the Apple server and sent the response to the app. If sending the data directly to the Apple server returns the JSON data, what's it used for?

Accepted Answer

1) Decoding the receipt on the device itself is the best approach. There are no security flaws and it happens immediately.

2) Decoding by sending the receipt to your server is second. The transmission must be done using secure trnasmission coding. It is subject to a fairly difficult, if not impossible, security flaw which I will not disclose publicly.

3) Decoding by sending the receipt directly from the device to the Aple servers is subect to a 'man-in-the-middle' hack attack. In this attack the owner of the device sends all output to a hacker server. The owner then 'purchases' an IAP but the purchase request goes only to the hacker's server and is immediately returned with a "purchased" call to updatedTransactions. The app on the device then sends its receipt (which has no IAP in it because no IAP was actually purchased) to the hacker's server thinking it is going to the Apple servers. The hacker server sends the output to the Apple servers and receives the reply. The hacker servers then modify that reply by adding an IAP receipt and forwarding it back to the device. Such a hack was set up a few years ago and widely circulated among folks unwilling to pay for IAPs. This led to the current system of an encoded, device signed receipt capable of being decoded using #1 above.

Thanks of the detailed answer PBK. It all makes sense now. I'm planning on doing a lot of updates to my app over time, so I think I'll stick with what I've got for now and update it to decoding it locally. Now, I have one more question. If I want to learn how to create my own file to decode the receipt, where do I go? I've looked at what Apple said in the link you posted, but I don't know enough to understand what they're talking about.

Decoding the receipt is tough programming requiring some C++ and Objective-C; (forgive me, I can't resist) it's a job for for a Batman-level programmer. It will come to you in time.


The basics are in the link above. I believe there is pretty good publicly available code on GitHub.

Alfred, is that you?



Thanks again PBK! You just made my day 😁😁😁

How to set up a server for IAP receipt validation
 
 
Q