Android in-app billing - restoreTransactionInformation

I am trying to make a free application upgradeable to a “paid” version using in-app billing. I used the code in this tutorial to treat billing like the one on the official developer site too complex and messy to follow a simple stream like mine.

My update code works fine.

The problem arises when I try to add something to check if the user has already been purchased, but has lost his purchase data either by reinstalling or by clearing the data (I don't care).

When starting the application, I check the flag set after the first launch. If this flag is not present, the user will be shown a dialog box warning that the application will check previous purchases, and when they click "OK", the restoreTransactionInformation method is called. This forces the application to force close.

Since billing in the application does not work during debugging or in the emulator, I have to publish a signed version of the application every time I want to try the code. I cannot understand why the application exits when I try to make a restoreTransactionInformation request. Does anyone know how I can diagnose it, or what could lead to the death of my application? Or a working example of how to use the restoreTransactionInformation method?

EDIT: It looks like the RESTORE_TRANSACTIONS request is getting the correct answer and returning information about my test purchase. Unfortunately, before he can do anything with this, the application is forcibly closed. Here logcat (without confusing code) of what happens immediately after entering the market responds to the RESTORE_TRANSACTIONS request:

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 
+7
source share
3 answers

So, I finally managed to figure it out.

If you look at Google Docs for an overview of incoming payments in the app, it states that:

The request type RESTORE_TRANSACTIONS also triggers the broadcast intent PURCHASE_STATE_CHANGED, which contains information about the same type of transaction that is sent during the purchase request , although you do not need to respond to this intention with the CONFIRM_NOTIFICATIONS message.

In the usual purchase-confirmation-confirmation cycle, when you request a purchase for a billing product, Google sends back JSON with a bunch of fields. One of these fields is "notification_id". When Google sends the intent PURCHASE_STATE_CHANGED, it expects a CONFIRM_NOTIFICATIONS response from the application, with a packet containing a bunch of information, including a notification. Everything is good and good here.

The problem starts when you get PURCHASE_STATE_CHANGED from Google to request RESTORE_TRANSACTIONS from the application. This JSON does not contain notificaion_id fields. The library still responds to CONFIRM_NOTIFICATIONS by adding the notification_id array to the package, which in this case is NULL. This throws a NullPointerException.

Solution: I changed the BillingHelper.java class, adding a boolean value to track when a user makes a regular purchase and when he wants to recover transactions. If this is a restoreTransactions request, I send a message to the handler and skip the confirmNotifications step.

EDIT: The code for the above fix is ​​in BillingHelper.java I use a boolean flag to track whether the user called RESTORE_TRANSACTIONS (isRestoreTransactions).

In the BillingHelper.java 'verifyPurchase' method, I changed the code as follows:

 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 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 *...... *...... *...... */ } } 
+4
source

You cannot distinguish between “reinstallation” and “cleared application data”. They are essentially the same: general preferences are empty. Also not necessary.

As for diagnosing the problem, put the "Restore Transactions" button and just click it in different states (just installed, set flags, etc.). Then watch the logarithm.

By the way, maybe it’s better to stick with the original Google code first, you will get more help in this direction. There are also some projects in Google Code that wrap IAB code to make it a little easier to integrate.

+4
source

I am also developing a free app update, but I used the official sample instead of the Blundell tutorial because this tutorial does not save information and does not use managed elements

Just take a look at the restoreDatabase () method in the Dungeons example, it does what you want, checks using SharedPreferences if this is the first run, and if it calls the restoreTransactions method.

To debug, just connect your device to the eclipse and check the logarithm, just remember to set the Debug constant (in Consts.java) to true and in the manifest also set the debug tag to true.

To better understand the sample code, I just added a lot more debugging and now its working.

0
source

All Articles