Подтвердить что ты не робот

Транзакция возвращается после завершения транзакции: она была вызвана

Я использую покупку в приложении для iPhone. У меня есть класс, который действует как SKProductsRequestDelegate и SKPaymentTransactionObserver, и все это отлично работает в текущей версии, доступной в iTunes.

Однако, после добавления нового не потребляемого продукта и тестирования его в среде Sandbox, я столкнулся с какой-то странной проблемой. Каждый раз, когда я запускаю приложение, покупка, которую я сделал вчера, появляется в списке транзакций, переданных мне paymentQueue:updatedTransactions:, несмотря на то, что я уже вызывал [[SKPaymentQueue defaultQueue] finishTransaction:transaction] уже (несколько раз). Это нежить!

В моей реализации paymentQueue:updatedTransactions: у меня есть:

for (SKPaymentTransaction* transaction in transactions) 
    switch (transaction.transactionState)
    {
        case SKPaymentTransactionStatePurchased:
        case SKPaymentTransactionStateRestored:
        {
            ....
                DDLog(@"Transaction for %@ occurred originally on %@.", transaction.payment.productIdentifier, transaction.originalTransaction.transactionDate);
                ....

Затем я обрабатываю покупку, загружаю содержимое пользователя и, наконец, другим способом, сделайте следующее:

for (SKPaymentTransaction* transaction in [[SKPaymentQueue defaultQueue] transactions])         
            if (([transaction.payment.productIdentifier isEqualToString:theParser.currentProductID]) &&
                 ((transaction.transactionState==SKPaymentTransactionStatePurchased) || (transaction.transactionState==SKPaymentTransactionStateRestored))
               )
            {
                DDLog(@"[[ Transaction will finish: product ID = %@; date = %@ ]]", transaction.payment.productIdentifier, transaction.transactionDate);
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
            }

Как вы, возможно, заметили, я не стремлюсь к оригинальному объекту транзакции ради простоты, и относительно легко найти его позже из вызова [[SKPaymentQueue defaultQueue] transactions]. Несмотря на это, я действительно вижу ожидаемый результат; что транзакция завершена и что она точно соответствует идентификатору продукта и дате первоначальной транзакции. Однако, в следующий раз, когда я запустил приложение, все начнется! Он, как и iTunes Store, никогда не был уведомлен о завершении транзакции или отказывается признать его.

4b9b3361

Ответ 1

Эта проблема также была затронута на форумах разработчиков, и общий вывод заключался в том, что дело не только в обработке транзакций в iPhone OS 4.0. Проблема только возникает, когда существует значительная задержка между получением уведомления о завершенной транзакции и вызовом finishTransaction в очереди платежей. В итоге мы не нашли идеального решения, но мы сделали следующее:

  • Как только транзакция прибывает, обработайте ее и запишите значение в пользовательских настройках, если обработка прошла успешно.

  • В следующий раз, когда транзакция появится в очереди, которая может не появиться до следующего запуска приложения, немедленно вызовите finishTransaction на нем.

Наши продукты "не потребляются", поэтому достаточно проверить, что оплаченный продукт является действительным и безошибочным, чтобы безопасно игнорировать любые "неживые" повторные транзакции из iTunes. Для расходных материалов вам нужно будет сохранить дополнительную информацию о покупке, например, дату первоначального платежа, чтобы убедиться, что будущие уведомления о транзакции могут быть сопоставлены с уже обработанными покупками.

Ответ 2

Эта проблема возникла и со мной, я нашел решение. Это может помочь вам в подобных случаях.

Я звонил finishTransaction сразу, но в следующий раз, когда я пытаюсь что-то купить, предыдущий продукт тоже пришел! Поэтому в первый раз я покупал один продукт. Но во второй раз я покупал второй продукт и первый продукт тоже.

Я узнал, что добавляю SKPaymentTransactionObserver несколько раз! Это вызвало проблему, вызвав несколько покупок.

Когда процесс заканчивается, я имею в виду, когда вы вызываете finishTransaction, сразу после этого вызывайте: [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

Это очистит транзакции и удалит наблюдателя. Поэтому в следующий раз вы не будете делать несколько покупок.

Ответ 3

Я не глубоко вникал в это, но я видел, как мои вызовы завершали транзакцию, надежно проваливающуюся в iOS 4. На догадку я поместил вызов в конецTransaction в вызове dispatch_async, и проблема исчезла.

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
  [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
});

Возможно, важным моментом является то, что я вызывал finishTransaction изнутри блока, который в конечном итоге выполнялся после сетевого вызова на мой сервер.

Ответ 4

У меня была такая же проблема, но я решил ее.

Здесь мой код:

- (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;
            case SKPaymentTransactionStateRestored:
                [self restoreTransaction:transaction];
                break;
            default:
                break;
        }
    }
}

- (void)completeTransaction:(SKPaymentTransaction *)transaction
{
    NSLog(@"completeTransaction... %@", [[transaction class] description]);

    [self provideContentForProductIdentifier:transaction];
}

- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
    NSLog(@"restoreTransaction...");

    [self provideContentForProductIdentifier:transaction.originalTransaction];
}

