iOS IAP 无法在仅 ipv6 的网络上运行

Posted

技术标签:

【中文标题】iOS IAP 无法在仅 ipv6 的网络上运行【英文标题】:iOS IAP not working on ipv6 only network 【发布时间】:2016-06-09 12:15:23 【问题描述】:

我在 Appstore 中有一款 iPhone 游戏,我最近尝试使用最新的 Xcode 上传更新的构建,但它被拒绝了,因为 inApp-purchases 无法在仅 ipv6 的网络上运行。它在 ipv4 网络上运行良好。

//
//  ViewController.m
//

NSMutableArray * arrayOfSection;
NSMutableArray * sectionHeaders;
NSString *error;

#import "CoinsController.h"
#import "NSString+SBJSON.h"

#import <CommonCrypto/CommonDigest.h>

@implementation CoinsController

@synthesize bkg;

-(void) callPurchaseId:(NSString*)iapId amount:(NSUInteger)ncoins
    SKPayment *payment = [SKPayment paymentWithProductIdentifier:iapId];
    //[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
    [[SKPaymentQueue defaultQueue] addPayment:payment];

    [UIView setAnimationDuration:0.5];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
    viewLoading.alpha = 0.9;
    [UIView commitAnimations];
    loadingtext.text = @"Processing Purchase";
    NSLog(@"Processing Purhcase");

    //    currentGem = [NSString stringWithFormat:@"vegas%@", @"80k"];
    [CommonUtilities encryptString:[NSString stringWithFormat:@"%lu", (unsigned long)ncoins]:@"c"];

    NSLog(@"Processing");

- (IBAction)purchaseCoins:(id)sender

    UIButton *button = (UIButton *)sender;

    NSLog(@"%li", (long)button.tag);
    switch (button.tag) 
        case 1001:
            [self callPurchaseId:IAP1 amount:IAP_AMT_1];
            break;
        case 1002:
            [self callPurchaseId:IAP2 amount:IAP_AMT_2];
            break;
        case 1003:
            [self callPurchaseId:IAP3 amount:IAP_AMT_3];
            break;
        case 1004:
            [self callPurchaseId:IAP4 amount:IAP_AMT_4];
            break;
        case 1005:
            [self callPurchaseId:IAP5 amount:IAP_AMT_5];
            break;
        case 1006:
            [self callPurchaseId:IAP6 amount:IAP_AMT_6];
            break;
        default:
            break;
    


-(void) viewWillAppear:(BOOL)animated
    [super viewWillAppear:animated];
    NSNumberFormatter* nf = [[NSNumberFormatter alloc] init];
    nf.usesGroupingSeparator = YES;
    nf.groupingSize = 3;
    ((UILabel*)[self.view viewWithTag:2001]).text = [NSString stringWithFormat:@"%@ Coins", [nf stringFromNumber:[NSNumber numberWithInteger:IAP_AMT_1]]];
    ((UILabel*)[self.view viewWithTag:2002]).text = [NSString stringWithFormat:@"%@ Coins", [nf stringFromNumber:[NSNumber numberWithInteger:IAP_AMT_2]]];
    ((UILabel*)[self.view viewWithTag:2003]).text = [NSString stringWithFormat:@"%@ Coins", [nf stringFromNumber:[NSNumber numberWithInteger:IAP_AMT_3]]];
    ((UILabel*)[self.view viewWithTag:2004]).text = [NSString stringWithFormat:@"%@ Coins", [nf stringFromNumber:[NSNumber numberWithInteger:IAP_AMT_4]]];
    ((UILabel*)[self.view viewWithTag:2005]).text = [NSString stringWithFormat:@"%@ Coins", [nf stringFromNumber:[NSNumber numberWithInteger:IAP_AMT_5]]];
    ((UILabel*)[self.view viewWithTag:2006]).text = [NSString stringWithFormat:@"%@ Coins", [nf stringFromNumber:[NSNumber numberWithInteger:IAP_AMT_6]]];
    [nf release];


- (void)requestProUpgradeProductData

    NSLog(@"called  productsRequest");

    [UIView setAnimationDuration:0.5];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
    viewLoading.alpha = 0.9;
    [UIView commitAnimations];

    loadingtext.text = @"Connecting to Store";

    if([cost1000.text isEqual:@"0.00"])

        NSSet *productIdentifiers = [NSSet setWithObjects:IAP1, IAP2, IAP3, IAP4, IAP5, IAP6, nil];
        SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
        productsRequest.delegate = self;
        [productsRequest start];


    else
        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
        viewLoading.alpha = 0.0;
        [UIView commitAnimations];
    





- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response

    NSString *sym = @"";
    NSArray *items = response.products;

    for(SKProduct *itemproduct in items)
    

        NSLog(@"Product title: %@    - %@" , itemproduct.localizedTitle, itemproduct.priceAsString);
        //NSLog(@"Product description: %@" , item.localizedDescription);
        //NSLog(@"Product price: " , ;
        NSLog(@"Product id: %@" , itemproduct.productIdentifier);

        if([itemproduct.productIdentifier isEqual:IAP1])
            cost1000.text = [sym stringByAppendingString:[NSString stringWithFormat:@"%@", itemproduct.priceAsString]];
        else if([itemproduct.productIdentifier isEqual:IAP2])
            cost3200.text = [sym stringByAppendingString:[NSString stringWithFormat:@"%@", itemproduct.priceAsString]];
        else if([itemproduct.productIdentifier isEqual:IAP3])
            cost8000.text = [sym stringByAppendingString:[NSString stringWithFormat:@"%@", itemproduct.priceAsString]];
        else if([itemproduct.productIdentifier isEqual:IAP4])
            cost20000.text = [sym stringByAppendingString:[NSString stringWithFormat:@"%@", itemproduct.priceAsString]];
        else if([itemproduct.productIdentifier isEqual:IAP5])
            cost80000.text = [sym stringByAppendingString:[NSString stringWithFormat:@"%@", itemproduct.priceAsString]];
        else if([itemproduct.productIdentifier isEqual:IAP6])
            cost200000.text = [sym stringByAppendingString:[NSString stringWithFormat:@"%@", itemproduct.priceAsString]];
        

    

    for (NSString *invalidProductId in response.invalidProductIdentifiers)
    
        NSLog(@"Invalid product id: %@" , invalidProductId);

        error = @"YES";
    

    if([error isEqual:@"YES"])
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Payments Error Occured" message:@"Could not read payment information from Apple In-App Servers. Please try again later" delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
        [alert show];
        [alert release];
        [self dismissViewControllerAnimated:YES completion:nil];
        error = @"NO";
    

    // finally release the reqest we alloc/init’ed in requestProUpgradeProductData
    //[productsRequest release];

    [UIView setAnimationDuration:0.5];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
    viewLoading.alpha = 0.0;
    [UIView commitAnimations];


