Android InApp-Purchases not calling transaction handler

Hey everyone!

I just tried the new Android InApp-Purchases from the daily build with my own app, and it didn’t seem to work properly.

Then i tried the InApp-Demo from the sample folder, and it didn’t work as well. Everything seems fine - i select the first product (which maps to “android.test.purchased”), the Marketplace app gets started and i can buy the product.

I get a success message from the marketplace and it switches back to the app. But the given transactionHandler never gets called, so it is not able to react properly on the purchase.

Am i missing something?

Thanks in advance for any help in this matter! =)

Best regards,
Marian [import]uid: 109308 topic_id: 22914 reply_id: 322914[/import]

No ideas, anyone? =|

Interestingly enough, the transaction handler gets properly called for the android.test.canceled and android.test.item_unavailable products. But not for android.test.purchased.

This is a snippet of the log file

[…]
D/Finsky (24200): [1] MarketBillingService.sendResponseCode: Sending response RESULT_ERROR for request 3891213049123785337 to com.flaregames.games.inappdemo.
D/Finsky (24200): [1] PendingNotificationsService.setMarketAlarm: Setting alarm for account=xxxxxx@googlemail.com, duration=120000
I/Corona (24169): transactionCallback: Received event storeTransaction
I/Corona (24169): state: failed
I/Corona (24169): errorType: unknown
I/Corona (24169): errorString:
I/Corona (24169): Transaction failed, type: unknown
[…]

As far as i can see, the response is handled properly by corona. This does not seem to be the case when purchasing:

[…]
D/Finsky (24200): [1] MarketBillingService.sendResponseCode: Sending response RESULT_OK for request 6127319678056875116 to com.flaregames.games.inappdemo.
D/Finsky (24200): [1] PendingNotificationsService.setMarketAlarm: Setting alarm for account=xxxxxx@googlemail.com, duration=120000

And then - nothing happens anymore. No call of the transactionHandler whatsoever, despite the fact that some RESULT_OK is being sent as response.

Am i doing something wrong?

I tested on a Samsung GT-I9003 with Android v2.3.5 and Marketplace v3.4.4

Help is pretty much appreciated!

Best regards and thanks in advance,
Marian

[import]uid: 109308 topic_id: 22914 reply_id: 91935[/import]

Hello Marian,

Purchases are definitely working for me and I’ve tried it on multiple devices. What’s happening is that you are getting a response OK from the Marketplace app on your device (purchases are done via this app and not directly via the Internet), but you are getting no notifications at all from the Android Marketplace server.

I did a quick Internet search and it seems other developers have ran into this issue as well. Please see the following link for bug reported to Google about this matter…
http://code.google.com/p/android/issues/detail?id=16042

Now, people on that thread claim that this issue will fix itself if you reboot your phone. Can you try that and see if it works?

Not that this is a real solution for your customers, but at least it will help us identify the issue. If this turns out to be a common issue, then I might have to implement a timeout in Corona to “give up” on Google if we do not receive a notification in time. [import]uid: 32256 topic_id: 22914 reply_id: 93104[/import]

Hey Joshua!

Thanks for your answer - unfortunately, i tried this before and it didn’t help. It seems to me that the marketplace is working ok, as far as i can read from the log output. This is the output from the last “exit point” to the return code:

