2010-06-29 13 views
21

Estoy usando la compra en la aplicación para una aplicación de iPhone. Tengo una clase que actúa como SKProductsRequestDelegate y SKPaymentTransactionObserver, y todo está funcionando bien en la versión actualmente disponible en iTunes.La transacción vuelve después de finalizar la transacción: ha sido llamada en ella

Sin embargo, después de agregar recientemente un nuevo producto no consumible y probarlo en el entorno de Sandbox, ahora me encuentro con un problema extraño. Cada vez que lance la aplicación, la compra que hice ayer reaparece en la lista de transacciones que me pasó por paymentQueue:updatedTransactions:, a pesar de que ya había llamado al [[SKPaymentQueue defaultQueue] finishTransaction:transaction] (varias veces). ¡Es un muerto viviente!

En mi aplicación paymentQueue:updatedTransactions:, que tengo:

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

entonces procesar la compra, descargue el contenido del usuario y, por último, en otro método, hacer esto:

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

ya que puede tener notado, no me estoy aferrando al objeto de transacción original por simplicidad, y es relativamente fácil encontrarlo más tarde desde la llamada al [[SKPaymentQueue defaultQueue] transactions]. En cualquier caso, de hecho veo el resultado esperado; que la transacción se complete y que coincida exactamente con la identificación del producto y la fecha de la transacción original. Sin embargo, la próxima vez que ejecute la aplicación todo comienza de nuevo. Es como si nunca se notificara a iTunes Store que la transacción se completó, o se niega a reconocerla.

+0

se las arregló para encontrar una solución para esto? @Craig McMahon – KarenAnne

+0

Esta es una publicación de los foros de desarrolladores que describe el problema que todavía está sucediendo en 2017. https://forums.developer.apple.com/thread/72099 –

Respuesta

15

Este problema también se planteó en los foros de desarrolladores, y la conclusión general fue que la manipulación de las transacciones en iPhone OS 4.0 se debió a una diferencia. El problema solo parece ocurrir cuando hay un retraso significativo entre la recepción de una notificación de la transacción finalizada y la llamada al finishTransaction en la cola de pagos. Al final no hemos encontrado una solución ideal, pero lo que hicimos fue lo siguiente:

  1. Tan pronto como llega la transacción, procesarla y registrar un valor en las preferencias del usuario si el procesamiento se ha realizado correctamente.

  2. La próxima vez que la transacción aparece en la cola, que puede no ser hasta la próxima puesta en marcha de la aplicación, inmediatamente llamada finishTransaction en él.

Nuestros productos son "no consumible" por lo que es suficiente para comprobar que el producto pagado es válida y para ignorar cualquier transacción repetidas 'muertos vivientes' de iTunes sin errores. Para los productos de consumo uno necesitaría guardar más información sobre la compra, como la fecha de pago original, para asegurarse de que las futuras notificaciones de transacciones se puedan combinar con las compras que ya se procesaron.

+0

¿cómo se puede decir si es la misma transacción que debe completarse? –

0

Me pregunto, ¿está garantizado que la defaultQueue será la misma que pasó en paymentQueue:updatedTransactions:? Si no es así, entonces quizás el problema es llamar a finishTransaction en un SKPaymentQueue diferente del que originó la transacción.

+0

Esa es en realidad una idea muy interesante ... –

+0

En mi pequeña prueba, parecen ser la misma cola. En 'paymentQueue: updatedTransactions:', 'queue == [SKPaymentQueue defaultQueue]' return 'YES' – Alston

1

Tuve este problema también. El error resultó estar de mi lado. El problema era que había una transacción pasada al acecho que se había ejecutado (el contenido proporcionado) pero no se limpió usando finishTransaction. Desafortunadamente, al preguntar en varios lugares, incluida una TSI de Apple, descubrí que no había forma de sondear tales transacciones 'no muertas'; solo tenía que registrarse para recibir notificaciones y esperar el correspondiente paymentQueue:updatedTransactions:. Esto complicó mi código, pero no por mucho.

Lo que hago ahora, que ha estado trabajando muy bien:

  • Cuando la tienda está a punto de ser invocado (que está parpadeando unas diapositivas de marketing, unos términos de uso quizás), ir a buscar a su lista de productos y registrar las notificaciones en calidad de observador a través de [[SKPaymentQueue defaultQueue] addTransactionObserver:self]
  • Mantener una variable de estado que se actualiza a sí cuando se presiona un pago en la cola utilizando [[SKPaymentQueue defaultQueue] addPayment:payment]
  • al recibir una notificación de una compra exitosa a través de cheque paymentQueue:updatedTransactions: la variable de estado. Si no se ha configurado, significa que ha recibido una notificación de un pago anterior. En ese caso, respete ese pago en lugar de presionar uno nuevo.

