0

I have an application which supports subscription as well as pay as go features. I have facing issues with receipt validation at server side. If

  1. user has not subscribed and tried to download content using pay as you go i am getting 21002 error(passing shared secret for validation)
  2. If user subscribed and it was expired. now user tried to download pay as you go then response was 21004(not passing shared secret key)

I am unable to understand when I should pass shared secret to apple server. Since we have only one apple receipt which contains consumable and subscription receipt information.

Any help?

naresh
  • 627
  • 1
  • 7
  • 28

2 Answers2

1

Code 21002 means that the JSON you are sending to apple which has your shared secret and your receipt data is "misformed" or not in the format apple wants it.

enter image description here

Also Try removing from receipt the characters '\n' and '\r' and replacing '+' with'%2B' before sending it to the server. Something like this:

 NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
 NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
 NSString *receiptDataString = [receipt base64EncodedStringWithOptions:0];
 receiptDataString=[receiptDataString stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];
 receiptDataString=[receiptDataString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
 receiptDataString=[receiptDataString stringByReplacingOccurrencesOfString:@"\r" withString:@""];
 NSString *postDataString = [NSString stringWithFormat:@"receipt-data=%@", receiptDataString];
 NSString *length = [NSString stringWithFormat:@"%lu", (unsigned long)[postDataString length]];
 [request setValue:length forHTTPHeaderField:@"Content-Length"];
 [request setHTTPBody:[postDataString dataUsingEncoding:NSASCIIStringEncoding]];

Reference

Code 21004 this means you have a logical error in the code, somehow you are not passing the key in that case

Community
  • 1
  • 1
Mostafa Sultan
  • 2,268
  • 2
  • 20
  • 36
  • I could solve a similar issue with removing `\n\r` and leaving everything else in the receipt as it is. – Timo Mar 08 '17 at 16:10
  • Recently there is a bug - that causes Apple to return 21004 status in the sandbox even if you are using the right shared secret code. – Cherpak Evgeny Apr 08 '17 at 05:03
1
func receiptValidation() {

    let SUBSCRIPTION_SECRET = "secret"
    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://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 queue = DispatchQueue(label: "itunesConnect")
            queue.async {
                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, options: .allowFragments) as? NSDictionary
                            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)")
        }
    }
}
Meloman
  • 3,558
  • 3
  • 41
  • 51
preeti
  • 11
  • 1