事务在finishTransaction之后返回:已被调用

时间:2010-06-29 09:10:02

标签: iphone in-app-purchase

我正在使用应用内购买iPhone应用。我有一个充当SKProductsRequestDelegateSKPaymentTransactionObserver的课程,它在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]的调用中找到它相对容易。无论如何,我确实看到了预期的输出;交易已完成,并且与产品ID和原始交易日期完全匹配。但是,下次我运行应用程序时,整个事情重新开始!就像iTunes Store从未通知交易已完成或拒绝承认。

10 个答案:

答案 0 :(得分:17)

在开发人员论坛中也提出了这个问题,一般的结论是iPhone OS 4.0中的事务处理有所不同。只有在收到已完成交易的通知与在支付队列上调用finishTransaction之间存在显着延迟时,才会出现此问题。最后我们找不到理想的解决方案,但我们做的是:

  1. 一旦交易到达,处理它并在处理成功时在用户首选项中记录一个值。

  2. 下次该事务出现在队列中时(可能不会在下次启动应用程序之前),立即在其上调用finishTransaction

    < / LI>

    我们的产品是“非消耗品”,因此足以检查所支付的产品是否有效且无错误,以安全地忽略来自iTunes的任何“不死”重复交易。对于消费品,需要保存有关购买的更多信息,例如原始付款日期,以确保未来的交易通知可以与已经处理的购买相匹配。

答案 1 :(得分:6)

这个问题也发生在我身上,我找到了一个解决方案。在类似的情况下,这可能对你有帮助。

我立刻打电话给finishTransaction,但下次当我尝试买东西的时候,以前的产品也来了!所以第一次,我买了一个产品。但是第二次,我也买了第二个产品和第一个产品。

我发现我多次添加SKPaymentTransactionObserver!这导致了问题,多次购买。

当流程结束时,我的意思是当您拨打finishTransaction之后,请致电: [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

这将清除交易并移除观察者。所以下次,你不会多次购买。

答案 2 :(得分:5)

我没有深入研究过这个问题,但我看到我在iOS 4中对finishTransaction的调用可靠地失败了。在预感中,我在dispatch_async调用中调用了finishTransaction,问题就消失了。

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

也许一个突出的问题是我在一个块中调用了finishTransaction,该块最终在网络调用我的服务器后运行。

答案 3 :(得分:3)

我有同样的问题,但我解决了。

这是我的代码:

- (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];
}

然后我在provideContentForProductIdentifier:方法中调用了finishTransaction:方法。 在恢复事务的情况下,我调用了finishTransaction:对原始事务对象而不是事务本身。

我通过此代码解决了我的问题(方法restoreTransaction:)

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

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

答案 4 :(得分:2)

检查您是否只是多次添加观察者。我有多个updatedTransactions有同样的问题,但后来我注意到我每次都在didBecomeActive中添加一个新的观察者。每次我在沙箱中恢复购买时都会调用一次。

答案 5 :(得分:1)

我也有这个问题。这个bug竟然在我身边。问题是潜伏的过去事务已经执行(提供的内容)但没有使用finishTransaction进行清理。不幸的是,在包括Apple TSI在内的几个地方询问时,我发现无法对这种“不死”交易进行轮询 - 您只需注册通知并等待相应的paymentQueue:updatedTransactions:。这使我的代码变得复杂,但不是很多。

我现在做的,一直运作良好:

  • 当即将调用商店时(您正在刷新一些营销幻灯片,可能会使用使用条款),通过[[SKPaymentQueue defaultQueue] addTransactionObserver:self]
  • 获取您的产品列表并以观察者身份注册通知
  • 使用[[SKPaymentQueue defaultQueue] addPayment:payment]
  • 在队列中推送付款时,保持状态变量更新为YES
  • 当您通过paymentQueue:updatedTransactions:收到成功购买通知时,请检查状态变量。如果尚未设置,则表示您已收到过去付款的通知。在这种情况下,尊重付款而不是推新付款。

此方法自然会假设您有时间在开始新交易之前等待旧交易显示。

答案 6 :(得分:1)

我遇到了同样的问题。

根据答案,我做了一些实验,发现如果我对队列有一个引用,问题就会消失。

例如:

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

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

然后我确保我的所有方法都使用了我的实例变量引用。由于这样做,问题已经解决了。

答案 7 :(得分:0)

我想知道,defaultQueue是否保证与paymentQueue:updatedTransactions:中传递的队列相同?如果没有,那么问题可能是在与事务源自的SKPaymentQueue不同的SKPaymentQueue上调用finishTransaction。

答案 8 :(得分:0)

这可能是由多个观察者引起的。在dealloc中删除观察者,不要以为你是安全的。

您致电[[SKPaymentQueue defaultQueue] addTransactionObserver:self]中的viewDidLoad[[SKPaymentQueue defaultQueue] removeTransactionObserver:self]中的dealloc。你认为你像我一样安全,但你不是。

实际上,如果你购买东西,完成交易,将其从支付队列中删除,从导航视图控制器弹出这个视图控制器,然后再次进入视图控制器,你可能有多个交易观察者。

每次推送此视图控制器时,您都将视图控制器本身添加为transactionObserver,但每次弹出此视图控制器时,都不能保证您从transactionObservers中删除视图控制器本身。

即使弹出视图控制器,某个视图控制器的dealloc也不会被调用。所以视图控制器仍然在黑暗中观察。

我认为这种情况的最佳解决方案是在处理事务时检测此视图控制器是否可见。

在处理交易之前简单地添加它,它对我有用:

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

可见检测来自here


PS。也许在viewWillAppear / viewWillDisappear中放置/删除transactionObserver是解决这个问题的另一种方法,但是如果用户需要输入密码,你必须小心处理键盘显示/隐藏事件。

答案 9 :(得分:-1)

我使用此代码并且它为我工作

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]);
        }
    }
}