paymentQueue:updatedTransactions:在恢复期间未调用

时间:2017-07-13 17:37:23

标签: ios in-app-purchase skpaymenttransaction

在极少数情况下,我的某些用户似乎无法进行非消耗品购买。当他们尝试购买时,它不会激活“premium”,当他们从当前安装恢复或新安装时paymentQueue: updatedTransactions:未被调用。

我专门添加了大量日志记录,以尝试确定还原未遵循预期流量的原因。在恢复失败期间,不会触发任何“RESTORE”类别事件。

仅供参考[self success];显示内容视图,而[self fail:]会向用户显示错误消息。

同样在[[SKPaymentQueue defaultQueue] addTransactionObserver:self];中调用viewDidLoad,按下按钮时调用[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
    // COMPLETION POINT - RESTORE COMPLETE***
    [MBProgressHUD hideHUDForView:self.view animated:TRUE];

    if ([SKPaymentQueue defaultQueue].transactions.count == 0) {
        [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
                                                                   action:@"failure_hard"
                                                                    label:@"no_purchases"
                                                                    value:nil] build]];
        [self fail:@"There are no items available to restore at this time."];
    } else {
        [self success];
    }
}

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error {
    // COMPLETION POINT - RESTORE FAILED
    [MBProgressHUD hideHUDForView:self.view animated:TRUE];

    [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
                                                               action:@"failure_hard"
                                                                label:error.localizedDescription
                                                                value:nil] build]];
    [self fail:error.localizedDescription];
}

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    // Make sure completion states call [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
    // in order to prevent sign in popup
    // http://stackoverflow.com/a/10853107/740474
    [MBProgressHUD hideHUDForView:self.view animated:TRUE];
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                break;
            case SKPaymentTransactionStateDeferred:
                break;
            case SKPaymentTransactionStateFailed:
                // COMPLETION POINT - PURCHASE FAILED
                [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"PURCHASE"
                                                                           action:@"failure_hard"
                                                                            label:transaction.error.localizedDescription
                                                                            value:nil] build]];
                if (transaction.error.code != SKErrorPaymentCancelled) {
                    // only show error if not a cancel
                    [self fail:transaction.error.localizedDescription];
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStatePurchased:
                // COMPLETION POINT - PURCHASE SUCCESS
                if ([transaction.payment.productIdentifier isEqualToString:(NSString*)productID]) {
                    // premium purchase successful
                    [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"PURCHASE"
                                                                               action:@"success"
                                                                                label:nil
                                                                                value:nil] build]];
                    [Utils setPremium:YES];
                    [self success];
                } else {
                    [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"PURCHASE"
                                                                               action:@"failure_hard"
                                                                                label:@"no_id"
                                                                                value:nil] build]];
                    [self fail:@"The item you purchased was not returned from Apple servers. Please contact us."];
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                if ([transaction.payment.productIdentifier isEqualToString:(NSString*)productID]) {
                    // premium purchase restored
                    [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
                                                                               action:@"restore_success"
                                                                                label:nil
                                                                                value:nil] build]];
                    [Utils setPremium:YES];
                } else {
                    [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"RESTORE"
                                                                               action:@"failure_hard"
                                                                                label:@"no_id"
                                                                                value:nil] build]];
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            default:
                // For debugging
                   [self.tracker send:[[GAIDictionaryBuilder createEventWithCategory:@"STORE"
                                                                           action:@"transaction_weird"
                                                                               label:[NSString stringWithFormat:@"Unexpected transaction state %@", @(transaction.transactionState)]
                                                                            value:nil] build]];
                break;
        }
    }
}

任何建议都将不胜感激

6 个答案:

答案 0 :(得分:3)

您是否实施了以下方法:

  • (void)request:(SKRequest *)request didFailWithError:(NSError *)error NS_AVAILABLE_IOS(3_0);

它是SKRequestDelegate的可选方法之一。

我们也遇到了丢失恢复购买电话的同样问题。处理这个代表帮助了我们。由于任何原因,甚至没有被送到队列的所有请求都是在这个失败代表中提供的。

