Intereting Posts

Проверка статуса приложения AppReceiptStoreURL 21002

Я создал класс, который обрабатывает покупки в In-App Purchase, а также проверку чеков. Некоторое время назад я использовал свойство transactionReceipt в SKPaymentTransaction, но обновил свой код в достаточной степени и теперь использую appStoreReceiptURL в [NSBundle mainBundle].

В принципе кажется, что моя квитанция отправляется на сервер Apple приемлемым образом, но я продолжаю получать код состояния 21002. В автоматических возобновляемых подписках я знаю, что это означает, что квитанция не в приемлемом формате, однако у меня есть не знаю, что означает этот статус в отношении квитанции о покупке в приложении.

Вот локальный метод, подтверждающий получение:

/** * 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]; }]; } 

Важная функция PHP на сервере делает следующее:

  /** * 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); 

Solutions Collecting From Web of "Проверка статуса приложения AppReceiptStoreURL 21002"

Сравнение вашего PHP с моим (что я знаю, работает) сложно, потому что я использую HTTPRequest, а не raw curl API. Однако мне кажется, что вы устанавливаете строку «{receipt-data: ..}» JSON как просто поле в данных 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(); 

Я немного изменил имена переменных, чтобы они соответствовали вашему примеру.

Я думаю, что Мортеза М верна. Я сделал тест и получил ответ (JSON):

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