[Resolved] Goole IAP: need to check transaction callback (refunded event)

Naomi, I have to say i’m watching you every post, I am in the middle of writing an app that is scheduled to have a bunch of In-App purchases and you started these post’s that I needed to review just when I needed them :slight_smile:

Larry [import]uid: 11860 topic_id: 24973 reply_id: 101409[/import]

This answers my question about refunds but my next question would be when to call the store.finishTransaction() function. Just in some common area in the menu every time they pass it? [import]uid: 126161 topic_id: 24973 reply_id: 101411[/import]

Oh sweet thanks for the code.

[import]uid: 11860 topic_id: 24973 reply_id: 101430[/import]

You are aware right that there are NO refunds on the Android Market IAP’s correct? [import]uid: 29181 topic_id: 24973 reply_id: 101464[/import]

Hey, Ninja Pig, I’m not sure what you mean?

Here’s what I can do so far:

  1. As a test user, buy unmanaged item (charging to the test user’s real credit card).

  2. I go to Google Checkout, signing in as the merchant/publisher who owns the game.

  3. I see the purchase transaction, showing the name of the test user, what the test user bought and the amount of payment charged to his/her credit card.

  4. While the credit card has not been processed, I can cancel the transaction. However, once the credit card charge is fully processed, instead of canceling the transaction, I can refund the transaction.

  5. As soon as I refund the transaction, the payout to me as a merchant is reduced by the amount refunded.

  6. The test user receives an email, telling him/her that the money was refunded.

  7. Android IAP integrated into Corona SDK receives “refunded” event notification, and upon receipt, it fires a function inside the “refunded” event. And the function I wrote in there does its work and removes the virtual item from the game.

Did you mean that Android IAP does not automatically take away the products upon refunded event? If so, you are correct. All it does is to let the app/game know that the refund happened. We need to code what we want our app/game to do when the game receives the info.

I hope this makes sense?

Naomi
[import]uid: 67217 topic_id: 24973 reply_id: 101476[/import]

Oh I didn’t understand that’s how it worked. Honestly though, if someone buys an IAP you shouldn’t refund them. [import]uid: 29181 topic_id: 24973 reply_id: 101491[/import]

Ha ha ha. Yeah, we wouldn’t want to refund them, do we?

We’d need to read some fine prints regarding how merchants are expected to behave, but based on my experience, if there is a dispute, paying consumers always win, and it really doesn’t help when a merchant refuses to make the refund without a good reason . Especially when the product sold is a virtual item, I don’t know how easy/difficult it is to keep the money when the consumer demands it back.

I don’t know the “chargeback” policy at Google Play/Checkout, but merchants can be hit by a hefty chargeback, which can cost more than simply refunding some disgruntled (or sleazy) customer – especially if the product isn’t a very expensive item.

Naomi

P.S. The user who demands refund would be a better user than those who simply pirate and play for free, don’t you think? [import]uid: 67217 topic_id: 24973 reply_id: 101500[/import]

@doubleslashdesign, glad to hear my posts are timely for you. :wink:

@3 Dreams Gaming, store.finishTransaction() function is called from inside the transactionCallback, and transactionCallback is called when I initialize the store in main.lua.


Edit: So, I think, calling once (i.e., initializing once) at the start of the game is enough… Ah, but I see, if the user left the game in some suspended state, how do we initialize the store again when the user resume from some suspended state? I suppose if it was suspended, the store is still active (and eventually resume listening for the transaction). At least that seems like how it should be handled? Hmmm… good question though. May need some further testing on that one. :slight_smile:

Here’s the relevant code structure, and in the order they show up:

[lua]-- main.lua

function transactionCallback(event)
if transaction.state == “purchased” then

elseif transaction.state == “restored” then

elseif transaction.state == “refunded” then

elseif transaction.state == “cancelled” then

elseif transaction.state == “failed” then

else

end

store.finishTransaction( transaction )

end

– Connect to store at startup
if store.availableStores.apple then
store.init(“apple”, transactionCallback)
elseif store.availableStores.google then
store.init(“google”, transactionCallback)
end [/lua]

To fill out the details, might be wroth reading up on this tutorial blog post:

http://blog.anscamobile.com/2012/03/getting-started-with-android-in-app-billing/

Naomi [import]uid: 67217 topic_id: 24973 reply_id: 101427[/import]

Everyone,

You should only call the store.finishTransaction() function AFTER you saved the purchased item to file/database. This is just in case your app crashes or has been force-quit by the device (due to low memory) before it has a chance to write the purchased item setting to file.

If the purchase involves downloadable content (aka: DLC), then you should only call the store.finishTransaction() function AFTER it has been downloaded, such as in Corona’s network listener. This is more likely to fail on a mobile device since Internet access can easily be lost while the user is walking around.

If your app does crash or if the download fails, then you should not call the store.finishTransaction() function. Instead, you should wait for the Android marketplace to send the purchase notification again and re-attempt to save/download the purchased item in your app. The Android marketplace will send the purchase notification repeatedly until your app finishes it or until the marketplace gives up and times it out. [import]uid: 32256 topic_id: 24973 reply_id: 101665[/import]

Naomi,

You only have to call store.init() once on app startup. You do not have to call this function after a suspend/resume. Corona will continue to listen for marketplace notifications even while suspended, but it won’t send the notifications to your Lua code until it resumes. Corona will just queue up the notification until resumed and then send it to your Lua callback. [import]uid: 32256 topic_id: 24973 reply_id: 101678[/import]

Edit: I went back to basics and looked at http://lua-users.org/wiki/CoroutinesTutorial – and it sounds like to me that I don’t really need to change the code structure I have (posted in #5 above). So, I’m scratching the idea posted in #13 above. The functions placed inside the if/elseif statement inside the transactionCallback has to complete (without an error) before store.finishTransaction() is triggered anyway.

I think what Joshua meant is, if content delivery must take place (and if the execution of such delivery takes a while), there will be a real risk for unsuccessful delivery. So, you don’t want to call store.finishTransaction() merely after setting a flag (or whatever else) for the app/game to download the content (but not actually executing the delivery).

Since I’m executing the actual delivery of the purchased item in the savePurchase function itself, and since store.finishTransaction() is called outside the if/elseif statement, I think it does what it should.

Whew. Got confused a bit there.

Naomi

P.S., thank you for clarifying how Corona handles store & store related notifications at suspend/resume event. [import]uid: 67217 topic_id: 24973 reply_id: 101687[/import]

Thank you, Joshua, for the detailed message as to how we should handle store.finishTransaction() function.

I have a question relating to this. Using single code base, I build for both iOS and Android, and most of the IAP related code did not need any changes (which is awesome, by the way.)

Currently, the store.finishTransaction appear inside the transactionCallback (as shown in the shell structure posted above), and I believe that’s the best place for iOS version. (If it isn’t true, please let me know.)

Edit: I’m scratching the alternative code structure I posted. I think I misunderstood what was intended/suggested.

[code deleted – because it wouldn’t do any good.]

Naomi

P.S. I just posted a question regarding “failed” event on tutorial blog comment section. I debated whether I should simply start a new thread or post it on the blog – couldn’t tell which might be the better way to handle it, and ended up posting a comment on the blog. Please let me know what is more appropriate approach when I have a very specific question like this:

http://blog.anscamobile.com/2012/03/getting-started-with-android-in-app-billing/comment-page-1/#comment-7535

If it’s better to start a new thread, please feel free to have the comment removed from the blog post. I’m happy to repost it here (or under “cancelled” event related thread.) Thanks!
[import]uid: 67217 topic_id: 24973 reply_id: 101686[/import]

Right. If you don’t have any downloadable content, then finishing the transaction within the transaction callback will work just fine.

So, does this mean everything is working correctly for you now?
Just following up to be sure. [import]uid: 32256 topic_id: 24973 reply_id: 101978[/import]

Thank you so much, Joshua, for following up. Yes, everything is working well. Only “unknown” I have is the “failed” event. I’m not sure if I should handle it like I do with “cancelled” event (i.e., message the user that transaction failed), or if I should mirror the “refunded” event (i.e., message the user and take away the purchased item.)