I/Corona ( 2261): Ka-ching! Purchasing android.test.purchased
D/Finsky ( 2338): [7] MarketBillingService.getPreferredAccount: com.test.inapptest: Account from first account.
D/Finsky ( 2338): [7] MarketBillingService.getPreferredAccount: com.test.inapptest: Account from first account.
D/Finsky ( 2338): [1] DfeApiContext.getSmallestScreenWidthDp: smallestScreenWidthDp does not exist, using pre-ics hack.
D/Finsky ( 2338): [1] SelfUpdateScheduler.checkForSelfUpdate: Skipping self-update. Local Version [8010007] >= Server Version [0]
D/Finsky ( 2338): [1] GetBillingCountriesAction.run: Skip getting fresh list of billing countries.
W/Finsky ( 2338): [1] CarrierParamsAction.run: Saving carrier billing params failed.
D/Finsky ( 2338): [1] CarrierProvisioningAction.shouldFetchProvisioning: Required CarrierBillingParams missing. Shouldn’t fetch provisioning.
D/Finsky ( 2338): [1] CarrierProvisioningAction.run: No need to fetch provisioning from carrier.
D/Finsky ( 2338): [1] CarrierProvisioningAction.shouldFetchProvisioning: Required CarrierBillingParams missing. Shouldn’t fetch provisioning.
D/Finsky ( 2338): [1] CarrierProvisioningAction.run: No need to fetch provisioning from carrier.
D/Finsky ( 2338): [1] PurchaseFragment.onStateChange: Finished purchase with ID https://android.clients.google.com/fdfe/purchaseStatus?doc=inapp:com.test.inapptest:android.test.purchased&order=transactionId.android.test.purchased
D/Finsky ( 2338): [1] MarketBillingService.sendResponseCode: Sending response RESULT_OK for request 4335309444068578400 to com.test.inapptest.
D/Finsky ( 2338): [1] PendingNotificationsService.setMarketAlarm: Setting alarm for account=hiddenemail@googlemail.com, duration=120000
For comparison - this is what i when “purchasing” the canceled-Testproduct:

I/Corona ( 2261): Ka-ching! Purchasing android.test.canceled
D/Finsky ( 2338): [7] MarketBillingService.getPreferredAccount: com.test.inapptest: Account from first account.
D/Finsky ( 2338): [7] MarketBillingService.getPreferredAccount: com.test.inapptest: Account from first account.
D/Finsky ( 2338): [1] SelfUpdateScheduler.checkForSelfUpdate: Skipping self-update. Local Version [8010007] >= Server Version [0]
W/Finsky ( 2338): [1] CarrierParamsAction.run: Saving carrier billing params failed.
D/Finsky ( 2338): [1] CarrierProvisioningAction.shouldFetchProvisioning: Required CarrierBillingParams missing. Shouldn’t fetch provisioning.
D/Finsky ( 2338): [1] CarrierProvisioningAction.run: No need to fetch provisioning from carrier.
D/Finsky ( 2338): [1] GetBillingCountriesAction.run: Skip getting fresh list of billing countries.
D/Finsky ( 2338): [1] CarrierProvisioningAction.shouldFetchProvisioning: Required CarrierBillingParams missing. Shouldn’t fetch provisioning.
D/Finsky ( 2338): [1] CarrierProvisioningAction.run: No need to fetch provisioning from carrier.
D/Finsky ( 2338): [1] PurchaseFragment.onStateChange: Finished purchase with ID https://android.clients.google.com/fdfe/purchaseStatus?doc=inapp:com.test.inapptest:android.test.canceled&order=transactionId.android.test.canceled
W/Finsky ( 2338): [1] PurchaseInitiator.switchToError: Error when purchasing document inapp:com.test.inapptest:android.test.canceled: Error{title=‘Kauf storniert’, docTitle=‘Titel (Beispiel)’, briefMessage=‘Kauf bei Google Wallet prüfen’, detailedMessage=‘Ihre Zahlung konnte nicht verarbeitet werden. Melden Sie sich in Ihrem Google Wallet-Konto an, um Support anzufordern.’, sourceUrl=‘details?doc=inapp:com.test.inapptest:android.test.canceled’, detailsUrl=‘https://checkout.google.com’}
D/Finsky ( 2338): [1] MarketBillingService.sendResponseCode: Sending response RESULT_ERROR for request 8314807378565835402 to com.test.inapptest.
D/Finsky ( 2338): [1] PendingNotificationsService.setMarketAlarm: Setting alarm for account=hiddenemail@googlemail.com, duration=120000
I/Corona ( 2261): transactionCallback: Received event storeTransaction
I/Corona ( 2261): state: failed
I/Corona ( 2261): errorType: unknown
I/Corona ( 2261): errorString:
I/Corona ( 2261): Transaction failed, type: unknown
Especially interesting to me are the lines:

D/Finsky ( 2338): [1] MarketBillingService.sendResponseCode: Sending response RESULT_ERROR for request 8314807378565835402 to com.test.inapptest.

and

D/Finsky ( 2338): [1] MarketBillingService.sendResponseCode: Sending response RESULT_OK for request 4335309444068578400 to com.test.inapptest.

As far as i understand them, they basically signal that the communication with the marketplace server went well and a result code is to be emitted to the app which initiated the purchase. Am i wrong there?

I will have another look into that matter - maybe i’ll find a reason or solution today, but a reboot definitely did not help so far. =|

In case i find out what’s happening, i’ll definitely post the solution here. =)

