Facturación en la aplicación Android – restoreTransactionInformation

Estoy intentando hacer una aplicación gratuita actualizable a la versión "pagada" usando la facturación en la aplicación. He utilizado el código de este tutorial para manejar la facturación como el que está en el sitio oficial de desarrolladores es demasiado complejo y desordenado para seguir para un flujo simple como el mío.

Mi código para hacer la actualización funciona bien.

El problema viene cuando intento agregar algo para comprobar si el usuario ha comprado ya pero ha perdido sus datos de la compra reinstalando o despejando los datos (no cuido qué).

En el inicio de la aplicación, compruebo si hay un indicador que se ha configurado después de la primera ejecución. Si ese indicador no está allí, se muestra al usuario un cuadro de diálogo advirtiéndole que la aplicación comprueba las compras anteriores y cuando hace clic en ok, se llama al método restoreTransactionInformation. Esto hace que la aplicación cierre la fuerza.

Debido a que la facturación en la aplicación no funciona al depurar o en el emulador, tengo que publicar una versión firmada de la aplicación cada vez que quiera probar el código. No tengo manera de saber por qué se cierra la aplicación cuando intento hacer la solicitud restoreTransactionInformation. ¿Alguien tiene una pista de cómo puedo diagnosticar, o lo que podría estar causando mi aplicación para morir? ¿O un ejemplo práctico de cómo utilizar el método restoreTransactionInformation?

EDIT: Así que parece que la solicitud RESTORE_TRANSACTIONS está recibiendo una respuesta correcta, y los detalles de la devolución de mi compra de prueba. Por desgracia antes de que pueda hacer nada con él, la aplicación se ve obligado a cerrar. Aquí está un logcat (sin código ofuscado) de lo que sucede justo después del mercado responde a la petición RESTORE_TRANSACTIONS:

I/BillingService( 6484): confirmTransaction() D/Finsky ( 1884): [7] MarketBillingService.getPreferredAccount: com.hippypkg: Account from first account. I/BillingService( 6484): current request is:********** I/BillingService( 6484): RESTORE_TRANSACTIONS Sync Response code: RESULT_OK D/WindowManagerImpl( 6484): finishRemoveViewLocked, mViews[0]: com.android.internal.policy.impl.PhoneWindow$DecorView@********** W/InputManagerService( 1381): [unbindCurrentClientLocked] Disable input method client. W/InputManagerService( 1381): [startInputLocked] Enable input method client. D/NativeCrypto( 1884): returned from sslSelect() with result 1, error code 2 D/Finsky ( 1884): [1] MarketBillingService.sendResponseCode: Sending response RESULT_OK for request ********** to com.hippypkg. I/BillingService( 6484): Received action: com.android.vending.billing.PURCHASE_STATE_CHANGED I/BillingService( 6484): purchaseStateChanged got signedData: {"nonce":**********,"orders":[{"orderId":"**********","packageName":"com.hippypkg","productId":"hippy_upgrade_free_to_full","purchaseTime":1331476540000,"purchaseState":0}]} I/BillingService( 6484): purchaseStateChanged got signature: **********== I/BillingService( 6484): signedData: {"nonce":**********,"orders":[{"orderId":"**********","packageName":"com.hippypkg","productId":"hippy_upgrade_free_to_full","purchaseTime":1331476540000,"purchaseState":0}]} I/BillingService( 6484): signature: **********== I/BillingService( 6484): confirmTransaction() I/BillingService( 6484): makerequestbundle success I/BillingService( 6484): putstringarray success D/Finsky ( 1884): [24] MarketBillingService.getPreferredAccount: com.hippypkg: Account from first account. D/AndroidRuntime( 6484): Shutting down VM W/dalvikvm( 6484): threadid=1: thread exiting with uncaught exception (group=0x4001d5a0) E/AndroidRuntime( 6484): FATAL EXCEPTION: main E/AndroidRuntime( 6484): java.lang.RuntimeException: Unable to start receiver com.hippypkg.BillingReceiver: java.lang.NullPointerException E/AndroidRuntime( 6484): at android.app.ActivityThread.handleReceiver(ActivityThread.java:2144) E/AndroidRuntime( 6484): at android.app.ActivityThread.access$2400(ActivityThread.java:135) E/AndroidRuntime( 6484): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1114) E/AndroidRuntime( 6484): at android.os.Handler.dispatchMessage(Handler.java:99) E/AndroidRuntime( 6484): at android.os.Looper.loop(Looper.java:150) E/AndroidRuntime( 6484): at android.app.ActivityThread.main(ActivityThread.java:4385) E/AndroidRuntime( 6484): at java.lang.reflect.Method.invokeNative(Native Method) E/AndroidRuntime( 6484): at java.lang.reflect.Method.invoke(Method.java:507) E/AndroidRuntime( 6484): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849) E/AndroidRuntime( 6484): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607) E/AndroidRuntime( 6484): at dalvik.system.NativeStart.main(Native Method) E/AndroidRuntime( 6484): Caused by: java.lang.NullPointerException E/AndroidRuntime( 6484): at android.os.Parcel.readException(Parcel.java:1328) E/AndroidRuntime( 6484): at android.os.Parcel.readException(Parcel.java:1276) E/AndroidRuntime( 6484): at com.android.vending.billing.IMarketBillingService$Stub$Proxy.sendBillingRequest(IMarketBillingService.java:100) E/AndroidRuntime( 6484): at com.hippypkg.BillingHelper.confirmTransaction(BillingHelper.java:152) E/AndroidRuntime( 6484): at com.hippypkg.BillingHelper.verifyPurchase(BillingHelper.java:250) E/AndroidRuntime( 6484): at com.hippypkg.BillingReceiver.purchaseStateChanged(BillingReceiver.java:41) E/AndroidRuntime( 6484): at com.hippypkg.BillingReceiver.onReceive(BillingReceiver.java:23) E/AndroidRuntime( 6484): at android.app.ActivityThread.handleReceiver(ActivityThread.java:2103) E/AndroidRuntime( 6484): ... 10 more W/ActivityManager( 1381): Force finishing activity com.hippypkg/.Hippy 

