validationappReceiptStoreURL返回21002状态

我已经创build了一个类来处理In-App Purchase中的购买以及validation收据。 前一段时间,我曾经使用SKPaymentTransaction上的transactionReceipt属性,但已经更新了我的代码,现在在[NSBundle mainBundle]上使用appStoreReceiptURL。

基本上,我的收据似乎是以可接受的方式发送到苹果的服务器,但我一直得到21002的状态代码。在自动更新订阅中,我知道这意味着收据不是可接受的格式,但是我有不知道这个状态对于应用内购买收据是什么意思。

以下是validation收据的本地方法:

/** * Validates the receipt. * * @param transaction The transaction triggering the validation of the receipt. */ - (void)validateReceiptForTransaction:(SKPaymentTransaction *)transaction { // get the product for the transaction IAPProduct *product = self.internalProducts[transaction.payment.productIdentifier]; // get the receipt as a base64 encoded string NSData *receiptData = [[NSData alloc] initWithContentsOfURL:[NSBundle mainBundle].appStoreReceiptURL]; NSString *receipt = [receiptData base64EncodedStringWithOptions:kNilOptions]; NSLog(@"Receipt: %@", receipt); // determine the url for the receipt verification server NSURL *verificationURL = [[NSURL alloc] initWithString:IAPHelperServerBaseURL]; verificationURL = [verificationURL URLByAppendingPathComponent:IAPHelperServerReceiptVerificationComponent]; NSMutableURLRequest *urlRequest = [[NSMutableURLRequest alloc] initWithURL:verificationURL]; urlRequest.HTTPMethod = @"POST"; NSDictionary *httpBody = @{@"receipt" : receipt, @"sandbox" : @(1)}; urlRequest.HTTPBody = [NSKeyedArchiver archivedDataWithRootObject:httpBody]; [NSURLConnection sendAsynchronousRequest:urlRequest queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { // create a block to be called whenever a filue is hit void (^failureBlock)(NSString *failureMessage) = ^void(NSString *failureMessage) { [[NSOperationQueue mainQueue] addOperationWithBlock: ^{ // log the failure message NSLog(@"%@", failureMessage); // if we have aready tried refreshing the receipt then we close the transaction to avoid loops if (self.transactionToValidate) product.purchaseInProgress = NO, [[SKPaymentQueue defaultQueue] finishTransaction:transaction], [self notifyStatus:@"Validation failed." forProduct:product], self.transactionToValidate = nil; // if we haven't tried yet, we'll refresh the receipt and then attempt a second validation else self.transactionToValidate = transaction, [self refreshReceipt]; }]; }; // check for an error whilst contacting the server if (connectionError) { failureBlock([[NSString alloc] initWithFormat:@"Failure connecting to server: %@", connectionError]); return; } // cast the response appropriately NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; // parse the JSON NSError *jsonError; NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError]; // if the data did not parse correctly we fail out if (!json) { NSString *responseString = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode]; NSString *failureMessage = [[NSString alloc] initWithFormat:@"Failure parsing JSON: %@\nServer Response: %@ (%@)", data, responseString, @(httpResponse.statusCode)]; failureBlock(failureMessage); return; } // if the JSON was successfully parsed pull out status code to check for verification success NSInteger statusCode = [json[@"status"] integerValue]; NSString *errorDescription = json[@"error"]; // if the verification did not succeed we fail out if (statusCode != 0) { NSString *failureMessage = [[NSString alloc] initWithFormat:@"Failure verifying receipt: %@", errorDescription]; failureBlock(failureMessage); } // otherwise we have succeded, yay else NSLog(@"Successfully verified receipt."), [self provideContentForCompletedTransaction:transaction productIdentifier:transaction.payment.productIdentifier]; }]; } 

服务器上重要的PHPfunction是这样的:

  /** * Validates a given receipt and returns the result. * * @param receipt Base64-encoded receipt. * @param sandbox Boolean indicating whether to use sandbox servers or production servers. * * @return Whether the reciept is valid or not. */ function validateReceipt($receipt, $sandbox) { // determine url for store based on if this is production or development if ($sandbox) $store = 'https://sandbox.itunes.apple.com/verifyReceipt'; else $store = 'https://buy.itunes.apple.com/verifyReceipt'; // set up json-encoded dictionary with receipt data for apple receipt validator $postData = json_encode(array('receipt-data' => $receipt)); // use curl library to perform web request $curlHandle = curl_init($store); // we want results returned as string, the request to be a post, and the json data to be in the post fields curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlHandle, CURLOPT_POST, true); curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postData); $encodedResponse = curl_exec($curlHandle); curl_close($curlHandle); // if we received no response we return the error if (!$encodedResponse) return result(ERROR_VERIFICATION_NO_RESPONSE, 'Payment could not be verified - no response data. This was sandbox? ' . ($sandbox ? 'YES' : 'NO')); // decode json response and get the data $response = json_decode($encodedResponse); $status = $response->{'status'}; $decodedReceipt = $response->{'receipt'}; // if status code is not 0 there was an error validation receipt if ($status) return result(ERROR_VERIFICATION_FAILED, 'Payment could not be verified (status = ' . $status . ').'); // log the returned receipt from validator logToFile(print_r($decodedReceipt, true)); // pull out product id, transaction id and original transaction id from infro trurned by apple $productID = $decodedReceipt->{'product_id'}; $transactionID = $decodedReceipt->{'transaction_id'}; $originalTransactionID = $decodedReceipt->{'original_transaction_id'}; // make sure product id has expected prefix or we bail if (!beginsWith($productID, PRODUCT_ID_PREFIX)) return result(ERROR_INVALID_PRODUCT_ID, 'Invalid Product Identifier'); // get any existing record of this transaction id from our database $db = Database::get(); $statement = $db->prepare('SELECT * FROM transactions WHERE transaction_id = ?'); $statement->bindParam(1, $transactionID, PDO::PARAM_STR, 32); $statement->execute(); // if we have handled this transaction before return a failure if ($statement->rowCount()) { logToFile("Already processed $transactionID."); return result(ERROR_TRANSACTION_ALREADY_PROCESSED, 'Already processed this transaction.'); } // otherwise we insert this new transaction into the database else { logToFile("Adding $transactionID."); $statement = $db->prepare('INSERT INTO transactions(transaction_id, product_id, original_transaction_id) VALUES (?, ?, ?)'); $statement->bindParam(1, $transactionID, PDO::PARAM_STR, 32); $statement->bindParam(2, $productID, PDO::PARAM_STR, 32); $statement->bindParam(3, $originalTransactionID, PDO::PARAM_STR, 32); $statement->execute(); } return result(SUCCESS); } 

正在执行的实际PHP脚本是:

  $receipt = $_POST['receipt']; $sandbox = $_POST['sandbox']; $returnValue = validateReceipt($receipt, $sandbox); header('content-type: application/json; charset=utf-8'); echo json_encode($returnValue); 

比较你的PHP与我(我知道工作)是困难的,因为我使用HTTPRequest而不是原始的curlAPI。 不过,在我看来,你只是将“{receipt-data:..}”JSONstring设置为POST数据中的一个字段 ,而不是原始的POST数据本身,这正是我的代码所做的。

 curl_setopt($curlHandle, CURLOPT_POST, true); curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postData); // Possible problem $encodedResponse = curl_exec($curlHandle); 

相比:

 $postData = '{"receipt-data" : "'.$receipt.'"}'; // yay one-off JSON serialization! $request = new HTTPRequest('https://sandbox.itunes.apple.com/verifyReceipt', HTTP_METH_POST); $request->setBody($postData); // Relevant difference... $request->send(); $encodedResponse = $request->getResponseBody(); 

我已经改变了我的variables名称,使它们与你的例子相匹配。

我认为Morteza M是正确的。 我做了一个testing,得到了答复(JSON):

 { 'status': 'environment': 'Sandbox' 'receipt': { 'download_id': .... 'in_app": { 'product_id': .... } .... } }