Best regards,

Marian

[import]uid: 109308 topic_id: 22914 reply_id: 93247[/import]

Hey!

The problem has been resolved - i tested the case with out WiFi only, which did not work at all for the examples given, even with deactivated proxies and firewall. As soon as i’m using a SIM card and the carriers network, i get the expected response for android.test.purchased as well.

We did not go down to the level where we examine exactly where the problem with our provider could lie, because right now it works well enough for us.

So the problem has obviously nothing to do with corona, but in part with quite misleading log entries of the marketplace app.

Thanks everyone for looking into this!

Best regards,

Marian [import]uid: 109308 topic_id: 22914 reply_id: 93281[/import]

Thanks for posting your results Marian. This is definitely good info to share.

Just to make things more interesting, I’ve tested in-app purchases with a phone that is not connected with a carrier but has a SIM card and purchases definitely worked. Perhaps a missing SIM card is what breaks it? Weird. [import]uid: 32256 topic_id: 22914 reply_id: 93329[/import]

Ok

I have managed to get the callback to work somehow. But, when doing a purchase with a real credit card, how do we get the receipt information to verify with google’s servers?

event.transaction.receipt only returns the product id.

So yeah, I got it to work with a real CC, but don’t know how to get the info so we can verify on our servers. There should be a signature somewhere.

Thanks
Hoan
[import]uid: 22829 topic_id: 22914 reply_id: 93378[/import]

Android does not provide a receipt string like how Apple does it. If you received the “purchased” notification, then that means the credit card was accepted and the purchase was approved. You would receive an error notification if the credit card was turned down.

I’m not sure what you are trying to verify. Can you clarify please? Thanks. [import]uid: 32256 topic_id: 22914 reply_id: 93407[/import]

Using cryptography we can verify that the purchase was legitimate

We just need these:

"\_responseCode|\_nonce|\_packageName|\_versionCode|\_userId|\_timestamp"

“A simple PHP library for verifying responses from the Android Market licensing service. By verifying responses signed using public-key cryptography you can check whether a user has genuinely purchased your application or in-app product.”

Here is more info:

http://security.stackexchange.com/questions/4067/how-should-i-protect-in-app-android-payments-when-using-a-seperate-server
http://code.google.com/p/android-market-license-verification/

There is little information on this, but we gotta make sure that corona provides all the required information to verify a purchase.

Thanks
Hoan

[import]uid: 22829 topic_id: 22914 reply_id: 93411[/import]

Oh I see. You’re worried about people who hack the Android Marketplace app to approve all purchase requests. Correct?

Google actually already has a solution for this. It’s the public key that you can assign to your app in the marketplace. This can be used to verify that the purchase response actually came from the Android Marketplace. This is discussed here…
http://developer.android.com/guide/market/billing/billing_integrate.html#billing-signatures

At the moment, Corona does not support the “public key” mentioned in the above doc. We do support the nonce though, but that won’t help if the Marketplace app has been hacked. But would this public key support be enough for you? It should be… and it is the easiest to implement.
Or do you feel that you absolutely need to verify purchases via your server?

I say this because I’m trying to keep the store API as cross-platform as possible. As in I’m trying to keep the Android specific transaction info so that is still easy to support other stores such as Apple’s and other ones we plan on supporting in the future. [import]uid: 32256 topic_id: 22914 reply_id: 93614[/import]

Joshua,

Right now our system works like this:

* User presses confirm on the IAP
* We send the purchase info to our servers
* We then give our user the virtual goods (on the server)

