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.
- Expiración de facturación dentro de la aplicación
- Facturación personalizada en la aplicación para evitar las tarifas de transacciones de Android / iTunes
- Android Cómo obtener la lista de productos de la consola de GooglePlay
- Google Play Store está dando créditos adicionales al usuario?
- ¿Cómo puedo probar la facturación en la aplicación con una aplicación no publicada?
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
- ¿Cómo proteger Google In-App Billing v3 contra la piratería de código?
- Las compras en la aplicación realizadas a través de códigos promocionales devuelven la cadena de carga útil del desarrollador vacía
- Android InApp facturación envoltura?
- ¿Cuánto esfuerzo debo poner en mi seguridad para Android en la facturación de aplicaciones?
- Cómo crear una compra en la aplicación para actualizar la versión gratuita a la versión de pago de Android
- Comprador de comprobación de compra de Android en la compra sin conexión
- Error de facturación en la aplicación
- Estado de prueba gratuita de suscripción de IAB
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.
- Gradle NDK para especificar una directiva 'include' en Android.mk generado
- Hacer un teléfono Android para posar como un dispositivo de entrada Bluetooth (ratón o teclado)