Este método supone naturalmente que tiene tiempo de esperar a que aparezcan las transacciones anteriores antes de comenzar una nueva transacción.

+1

parece que realmente estás pensando en algo aquí, pero estoy un poco confundido en cuanto a lo que estás haciendo exactamente. Si recibe una notificación de una compra exitosa (SKPaymentTransactionStatePurchased) a través de paymentQueue: updatedTransactions :, usted dice que si la bandera no está configurada, respete el pago en lugar de presionar uno nuevo. Pero si ha llegado a este punto, independientemente de si tiene bandera o no, ¿alguna vez presiona un nuevo pago? ¿Hay algún caso en el que reciba esta notificación que indique que debe realizar un nuevo pago? – SAHM

1

Estaba teniendo el EXACTO mismo problema.

Basado en las respuestas, hice algunos experimentos y descubrí que si mantengo una referencia a la cola, el problema desaparecía.

Por ejemplo:

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

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

entonces me aseguré de que todos mis métodos utilizados mi ejemplo de referencia variable. Después de hacer eso, el problema se ha aclarado.

5

No he profundizado en esto, pero estaba viendo mis llamadas para terminar Transacción fallando de manera confiable en iOS 4. Por un presentimiento puse la llamada para terminar Transacción en una llamada dispatch_async y el problema desapareció.

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

Tal vez un punto saliente es que estaba llamando finishTransaction desde el interior de un bloque que finalmente corrió tras una llamada de red a mi servidor.

2

Compruebe que no está simplemente agregando el observador varias veces. Tuve el mismo problema con múltiples Transactions actualizadas, pero luego noté que estaba agregando un nuevo observador cada vez en didBecomeActive. Y se llamó una vez cada vez que, por ejemplo, restauré las compras en sandbox.

3

Tuve el mismo problema pero lo resolví.

Aquí está mi código:

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

que se llama entonces finishTransaction: método dentro provideContentForProductIdentifier: método. Y en el caso de la transacción de restauración que estaba llamando finishTransaction: al objeto original Transacción no la transacción en sí.

he resuelto mi problema con este código (método restoreTransaction :)

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

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

Este fue mi problema. ¡Salvó mi vida! –

+0

Esto es exactamente lo que estaba haciendo. Estaba tomando transaction.orginalTransaction y tratando de terminarlo. Cada vez que cargaba mi aplicación, intentaba procesar la transacción nuevamente. –

6

Ese problema que me ha pasado, así, he encontrado la solución. Eso puede ayudarte en casos similares.

Estaba llamando al finishTransaction inmediatamente pero la próxima vez cuando trato de comprar algo, el producto anterior también venía. Entonces, al principio, estaba comprando un producto.Pero en la segunda ocasión, estaba comprando el segundo producto y el primer producto también.

¡Descubrí que estoy agregando SKPaymentTransactionObserver varias veces! Eso estaba causando el problema, haciendo múltiples compras.

Cuando termina el proceso, quiero decir cuando llame finishTransaction, justo después de eso, llame a: [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];

que despejará a cabo transacciones y retire el observador. Entonces la próxima vez, no harás compras múltiples.

+0

Muchas gracias, hermano ... :) –

0

Posiblemente sea causado por varios observadores. No creas que estás a salvo con sólo quitar al observador en dealloc.

Ha llamado a [[SKPaymentQueue defaultQueue] addTransactionObserver:self] en viewDidLoad y [[SKPaymentQueue defaultQueue] removeTransactionObserver:self] en dealloc. Piensas que estás a salvo como yo, pero no lo estás.

En realidad, si compras algo, finalizas la transacción, lo quitas de la cola de pagos, sacas este controlador de vista del controlador de vista de navegación, luego ingresas el controlador de vista nuevamente, es probable que tengas varios observadores de transacciones.

Cada vez que presione este controlador de vista, está agregando el controlador de vista en sí mismo como transactionObserver, pero cada vez que abra este controlador de vista no garantiza que esté eliminando el controlador de vista de transactionObservers.

De alguna manera, no se puede llamar a la función dealloc de un controlador de vista aunque se muestre el controlador de vista. Entonces, el controlador de vista todavía está observando en la oscuridad.

Creo que la mejor solución para esta situación es detectar si este controlador de vista está visible cuando se procesa la transacción.

Basta con añadir esta antes de procesar la transacción, que funciona para mí:

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

detección visible se hace referencia desde here.


ps. Tal vez poner/eliminar transactionObserver en viewWillAppear/viewWillDisappear es otra forma de resolver eso, pero tienes que manejar cuidadosamente el teclado show/hide events en caso de que el usuario necesite escribir la contraseña.

0

I utilizando este código y que funcione para mí

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