The reason why I’m not sure is because I have not encountered “failed” event during my testing – so I don’t know what might cause the failure. Would it be always before the transaction completes? Or can it happen both before and after the transaction completes? I’m assuming it’s before the “purchased” event takes place, but I could think of a few reasons why transaction might fail after “purchased” state occur (especially after seeing “cancelled” event can happen both before and after the “purchased” event.)

Thanks again.

Naomi

Edit: Hey, Joshua, I saw your reply post on Blog section just now: http://blog.anscamobile.com/2012/03/getting-started-with-android-in-app-billing/

Thank you for getting back to me on it. [import]uid: 67217 topic_id: 24973 reply_id: 101992[/import]

The “failed” event is really general and can mean many things. On Android, Google’s Android Marketplace does not give us any error message at all. So, the “event.transaction.errorString” sent to the transaction callback will always be an empty string. My advise would be to display your own error message if “event.transaction.errorString” is nil or empty (ie: string length <= 0).

Now, you can determine the exact nature of the error by looking at the “event.transaction.errorType” property, which returns a string. Here are all of the possible error types:

  • “none”: Indicates that no error occurred.
  • “unknown”: Indicates that an unknown or unexpected error occurred.
  • “cancelled”: Indicates that the transaction/payment was cancelled. If the “event.transaction.state” is set to “failed”, then this indicates the credit card transaction was cancelled after the user approved the purchase in your app.
  • “invalidClient”: Indicates that the mobile device is not properly configured to make in-app purchases. For example, an account has not be assigned to the marketplace app, credit card information is wrong, or the user has not agreed to the marketplace license agreement yet.
  • “invalidPayment”: Indicates that the marketplace did not recognize the payment settings provided to the marketplace app. (I’ve never seen this error happen on Android.)
  • “paymentNotAllowed”: Indicates that the user is not authorized to make purchases on the device. For example, parental controls to prevent kids from making purchases.
    [import]uid: 32256 topic_id: 24973 reply_id: 102162[/import]

Wow, Joshua, thank you for the laundry list of all possible error types. This definitely will help dealing with the error handling/messaging.

Thank you so much!

Naomi [import]uid: 67217 topic_id: 24973 reply_id: 102165[/import]

Hey, Joshua, I’d like one more clarification regarding the event.transaction.errorType.

Does App Store return the same errorType, or does the list of errorType you provided above only work with Google Play?

The reason why I’m asking this is because I want to make an educated decision for error handling messages. If they don’t mirror each other, I’ll probably use simple “cancelled” or "failed message for iOS while I may give additional reason why it was cancelled or failed for Android.

Naomi [import]uid: 67217 topic_id: 24973 reply_id: 102962[/import]

The error types exactly match how they are used on iOS. That is, the error type string keys and the reasons they happen exactly match between iOS and Android. So your code for handling these errors should be the same. We try to make it easy on everyone. :slight_smile:

Now, there might be differences in behavior in regards to error alert boxes between iOS and Android. For example, some error types might cause iOS to display an error alert while the equivalent error type on Android might not. I haven’t fully investigated this for myself.

Although I did notice that different version of the Android Marketplace app (before it became Google Play) would display an error alert message for an “invalidClient” while the old Android 2.2 version would not. This inconsistency with Android’s store alone makes me think that the best way to handle errors is to not display an alert message and instead just label the product within the app as “Failed to purchase” and let the user try again. Now that’s just my opinion. Just be aware that for the “invalidClient” error alert on the newest Android devices will give the user the chance to fix it and then continue with the purchase… in which case just wait for the successful “purchase” transaction state to be received.

Anyways, that’s my 2 cents. [import]uid: 32256 topic_id: 24973 reply_id: 103127[/import]

Thank you, Joshua. Your detailed explanation really helps! Yes, I’ll probably not display the actual error that the store returns (but I may consider using my own error code to show – just in case we need to communicate with users who want to know why – but maybe that’s not even necessary…)

Thanks again!

Naomi

[import]uid: 67217 topic_id: 24973 reply_id: 103144[/import]

Great! Good luck with your app! [import]uid: 32256 topic_id: 24973 reply_id: 103162[/import]