Затем я вызывал метод finishTransaction: внутри метода containsContentForProductIdentifier:. И в случае транзакции восстановления я вызывал finishTransaction: к объекту originalTransaction не самой транзакции.

Я решил проблему с помощью этого кода (метод restoreTransaction:)

- (void)restoreTransaction:(SKPaymentTransaction *)transaction
    {
        NSLog(@"restoreTransaction...");

        //Pass the main transaction object.
        [self provideContentForProductIdentifier:transaction];
    }

Ответ 5

Убедитесь, что вы не просто добавляете наблюдателя несколько раз. У меня была та же проблема с несколькими updatedTransactions, но потом я заметил, что каждый раз добавляю нового наблюдателя в didBecomeActive. И он вызывался один раз каждый раз, когда я, например, восстанавливал покупки в песочнице.

Ответ 6

У меня тоже была эта проблема. Ошибка оказалась на моей стороне. Проблема заключалась в том, что была проделана последняя транзакция, которая была выполнена (она была предоставлена), но не была очищена с помощью finishTransaction. К сожалению, при запросе в нескольких местах, включая Apple TSI, я обнаружил, что невозможно было опросить такие транзакции "нежить" - вам просто нужно было зарегистрироваться для уведомлений и дождаться соответствующего paymentQueue:updatedTransactions:. Это осложняет мой код, но не намного.

Что я делаю сейчас, что отлично работает:

  • Когда магазин будет вызван (вы можете мигать некоторые маркетинговые слайды, возможно, условия использования), выберите ваш список продуктов и зарегистрируйтесь для уведомлений в качестве наблюдателя через [[SKPaymentQueue defaultQueue] addTransactionObserver:self]
  • Сохраняйте переменную состояния, которая обновляется до YES, когда вы нажимаете платеж в очереди, используя [[SKPaymentQueue defaultQueue] addPayment:payment]
  • Когда вы получите уведомление об успешной покупке через paymentQueue:updatedTransactions:, проверьте переменную состояния. Если он не был установлен, это означает, что вы получили уведомление о прошедшем платеже. В этом случае соблюдайте этот платеж, а не нажимайте новый.

Этот метод, естественно, предполагает, что у вас есть время дождаться появления старых транзакций перед началом новой транзакции.

Ответ 7

У меня была ТОЧНАЯ проблема.

На основании ответов я сделал несколько экспериментов и обнаружил, что если я держу ссылку на очередь, проблема исчезла.

Например:

// myStoreManagerClass.h
...
SKPaymentQueue *_myQueue;
...

//myStoreManagerClass.m
...
if(_myQueue == nil) {
_myQueue = [[SKPaymentQueue defaultQueue];
}
...

Затем я убедился, что все мои методы использовали мою ссылку на переменную экземпляра. После этого проблема выяснилась.

Ответ 8

Мне интересно, гарантирован ли defaultQueue быть той же очереди, что и в paymentQueue:updatedTransactions:? Если нет, то, возможно, проблема заключается в вызове finishTransaction на другой SKPaymentQueue, чем тот, с которого произошла транзакция.

Ответ 9

Возможно, это вызвано несколькими наблюдателями. Не думайте, что вы в безопасности, просто удалив наблюдателя в dealloc.

Вы вызывали [[SKPaymentQueue defaultQueue] addTransactionObserver:self] в viewDidLoad и [[SKPaymentQueue defaultQueue] removeTransactionObserver:self] в dealloc. Вы думаете, что вы в безопасности, как я, но вы этого не сделали.

Собственно, если вы что-то приобретаете, завершите транзакцию, удалите ее из очереди платежей, вытащите этот контроллер вида из контроллера просмотра навигации, затем снова войдите в контроллер просмотра, у вас, вероятно, есть несколько наблюдателей транзакций.

Каждый раз, когда вы нажимаете этот контроллер вида, вы добавляете сам контроллер представления как transactionObserver, но каждый раз, когда вы выходите на этот контроллер, вы не можете удалить сам контроллер представления из транзакционных транзакций.

Как-то диспетчер представлений dealloc не будет вызван, даже если вы выберете контроллер вида. Таким образом, контроллер просмотра все еще наблюдает в темноте.

Я думаю, что лучшим решением для этой ситуации является обнаружение, видимо ли этот контроллер просмотра при обработке транзакции.

Просто добавьте это, прежде чем обрабатывать транзакцию, он работает для меня:

if ( !self.view.window) {
    return;
}

видимое обнаружение ссылается на здесь.


пс. Возможно, put/remove transactionObserver в viewWillAppear/viewWillDisappear - это еще один способ решить эту проблему, но вам нужно тщательно обрабатывать события show/hide на клавиатуре, если пользователю нужен пароль типа.

Ответ 10

Я использую этот код, и он работает для меня

if ([[SKPaymentQueue defaultQueue].transactions count] > 0) {
    for (SKPaymentTransaction *transaction in [SKPaymentQueue defaultQueue].transactions) {
        @try {
            [[SKPaymentQueue defaultQueue] finishTransaction: transaction];
        } @catch (NSException *exception) {
            NSLog([NSString stringWithFormat:@"%@", exception.reason]);
        }
    }
}