У меня очень странный случай, когда мое приложение продолжает отклоняться при проверке приложения, в то время как все работает с моей отладкой iPhone и с несколькими пользователями testflight.
Я не уверен, как я могу Попробуй это? Когда это не работает для группы проверки, это работает и для меня? Я отправляю новую версию каждый день, чтобы попытаться отладить, и этот процесс очень трудоемкий.
Судя по снимкам экрана с сообщениями об ошибках, полученными от группы проверки приложений, проверка ответа на получение равна нулю. AppStoreReceiptURL, похоже, возвращает действительный URLReceipt, но после этого проверка ReceiptResponse равна нулю. Поскольку он возвращается как nil вместо кода ошибки 21007, среда является производственной.
Я участвовал в этом с 24 мая до сегодняшнего дня, поэтому, пожалуйста, любой, кто может помочь, я был бы очень признателен.
Здесь вся необходимая информация / код:
Соответствующий код:
-(void)completeTransaction:(SKPaymentTransaction*)transaction {
NSLog(@"StoreKitTransactionObserver:CompleteTransaction");
//Load the receipt from the app bundle.
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
if (![[NSFileManager defaultManager] fileExistsAtPath:[receiptURL path]]) {
NSString* message = [NSString stringWithFormat: @"Could not find local receipt file. ReceiptURL is %@ \n(in getStoreReceipt function).\nYou are probably testing in a sandbox environment.\n We are trying to refresh receipt. If refresh succesful, Please press the restore button (you might have already recieved an alert). If the problem presists, try restart the app.", [receiptURL absoluteURL]];
SKReceiptRefreshRequest *refreshReceiptRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
[refreshReceiptRequest setDelegate: (id)self];
[refreshReceiptRequest start];
dispatch_async(dispatch_get_main_queue(), ^(){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Error!"
message:message
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alert show];
});
return;
}
receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
if (!receipt) {
NSLog(@"Eror, couldn't find receipt!");
} else {
NSLog(@"Received receipt!");
}
//Checking Production first. If get error then trying Sandbox. ReceiptResponse declared as class Variable.
receiptResponse = [self getStoreReceipt:NO];
NSString* environment = @"Environment: Production.";
if ([[receiptResponse valueForKey:@"status"] intValue] == 21007) {
NSLog(@"receiptResponse status 21007. Trying sandbox. Status: %d", [[receiptResponse valueForKey:@"status"] intValue]);
receiptResponse = [self getStoreReceipt:YES];
environment = @"Environment: Sandbox.";
}
NSLog(@"String of [ReceiptResponse valueForKey:@'status']] is %@", [receiptResponse valueForKey:@"status"]);
if ([receiptResponse objectForKey:@"status"] == nil || [[receiptResponse valueForKey:@"status"] intValue] != 0) {
NSString* errorMessage;
NSString* receivedRecieptData;
if (!receipt) {
receivedRecieptData = @"receipt data is nil";
} else {
receivedRecieptData = @"receipt data downloaded";
}
if ([receiptResponse objectForKey:@"status"] == nil) errorMessage = [NSString stringWithFormat: @"Something went wrong. Cannot validate receipt. Receipt Response is nil. %@. /nReceipt URL is %@ /nThis error is only generated to the App Review Team. For all beta testers the App is working as it should. If there are more errors kindly take screenshots. Please press restore and take screeenshots of every error. If you can send me the log file it's better because it's very hard to debug what is the problem on your environment. %@ ", environment, [receiptURL absoluteURL],receivedRecieptData];
else errorMessage = [NSString stringWithFormat: @"Something went wrong. Receipt Response Error Status %d in %@", [[receiptResponse valueForKey:@"status"] intValue], environment];
NSLog(@"%@",errorMessage);
dispatch_async(dispatch_get_main_queue(), ^(){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Error!"
message:errorMessage
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alert show];
});
return;
}
// #if USE_ICLOUD_STORAGE
// NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
// #else
// NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
// #endif
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSArray *savedReceipts = [storage arrayForKey:@"origialreceipts"];
if (!savedReceipts) {
// Storing the first receipt
[storage setObject:@[receipt] forKey:@"origialreceipts"];
} else {
// Adding another receipt
NSArray *updatedReceipts = [savedReceipts arrayByAddingObject:receipt];
NSLog(@"Receipt array size is : %d" , (int)[updatedReceipts count]);
[storage setObject:updatedReceipts forKey:@"origialreceipts"];
}
[storage setObject:receiptResponse forKey:@"originalReceiptDictioanry"];
//[storage synchronize];
NSString* message;
//Writing log for archives
NSString *pathToArchives = [iDiamondsUtilis pathToArchives];
NSString *filePath;
filePath = [pathToArchives stringByAppendingPathComponent:@"OriginalReceiptsArray.plist"];
if ([[storage arrayForKey:@"origialreceipts"] writeToFile:filePath atomically:YES]) NSLog(@"OriginalReceiptsArray.plist saved successfully in Archives folder.");
filePath = [pathToArchives stringByAppendingPathComponent:@"ReceiptResponseDictionary.plist"];
if ([receiptResponse writeToFile:filePath atomically:YES]) NSLog(@"ReceiptResponseDictionary.plist saved successfully in Archives folder.");
bool showMessage ;
if (transaction != nil) {
if ([StoreKitTransactionObserver isSubscriptionValid]) {
message = @"Thank you for purchasing subscription!\n\n";
message = [NSString stringWithFormat:@"%@Subscription start date: \n%@ \n\nSubscription Expiration Date:\n%@",message,[StoreKitTransactionObserver formattedPurchaseString],[StoreKitTransactionObserver formattedExpirationString]];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"alreadyCheckedForExtension"];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"wasSubscribedBefore"];
} else {
message = [NSString stringWithFormat:@"There was an error processing the subscription. Receipt Response Error Status %d in enviroment %@ \n%@", [[receiptResponse valueForKey:@"status"] intValue], [receiptResponse objectForKey:@"environment"], [self printReciptResponse:receiptResponse] ];
}
if (_subVC != nil) showMessage = YES;
else showMessage = NO;
}
else {
if ([StoreKitTransactionObserver isSubscriptionValid]) {
message = @"Thank you for restoring.";
//if (_subVC != nil) showMessage = YES;
//else showMessage = NO;
showMessage = YES;
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:@"alreadyCheckedForExtension"];
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"wasSubscribedBefore"];
}
else {
message = @"Your subscription is not valid. Please press purchase to buy a subscription";
showMessage = YES;
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:@"alreadyCheckedForExtension"];
}
}
NSLog(@"%@",message);
if (showMessage)
dispatch_async(dispatch_get_main_queue(), ^(){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Attention!"
message:message
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alert show];
});
dispatch_async(dispatch_get_main_queue(), ^{
[self->uvc reloadAllDataInTableView];
});
}
+(bool)isSubscriptionValid {
#ifdef DEBUG
//return YES;
#endif
NSDate *expireDate = [StoreKitTransactionObserver getExpirationNSDate];
if (expireDate == nil) return NO;
bool isSubscriptionValid;
if ([expireDate timeIntervalSinceNow] < 0) isSubscriptionValid = NO; // Date has passed
else isSubscriptionValid = YES;
return isSubscriptionValid;
}
+(NSDate*)getExpirationNSDate{
NSDictionary *latestInfo = [StoreKitTransactionObserver return_Latest_Receipt_Info];
//subscritionrewnalDate = [latestInfo objectForKey:@"original_purchase_date"];
NSString* subscrtionStringExpireDate = [latestInfo objectForKey:@"expires_date"];
if (subscrtionStringExpireDate == nil) return nil;
subscrtionStringExpireDate = [subscrtionStringExpireDate stringByReplacingOccurrencesOfString:@"Etc/GMT" withString:@"GMT"];
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateformatter setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
NSDate *expireDate = [dateformatter dateFromString: subscrtionStringExpireDate];
NSLog(@"The date: %@", [dateformatter stringFromDate:expireDate]);
return expireDate;
}
+(NSString*)formattedExpirationString {
NSDictionary *latestInfo = [StoreKitTransactionObserver return_Latest_Receipt_Info];
NSString* subscrtionStringExpireDate = [latestInfo objectForKey:@"expires_date"];
//subscritionrewnalDate = [latestInfo objectForKey:@"original_purchase_date"];
subscrtionStringExpireDate = [subscrtionStringExpireDate stringByReplacingOccurrencesOfString:@"Etc/GMT" withString:@"GMT"];
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateformatter setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
NSDate *expireDate = [dateformatter dateFromString: subscrtionStringExpireDate];
[dateformatter setDateFormat:@"MMM dd, yyyy HH:mm:ss z"];
return [dateformatter stringFromDate:expireDate];
}
+(NSString*)formattedPurchaseString {
NSDictionary *latestInfo = [StoreKitTransactionObserver return_Latest_Receipt_Info];
//NSString* subscrtionStringExpireDate = [latestInfo objectForKey:@"expires_date"];
NSString* subscritionrewnalDate = [latestInfo objectForKey:@"purchase_date"];
subscritionrewnalDate = [subscritionrewnalDate stringByReplacingOccurrencesOfString:@"Etc/GMT" withString:@"GMT"];
NSDateFormatter *dateformatter=[[NSDateFormatter alloc] init];
[dateformatter setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];
NSDate *expireDate = [dateformatter dateFromString: subscritionrewnalDate];
[dateformatter setDateFormat:@"MMM dd, yyyy HH:mm:ss z"];
return [dateformatter stringFromDate:expireDate];
}
+(NSDictionary*)returnOriginalReciptDictionaryInUserDefault {
//#if USE_ICLOUD_STORAGE
// NSUbiquitousKeyValueStore *storage = [NSUbiquitousKeyValueStore defaultStore];
//#else
// NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
//#endif
NSUserDefaults *storage = [NSUserDefaults standardUserDefaults];
NSDictionary* originalReceiptDictioanry = [storage objectForKey:@"originalReceiptDictioanry"];
return originalReceiptDictioanry;
}
+(NSDictionary*)return_Latest_Receipt_Info {
NSDictionary* originalReceiptDictioanry = [StoreKitTransactionObserver returnOriginalReciptDictionaryInUserDefault];
NSArray* latest_receipt_info = [originalReceiptDictioanry objectForKey:@"latest_receipt_info"];
NSLog(@"latest_receipt_info Array size is %d", (int)latest_receipt_info.count);
return [latest_receipt_info lastObject];
}
+(int)return_Latest_Receipt_status {
NSDictionary* originalReceiptDictioanry = [StoreKitTransactionObserver returnOriginalReciptDictionaryInUserDefault];
return [[originalReceiptDictioanry objectForKey:@"status"] intValue];
}
В квитанции getStore:
- (NSDictionary *) getStoreReceipt:(BOOL)sandbox {
NSDictionary *dictionary = nil;
BOOL gotreceipt = false;
NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];
NSLog(@"Receipt URL is %@", [receiptUrl absoluteURL]);
NSData *receiptData = [NSData dataWithContentsOfURL:receiptUrl];
if (!receiptData) {
NSString* message = [NSString stringWithFormat: @"Didn't get receiptData. ReceiptURL is %@ \n(in getStoreReceipt function).", [receiptUrl absoluteURL]];
gotreceipt = false;
dispatch_async(dispatch_get_main_queue(), ^(){
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Error!"
message:message
delegate:nil
cancelButtonTitle:@"Ok"
otherButtonTitles:nil];
[alert show];
});
} else {
//NSString *receiptString = [self base64forData:receiptData]; old function
NSString *password = @"SomeSecret";
dictionary = @{ @"receipt-data": [receiptData base64EncodedStringWithOptions:0], @"password": password, @"exclude-old-transactions": @YES };
//NSString *postData = [self getJsonStringFromDictionary:dictionary];
NSError *error = nil;
NSData *postData = [NSJSONSerialization dataWithJSONObject:dictionary options:NSJSONWritingPrettyPrinted error:&error];
NSString *postString = @"";
if (!postData) {
NSLog(@"Got an error: %@", error);
} else {
postString = [[NSString alloc] initWithData:postData encoding:NSUTF8StringEncoding];
}
NSString *urlSting = @"https://buy.itunes.apple.com/verifyReceipt";
if (sandbox) urlSting = @"https://sandbox.itunes.apple.com/verifyReceipt";
NSLog(@"Verifying with: %@", urlSting );
dictionary = [self getJsonDictionaryWithPostFromUrlString:urlSting andDataString:postString];
if ([dictionary objectForKey:@"status"] != nil) {
if ([[dictionary objectForKey:@"status"] intValue] == 0) {
gotreceipt = true;
}
}
}
if (!gotreceipt) {
NSLog(@"StoreKitTransactionObserver: getStoreReceipt: couldn't validate receipt!");
}
return dictionary;
}
Вот скриншот.
введите описание изображения здесь
Также важно отметить:
a) Последняя версия приложения была отправлена в 2016 году. Все работало отлично до 24 мая, когда ВСЕ пользователи получили ошибку сообщение о том, что их подписка недействительна. После разговоров с командой технической поддержки Apple они заявили, что магазин приложений изменил порядок массива квитанций и что мое приложение было неправильно запрограммировано, потому что я не включил свойство excludes_old_trasactions при проверке appStoreReceipt.
б) Так случилось, что в тот же день 24 мая у меня были неподписанные контракты, и я считаю (не уверен), что другие мои приложения также перестали работать. Я подписал контракт, но приложение с возобновляемой подпиской не работало, и с тех пор пользователи не могут использовать приложение.
c) Профиль обеспечения, который я использовал для приложения с момента его загрузки в 2009 году, так как как и в обновлении 2016 года, имел старый префикс приложения, а не современный «идентификатор команды».
d) При попытке отправить первое обновление через две недели go я не мог подпишите мое приложение. После долгого дня головной боли оказалось, что возможность покупки в приложении для моего исходного идентификатора пакета была отключена / выделена серым цветом, и я не мог подписать свое приложение, потому что у меня была возможность покупки в приложении.
д) поэтому я перешел на новый «современный способ» подписания своего приложения с помощью идентификатора команды. и я продолжаю получать предупреждение о несоответствии доступа к связке ключей при загрузке приложения. Apple сказала мне, что это нормально, а НЕ причина проблемы, но я подумал, что об этом стоит упомянуть.
РЕДАКТИРОВАТЬ: я отредактировал приведенную выше статью, чтобы отразить изменения, которые я внес в код, и снимок экрана, предоставленный группой проверки приложения.