所以,我认为你可能面临同样的问题。

答案 1 :(得分:2)

开始恢复过程 -

-(void)restore{
isRestored = false;
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}

如果成功恢复任何事务,则调用以下方法 -

-(void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions{
       case SKPaymentTransactionStateRestored:

            DDLogVerbose(@"Restored");
            //Check with your product id if it is the right product that you want to restore
            if ([transaction.payment.productIdentifier isEqualToString:IAP_PRODUCT_ID]) {
                isRestored = true;
                // Successfully restored the payment, provide the purchased content to the user.
            }
            [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            break;
}

当支付队列完成发送已恢复的事务时,将调用以下方法(如果调用它意味着它已完成事务而不是该恢复成功) -

-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue{
DDLogVerbose(@"Restore completed");

if (isRestored) {

    // Successfully restored
}else{
    // No transaction to restore
  }
}

paymentQueueRestoreCompletedTransactionsFinished

恢复事务时发生任何错误时,将调用以下方法 -

- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error{

DDLogVerbose(@"Error in restoring:%@",error);

if (error.code == 0) {
    // unable to connect to iTunes
  }
}

答案 2 :(得分:2)

按预期使应用程序行为的几个步骤:

1。AppDelegate中添加事务观察器,跟踪延迟响应&amp;每当您的应用程序启动时,它都会更新&amp;完成队列中的交易

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

删除applicationWillTerminate

中的观察者
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

2. 第二步,在要求用户购买之前检查应用内收据

-(void)validateReceiptsFromAppStoreFor:(NSString *)productTag completionBlock:(void (^)(NSDictionary *receiptResponse,NSError *error))completion
{
    //check if receipt exists in app bundle
    //else request for refresh receipt data..
    //if receipt exists,verify with server & check if product tag exists in receipt & send receipt response as success msg
    //else wait for refresh request success 
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
    if (!receipt)
    { /* No local receipt -- handle the error. */
        refreshRequest = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:nil];
        refreshRequest.delegate = self;
        [refreshRequest start];
        return;
    }

    /* ... Send the receipt data to your server ... */
    NSError *error;
    NSDictionary *requestContents = @{
                                      @"password":@"Your shared secret key",
                                        @"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.
    NSString *storeURL = SANDBOX_VERIFY_RECEIPT_URL; //ITMS_PROD_VERIFY_RECEIPT_URL;
    if ([[[FFGDefaults sharedDefaults] objectForKey:@"environmentType"] isEqualToString:@"prod"])
    {
        storeURL = PROD_VERIFY_RECEIPT_URL;
    }
    NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString: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 ... */
                                   if (completion) {
                                       completion(nil,connectionError);
                                   }
                               } else {
                                   NSError *error;
                                   NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
                                   if (completion)
                                   {
                                       jsonResponse = jsonResponse[@"receipt"];
                                       if ([jsonResponse[@"bundle_id"] isEqualToString:[NSBundle mainBundle].bundleIdentifier])
                                       {                                           
                                               //check if product was purchased earlier..
                                               NSString *str_productID = [CFCommonUtils productIDForPlanTag:productTag];
                                                NSArray *receiptArr = jsonResponse[@"in_app"];
                                               if (receiptArr && receiptArr.count>0)
                                               {
                                                   NSArray *filteredArray = [receiptArr filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"product_id = %@",str_productID]];
                                                   if (filteredArray.count>0) {
                                                       completion(jsonResponse,error);
                                                   }
                                                   else
                                                   {
                                                       NSError *err = [NSError errorWithDomain:@"" code:100 userInfo:nil];
                                                       completion(nil,err);
                                                   }

                                               }
                                               else
                                               {
                                                   NSError *err = [NSError errorWithDomain:@"" code:100 userInfo:nil];
                                                   completion(nil,err);
                                               }

                                       }
                                       else
                                       {
                                           NSError *err = [NSError errorWithDomain:@"" code:100 userInfo:nil];
                                           completion(nil,err);

                                       }
                                   }

                               }
                               }];

}