Así que finalmente me las arreglé para entenderlo.

Si observa los Documentos de Google para la Visión general de la facturación de la aplicación, indica que:

El tipo de solicitud RESTORE_TRANSACTIONS también activa una intención de difusión PURCHASE_STATE_CHANGED, que contiene el mismo tipo de información de transacción que se envía durante una solicitud de compra, aunque no es necesario responder a esta intención con un mensaje CONFIRM_NOTIFICATIONS.

En un ciclo de compra-confirmación normal de transacción, cuando solicita la compra de un producto de facturación de aplicaciones, google envía un JSON con un montón de campos. Uno de estos campos es 'notification_id'. Cuando Google envía una intención PURCHASE_STATE_CHANGED, espera una respuesta CONFIRM_NOTIFICATIONS de la aplicación, con un paquete que contiene un montón de información, incluyendo los notification_id. Todo bien y bien aquí.

El problema se inicia cuando obtiene un PURCHASE_STATE_CHANGED de Google para una solicitud RESTORE_TRANSACTIONS de la aplicación. Este JSON no contiene campos de notificaion_id. La biblioteca sigue respondiendo con un CONFIRM_NOTIFICATIONS, agregando el array notification_id al paquete, que en este caso es null. Eso es lo que causa la excepción NullPointerException.

Modifiqué la clase de BillingHelper.java añadiendo un boolean para rastrear cuando el usuario hace una compra normal, y cuando él desea restoreTransactions. Si se trata de una solicitud restoreTransactions, envío un mensaje de nuevo al controlador y omita el paso confirmNotifications.

EDIT: El código de la solución anterior está en BillingHelper.java Estoy usando un indicador booleano para rastrear si el usuario realizó una llamada RESTORE_TRANSACTIONS (isRestoreTransactions).

En el método 'verifyPurchase' de BillingHelper.java, cambié el código de la siguiente manera:

 protected static void verifyPurchase(String signedData, String signature) { ArrayList<VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(signedData, signature); if(isRestoreTransaction) { /* * *Add some logic to retrieve the restored purchase product ID's from the 'purchases' array * */ //Set the boolean to false isRestoreTranscation = false; //Send a message to the handler, informing it that purchases were restored if(mCompletedHandler != null){ mCompletedHandler.sendEmptyMessage(0); } else { Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?"); } } else { /* *...... *...... *...... *Original method body here *...... *...... *...... */ } } 

Realmente no se puede hacer una distinción entre "reinstalar" y "datos de aplicación borrados". Son esencialmente los mismos: las preferencias compartidas están vacías. Tampoco debes hacerlo.

En cuanto al diagnóstico del problema, poner un botón de "restaurar las transacciones" y simplemente haga clic en diferentes estados (sólo instalado, bandera (s) conjunto, etc). Entonces mira el logcat.

Por cierto, podría ser mejor seguir con el código original de Google en un principio, obtendría más ayuda de esa manera. También hay algunos proyectos en Google Code que envuelven el código IAB para hacerlo un poco más fácil de integrar.

También estoy desarrollando una aplicación gratuita actualizable, pero he usado la muestra oficial en lugar del tutorial de Blundell, porque ese tutorial no guarda información y no usa elementos administrados

Simplemente eche un vistazo al método restoreDatabase () en el ejemplo de Dungeons, hace lo que quiera, verifica, usa SharedPreferences si es la primera ejecución y si se llama al método restoreTransactions.

Para depurar, solo conecta tu dispositivo a eclipse y comprueba el logcat, no te olvides de establecer la constante Debug (en Consts.java) a true y en el manifiesto establecer la etiqueta debugable a true también.

Para entender mejor el código de ejemplo, acabo de agregar más depuración y ahora funciona.

  • Android: en la facturación de la aplicación RESULT_SERVICE_UNAVAILABLE
  • ¿Qué identidad de usuario debe utilizarse para recopilar las compras en la aplicación de Google Play?
  • En la aplicación de facturación V3
  • Cómo solucionar los defectos de la lógica de adquisición de la API de los consumibles en la API de facturación de Google Play v3 (relevante para todos los consumibles con API v3)
  • Transferencia de la aplicación de Android con suscripción a otra cuenta
  • Error al agregar la biblioteca de facturación en la aplicación
  • Diferencia entre IABHelper y IInAppBillingService en InAPPBilling
  • IabHelper PurchaseFinishedListener
  • Prueba de la función de suscripción de facturación integrada en la aplicación
  • En Facturación de aplicaciones - Orientación rápida de dispositivos - causa bloqueo (IllegalStateException)
  • En el módulo BillingService, ¿qué necesita modificarse para aumentar la seguridad?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.