//
// removes the transaction from the queue and posts a notification with the transaction result
//
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful

    // remove the transaction from the payment queue.
    [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

    if (wasSuccessful)
    
#ifdef kURL_VERIFY_PURCHASE_RECEIPT
        NSString *jsonObjectString = [CommonUtilities encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];
        ////NSLog(jsonObjectString);


        [CommonUtilities encryptString:[CommonUtilities md5:jsonObjectString]:@"b"];

        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
        viewLoading.alpha = 0.9;
        [UIView commitAnimations];
        //loadingtext.text = @"Purchase Completing";

        loadingtext.text = @"Completing Transaction";

        NSString *httpBodyString=[[NSString alloc] initWithFormat:@"receipt=%@&userid=%@",jsonObjectString, [CommonUtilities decryptString:@"username"]];

        NSString *urlString=kURL_VERIFY_PURCHASE_RECEIPT;

        NSURL *url=[[NSURL alloc] initWithString:urlString];
        [urlString release];

        NSMutableURLRequest *urlRequest=[NSMutableURLRequest requestWithURL:url];
        [url release];

        NSString *postLength = [NSString stringWithFormat:@"%d", [httpBodyString length]];
        [urlRequest setValue:postLength forHTTPHeaderField:@"Content-Length"];
        [urlRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

        [urlRequest setHTTPMethod:@"POST"];
        [urlRequest setHTTPBody:[httpBodyString dataUsingEncoding:NSISOLatin1StringEncoding]];
        [httpBodyString release];

        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

        connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
        responseData=[[NSMutableData data] retain];
#else
        int addCoins = [[CommonUtilities decryptString:@"c"] intValue];
        int currentCoins = [[CommonUtilities decryptString:@"coins"] intValue];
        int newCoins = addCoins + currentCoins;
        [CommonUtilities encryptString:[NSString stringWithFormat:@"%i", newCoins]:@"coins"];

        // due to sync issues we add + 1 to xp so sync to server completes
        NSLog(@"transaction complete");
        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
        viewLoading.alpha = 0.0;
        [UIView commitAnimations];

        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
#endif
    
    else
    
        NSLog(@"Purchase failed");
        // send out a notification for the failed transaction
         NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, @"transaction" , nil];
        //[[NSNotificationCenter defaultCenter] postNotificationName:kInAppPurchaseManagerTransactionFailedNotification object:self userInfo:userInfo];
        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
        viewLoading.alpha = 0.0;
        [UIView commitAnimations];

    


//
// called when the transaction was successful
//
- (void)completeTransaction:(SKPaymentTransaction *)transaction

    [self finishTransaction:transaction wasSuccessful:YES];

//
// called when a transaction has failed
//
- (void)failedTransaction:(SKPaymentTransaction *)transaction

    if (transaction.error.code != SKErrorPaymentCancelled)
    
        // error!
        [self finishTransaction:transaction wasSuccessful:NO];

        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
        viewLoading.alpha = 0.0;
        [UIView commitAnimations];
        NSLog(@"error transaction");

    
    else
    
        // this is fine, the user just cancelled, so don’t notify
        [[SKPaymentQueue defaultQueue] finishTransaction:transaction];

        [UIView setAnimationDuration:0.5];
        [UIView setAnimationDelegate:self];
        [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
        viewLoading.alpha = 0.0;
        [UIView commitAnimations];
        NSLog(@"cancelled transaction");

    

//
// called when the transaction status is updated
//
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

    for (SKPaymentTransaction *transaction in transactions)
    
        switch (transaction.transactionState)
        
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                break;
            default:
                break;
        
    



#pragma mark -


- (IBAction)closeCoins:(id)sender
     [self dismissViewControllerAnimated:YES completion:nil];


- (void)viewDidLoad

   [[SKPaymentQueue defaultQueue] addTransactionObserver:self];

    webView.opaque = NO; 
    webView.backgroundColor = [UIColor clearColor];
    webView.dataDetectorTypes = UIDataDetectorTypeLink;

    jackpotView.opaque = NO; 
    jackpotView.backgroundColor = [UIColor clearColor];

    //[self didLoad];

    NSString *filename = @"store.jpg";
    CGRect screenRect = [[UIScreen mainScreen] bounds];
    if (screenRect.size.width == 568.0f) 
        filename = [filename stringByReplacingOccurrencesOfString:@".jpg" withString:@"-568h@2x.jpg"];
        [self.bkg setBounds:CGRectMake(0, 0, screenRect.size.width, screenRect.size.height)];
        NSLog(@"YAGO - changing background: %@", filename);
    

    if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad ) 
        filename = @"store-ipad.jpg";
    

    self.bkg.image = [UIImage imageNamed:filename];

    viewLoading.alpha = 0.0;
    viewNoInternet.alpha = 0.0;

    // reachability
    [self tryConnect:nil];

    if([SKPaymentQueue canMakePayments])
        ////NSLog(@"can make payments");
        [self requestProUpgradeProductData];
    else
        ////NSLog(@"cannot make payments");
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Payments Disabled" message:@"In-App Purchases are disabled on this device." delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
        [alert show];
        [alert release];
        [self dismissViewControllerAnimated: YES completion:nil];

    
    CGSize result = [[UIScreen mainScreen] bounds].size;
    CGFloat scale = [UIScreen mainScreen].scale;
    result = CGSizeMake(result.width * scale, result.height * scale);

    if(result.height == 960)
        bar.frame = CGRectMake(0,0,(result.height / 2),32);
    else if(result.height == 1136)
        bar.frame = CGRectMake(0,0,(result.height / 2),32);
    else if(result.height == 1024)
        bar.frame = CGRectMake(0,0,result.height,44);
    else if (result.height == 2048)
        bar.frame = CGRectMake(0,0,result.height/2,44);
    
    else
        bar.frame = CGRectMake(0,0,(result.height),32);
    

    int fontSize = 16;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        //[[UILabel appearance] setFont:[UIFont fontWithName:@"Myriad Web Pro" size:28.0]];
    else
        //[[UILabel appearance] setFont:[UIFont fontWithName:@"Myriad Web Pro" size:12.0]];
        fontSize = 12;
    

    UIBarButtonItem *backButton = [[[UIBarButtonItem alloc] initWithTitle:@"Close" style:UIBarButtonItemStyleBordered target:nil action:nil] autorelease];
    UIImage *buttonBack32 = [[UIImage imageNamed:@"NavigationBackButton"]
                             resizableImageWithCapInsets:UIEdgeInsetsMake(0, 10, 0, 5)];

    [[UIBarButtonItem appearance] setBackButtonBackgroundImage:buttonBack32 forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    [[UIBarButtonItem appearance] setTitleTextAttributes:
     [NSDictionary dictionaryWithObjectsAndKeys:
      [UIColor whiteColor],
      UITextAttributeTextColor,
      [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.8],
      UITextAttributeTextShadowColor,
      [NSValue valueWithUIOffset:UIOffsetMake(0, -1)],
      UITextAttributeTextShadowOffset,
      [UIFont fontWithName:@"Helvetica-Bold" size:fontSize],
      UITextAttributeFont,
      nil]
                                                forState:UIControlStateNormal];
    self.navigationItem.backBarButtonItem = backButton;



-(void)viewWillDisappear:(BOOL)animated
    NSLog(@"coins has gone");

    [[NSNotificationCenter defaultCenter] postNotificationName:@"updateCoins" object:self];
    [[NSNotificationCenter defaultCenter] postNotificationName:@"sortCoins" object:self];

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        //[[UILabel appearance] setFont:[UIFont fontWithName:@"Myriad Web Pro" size:20.0]];
    else
        //[[UILabel appearance] setFont:[UIFont fontWithName:@"Myriad Web Pro" size:10.0]];
    



- (NSUInteger)supportedInterfaceOrientations 
    return UIInterfaceOrientationMaskLandscape;


- (BOOL) shouldAutorotate 
    return YES;


- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation 
    // Return YES for supported orientations
    if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft || interfaceOrientation == UIInterfaceOrientationLandscapeRight)
        return YES;
    else
        return NO;
    



- (void)dealloc 
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
    [self.bkg release];
    [super dealloc];


#pragma mark for server side validation (no customer support)
// these connection-related methods are only here to help in case you ever decide to implement
// server-side validation of purchase receipts

// - in this case, you'll also need to define your own kURL_VERIFY_PURCHASE_RECEIPT and figure out the existing validation protocol (I can't provide you with support on this one, it's code written by someone else)

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 
    [responseData setLength:0];

    //NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response;
    if ([response respondsToSelector:@selector(allHeaderFields)]) 
        //NSDictionary *dictionary = [httpResponse allHeaderFields];
        //NSLog([dictionary description]);
    



- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data 

    [responseData appendData:data];

    //NSString *a = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    //NSLog(@"Data: %@", a);



- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 

    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Connection" message:@"You are not connected to the internet or data. Please connect and try again." delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:nil, nil];
    [alert show];
    [alert release];


- (void)connectionDidFinishLoading:(NSURLConnection *)connection 
    NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
    //////NSLog(responseString);

    NSString *gKey = [CommonUtilities decryptString:@"b"];
    NSString *result = [CommonUtilities base64Decrypt:responseString:gKey];

    NSLog(@"%@",result);

    int r = [result intValue];

    ////NSLog(@"r int: %i", r);

    if(r == 1)
        int addCoins = [[CommonUtilities decryptString:@"c"] intValue];
        int currentCoins = [[CommonUtilities decryptString:@"coins"] intValue];
        int newCoins = addCoins + currentCoins;
        [CommonUtilities encryptString:[NSString stringWithFormat:@"%i", newCoins]:@"coins"];

        // due to sync issues we add + 1 to xp so sync to server completes
        NSLog(@"transaction complete");
    else
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Receipt Failed" message:@"Please make the purchase again using a vaild iTunes account" delegate:nil cancelButtonTitle:@"Cancel" otherButtonTitles:nil, nil];
        [alert show];
        [alert release];
    

    [self dismissViewControllerAnimated:YES completion:nil];

    [UIView setAnimationDuration:0.5];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(fadeOut:finished:context:)];
    viewLoading.alpha = 0.0;
    [UIView commitAnimations];

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;


