首页 文章

iOS测试App Receipt验证

提问于
浏览
14

有很多关于如何使用沙盒测试器帐户测试应用内购买收据验证的示例 .

但付费应用程序本身的收据如何?我们如何在开发环境中获得App Receipt?

我想做两件事:

  • 防止非购买应用程序的用户运行我们的应用程序的非法复制 . 正如我所看到的那样,检测到连接的iTune帐户的应用程序并不拥有应用程序(它向用户显示警告他们没有应用程序,但是他们无法阻止用户继续使用该应用程序)

  • 将应用购买收据发送到我们的服务器 . 我们想知道他们何时购买我们的应用程序,他们带来了什么版本的应用程序 .

3 回答

  • 5

    答案的大多数部分都可以在Apple的文档中找到here . 但是存在差距,而Objective-c代码正在使用弃用的方法 .

    此Swift 3代码显示了如何获取App Receipt并将其发送到应用商店进行验证 . 在保存所需数据之前,您绝对应该使用应用商店验证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

    • 在测试期间,出现提示时,使用您的沙箱用户

    这是代码 . 快乐的道路流得很好 . 错误和故障点只是打印或评论 . 根据需要处理这些问题 .

    这部分 grab 了应用收据 . 如果不存在(在测试时会发生),它会要求应用商店刷新 .

    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;
    }
    
  • 27

    我假设你知道如何进行InApp购买 .

    交易完成后,我们需要验证收据 .

    - (void)completeTransaction:(SKPaymentTransaction *)transaction 
    {
        NSLog(@"completeTransaction...");
    
        [appDelegate setLoadingText:VALIDATING_RECEIPT_MSG];
        [self validateReceiptForTransaction];
    }
    

    成功购买产品后,需要对其进行验证 . 服务器为我们这样做,我们只需要传递Apple服务器返回的Receipt数据 .

    -(void)validateReceiptForTransaction
    {
        /* Load the receipt from the app bundle. */
    
        NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
        NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    
        if (!receipt) { 
            /* No local receipt -- handle the error. */
        }
    
        /* ... Send the receipt data to your server ... */
    
        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 ... */
                                   }
                               }];
    }
    

    响应的有效负载是一个JSON对象,包含以下键和值:

    status:

    如果收据有效,则为0,或者下面提到的错误代码之一:

    enter image description here

    对于iOS 6样式的交易收据,状态代码反映特定交易的收据的状态 .

    对于iOS 7样式的应用程序收据,状态代码将反映整个应用程序收据的状态 . 例如,如果您发送包含过期订阅的有效应用程序收据,则响应为0,因为整个收据有效 .

    receipt:

    已发送以进行验证的收据的JSON表示形式 .

    记住:


    编辑1

    不推荐使用 transactionReceipt :首先在iOS 7.0中弃用

    if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_6_1) {
        // iOS 6.1 or earlier.
        // Use SKPaymentTransaction's transactionReceipt.
    
    } else {
        // iOS 7 or later.
    
        NSURL *receiptFileURL = nil;
        NSBundle *bundle = [NSBundle mainBundle];
        if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
    
            // Get the transaction receipt file path location in the app bundle.
            receiptFileURL = [bundle appStoreReceiptURL];
    
            // Read in the contents of the transaction file.
    
        } else {
            /* Fall back to deprecated transaction receipt,
               which is still available in iOS 7.
               Use SKPaymentTransaction's transactionReceipt. */
        }
    
    }
    
  • 0

    如果您想测试应用程序内部在沙箱环境中进行收据验证,请考虑在沙箱更新间隔是

    1周3分钟1个月5分钟2个月10分钟3个月15分钟6个月30分钟1个1小时

    最好的方法是验证收据是将您的服务器与Apple服务器进行通信以进行验证 .

相关问题