If we can have all the information to do the verification cryptographically (like the nonce, etc), then we’ll do the verification on the server side. Verification on server side is always more secure. Client side verification can always be hacked.

So you won’t have to add a new method or anything like that, just expose this data:

"\_responseCode|\_nonce|\_packageName|\_versionCode|\_userId|\_timestamp"

That is what is required according to the php verification library

I hope this makes sense to you.

Hoan [import]uid: 22829 topic_id: 22914 reply_id: 93617[/import]

Also reading the google docs:

“Every PURCHASE_STATE_CHANGED broadcast intent also includes a signed JSON string and a signature, which you can use to verify the integrity of the response.”

If you could pass the JSON string and the signature too, that will be needed. I’m still figuring out how to do the verification on the server side.

This page has info:

http://stackoverflow.com/questions/9264122/verify-in-app-billing-signature-on-server-side

“You can use the OpenSSL library. The response that your app receives from the Market is a string of JSON data and a signature string which is created using the private key for the public key that is in your developer profile. You should keep that public key on your server, then your app can pass on the JSON string and signature to your server for verification.”
[import]uid: 22829 topic_id: 22914 reply_id: 93618[/import]

hoan,

Sorry about the late response.

I did a quick lookup on what the receipt string stores on iOS and it looks like Apple stores the JSON transaction string there. (Please forgive my ignorance for not knowing this. I only work on the Android and Windows code here at Ansca.)

So, it sounds like we should do the same on Android. This JSON string contains the following information:

  • “purchaseState”: Number indicating if the product was purchased, canceled, refunded, etc.
  • “productId”: The unique string key of the product. The same string that you feed into the store.purchase() function.
  • “packageName”: The unique package name of your application.
  • “purchaseTime”: The timestamp the transaction was made in UNIX time; milliseconds since 1970.
  • “orderId”: Unique string ID assigned to the purchase order.
  • “notificationId”: Unique string ID assigned to the transaction. Used internally by Corona to match responses with requests.
  • “developerPayload”: Optional string assigned to the product by the app developer.

The JSON signature that we receive from Google is not part of the JSON string… but are you sure you need that? We can verify that signature internally within Corona, but it would involve giving Corona that public key I was telling you about in my previous thread which is something our store API does not support yet.
[import]uid: 32256 topic_id: 22914 reply_id: 94530[/import]

Hello,

what is the current status of the Android purchase?

I would also need to verify purchases with an external server, (the signed JSON message) since the consumable content is handled by this server. [import]uid: 11772 topic_id: 22914 reply_id: 116976[/import]

aleeri,

We haven’t done anything about this. You and hoan are the only 2 people requesting us to expose the JSON message. Until there is more demand, we’re going to have to focus on things that are in high demand. Such as the features/enhancements listed on our roadmap here…
http://www.coronalabs.com/resources/roadmap/
[import]uid: 32256 topic_id: 22914 reply_id: 117041[/import]

I want to send the recepit to our server to signature verification.
But receipt data comes a 15 digit number, so server can’t handle it.

How can I get the data that the server need to perform signature verification step through corona’s IAP api? [import]uid: 155824 topic_id: 22914 reply_id: 117138[/import]

hello,

i have the same problem as aleeri has. i would like to verify Android in app purchases with an external server. i use signed JSON messages as well. so, now we are 3 people with that problem… [import]uid: 12022 topic_id: 22914 reply_id: 117487[/import]

Okay… I’ll write this up as a feature request. No ETA yet, but at least it’s on our to-do list. [import]uid: 32256 topic_id: 22914 reply_id: 117583[/import]

i have the same problem as aleeri has. i would like to verify Android in app purchases with an external server. i use signed JSON messages as well. so, now we are 3 people with that problem…(2)

hurry up !!! ?? [import]uid: 134563 topic_id: 22914 reply_id: 117633[/import]

I want to send the recepit to our server to signature verification.

Have not been able to market.

How can I get the data that the server need to perform signature verification step through corona’s IAP api? [import]uid: 150912 topic_id: 22914 reply_id: 117635[/import]