@end

我不知道这段代码有什么问题。我在 xcode 调试日志中得到的唯一信息是 "Purchase failed" 。 任何帮助将不胜感激。

【问题讨论】:

基础 SDK InApp Purchase 的哪个版本?我认为您需要更新到最新版本。 @CuongNguyen 9.3 这是新问题,我没有测试环境。但我建议使用 RMStore 进行 InApp 购买:github.com/robotmedia/RMStore。你能开发一个测试应用程序并告诉我结果吗?谢谢。 @CuongNguyen 将我的 dns 更改为 2001:778::37 可以解决问题。知道为什么吗? 其他人也有同样的问题。审查小组不知道他们在做什么。 forums.developer.apple.com/message/145301#145301 【参考方案1】:

原来问题不在我这边。由于 sandbox.itunes.apple.com 没有 ipv6 地址,因此它适用于 DNS64(或 NAT64 或其他任何东西:D。我对这些东西了解不多),而不是真正的纯 ipv6 网络。

不知道为什么他们第一次拒绝了我的应用,但在我重新提交后它被批准了。

【讨论】:

以上是关于iOS IAP 无法在仅 ipv6 的网络上运行的主要内容,如果未能解决你的问题,请参考以下文章

iOS 应用中支持 IPV6 的 pjsip 2.5.5

iOS IAP 无法在 Unity 上运行,“服务不可用”

本地连接网络展示ipv6无权限访问,部分应用及网站无法访问,求大佬建议,挺急的

iOS - 仅支持 ipv4 API

Swift IOS 13,IAP - “无法连接到 Itunes Connect”

在连接到 IPv6 网络的 Wi-Fi 上运行 iOS 10.0