3. 处理收据刷新委托方法以检查更新后的收据

- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
#ifdef DEBUG
    NSLog(@"SKRequest : didFailWithError :%@",error);
#endif
    if ([request isMemberOfClass:[SKReceiptRefreshRequest class]] && refreshRequest && delegate && [delegate respondsToSelector:@selector(receiptRefreshed:error:)])
    {
        [self receiptRefreshed:self error:error];
        refreshRequest = nil;
    }
    else
    {

    }
}

- (void)requestDidFinish:(SKRequest *)request
{
#ifdef DEBUG
    NSLog(@"SKRequest : requestDidFinish ");
#endif
    if ([request isMemberOfClass:[SKReceiptRefreshRequest class]] && refreshRequest && delegate && [delegate respondsToSelector:@selector(receiptRefreshed:error:)])
    {
        [self receiptRefreshed:self error:nil];
        refreshRequest = nil;
    }
    else
    {
    }
}



-(void) receiptRefreshed:(CFStorekitManager*)ebp error:(NSError *)error
{
    if (error)
    {
    }
    else
    {
        [self validateSubscriptionReceiptsFromAppStoreWithRefreshReceipt:YES completion:^(NSDictionary *receiptResponse, NSError *error)
     {
         dispatch_async(dispatch_get_main_queue(), ^{
             if (error)
             {
                 //show subscription for purchase
             }
             else
             {
             }
         });

     }];

    }
}

答案 3 :(得分:1)

我不确定这是否与您的问题有关,但可能是原因。 (或者至少建议苹果做这件事。)

Apple Documentation

Apple建议您在AppDelegate中注册 SKPaymentQueue 作为观察者,而不是在特定类中注册(除非您在AppDelegate中调用此类)

这意味着:

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

实际上应该进入AppDelegate中的 applicationDidFinishLaunchingWithOptions 方法。

为什么这很重要?你提到过:

  

在恢复失败期间,没有“RESTORE”类别事件   烧成。

这让我相信你的听众没有正确注册或准时注册。 (或者在非常罕见的情况下,用户可能会被重定向到您的应用程序之外以正确登录或某些内容,并且由于内存问题,您的应用可能会被杀死?无论哪种方式,这都可以确保一旦他们返回到您的应用程序,将始终存在观察员准备处理Apple发送的任何通知

使用App Receipt恢复购买

文档here

或者,通过刷新应用收据,然后根据用户购买的内容提供内容,实施还原购买逻辑要容易得多。

request = [[SKReceiptRefreshRequest alloc] init];
request.delegate = self;
[request start];

这将调用您的委托方法:

func requestDidFinish(SKRequest)

func request(SKRequest, didFailWithError: Error)

请求成功完成后,您可以解析收据以向用户授予之前购买的所有商品。收据解析指南的描述为here

答案 4 :(得分:1)

您是否有可能在应用中使用Firebase分析?

https://firebase.google.com/docs/analytics/ios/start

  

如果您要跟踪应用内购买,则必须初始化   应用程序中的事务观察器:didFinishLaunchingWithOptions:   在初始化Firebase之前,否则您的观察者可能不会收到全部   购买通知。请参阅Apple的应用内购买最佳做法,以了解   更多信息。

在这种情况下,建议在初始化Firebase分析之前初始化观察者。

以下是博客文章,其中包含其他详细信息:https://www.greensopinion.com/2017/03/22/This-In-App-Purchase-Has-Already-Been-Bought.html

答案 5 :(得分:0)

据我所知,如果restoreCompletedTransactions()调用已完成,但并未导致任何交易被还原,则{strong>未调用

paymentQueue(_:, updateTransactions:)

因此我们可以检查func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { if queue.transactions.count == 0 { // does NOT call paymentQueue:updatedTransactions: } else { // should call paymentQueue:updatedTransactions: } } 以确定是否将调用其他委托方法。