04-12-2012 09:21 AM - edited 04-12-2012 09:47 AM
Edit: In this post I talk about a hack to get the window group. The cocos2d-x site was down yesterday while I was at work. You can avoid that ugly hack by using CCEGLView::sharedOpenGLView().getWindowGroupId()
I've been adding support for in-app purchases to a game I'm working on since yesterday afternoon and it's been a bit of a hassle. So I've decided to do this post-mortem describing what I had to go through to get things working. Some things I got stuck on you guys might have answers to so please feel free to point things out. This will hopefully act as a good debugging guide for people having issues with in-app purchases.
So you want to have transactions in your native game or application and you're not quite sure where to start? You might have stumbled upon the Selling Digital Goods page already as I did but you'll quickly notice three things.
1) The syntax error in the code provided (tisk tisk) Missing a curly brace on an else statement.
2) A call to the function get_window_group_id() that doesn't exist anywhere.
3) The sample code hangs indefinitely.
Maybe you're lucky (or smart) and didn't have #3 happen to you. What wasn't obvious to me was that you need a window group in order to support in app purchases.
Although getting a window group wasn't hard in the NDK samples, it was not easy to do with Cocos2d-x. In order to create a window group you need the screen_window_t structure you used to create your window. Cocos2d-x has that hidden as a private member of CCEGLView.
Modifying Cocos2d-x wasn't an option for me. I work on a team with several others, and Cocos2d-x is not a part of the repository, we all have a local version of it that we reference from the project. So modifying Cocos2d-x would require me passing around modified source which would be a nightmare. So instead I devised a hack to get this working.
Please note at this point that what I'm about to say is a dirty hack. I'm not recommending this, I'm just saying how I did it.
I made a local copy of the CCEGLView_qnx.h and simply made m_screenWindow public. If we include my local header before the cocos headers the include guards in my version get hit before the actual cocos version. Preventing the cocos version from ever being hit and creating conflicts. This was I can check in my local copy to the repository and people who check out the source can still build it without any issues.
Now that I have a window context I can make my call to screen_create_window_group.
Now of course you're a wise developer and decided to muli-thread your solution. You learned your lesson after blocking indefinitely by simply not having a window group. But you may run into a couple issues still.
It's pretty well documented that you need to call bps_initialize and bps_shutdown from your worker thread, but maybe you forgot. Simple enough - just make those calls from your thread. However what you may not have realized is that paymentservice_set_connection_mode is local to your thread. Funny enough that's not documented anywhere at all. For me that looked like setting the parameter to true and false doesn't do anything and that local mode was broken some how.
Now that you've figured that out you'll probably be interested in writing verbose error handling, making your code really easy to debug. Brilliantly we're given two functions paymentservice_event_get_error_id and paymentservice_event_get_error_text. You can use these after you get a FAILURE_RESPONSE from an event. So now you're logging the error number and text and you want to have something else happen depending on what the error code is.
So.... do we just switch on the error number? Sounds reasonable. So what do those error codes mean? In the documentation that has proved to be ever so helpful thus far we can note the comments for paymentservice_event_get_error_id saying simply "@return The error ID.". Okay so that wasn't helpful - maybe the online documentation is better. The online documentation says the same thing.
Well, that's not a problem. We're smart developers and we value debuggable code over the sweat of our brow. I'm willing to reproduce the error scenarios and pull the error codes from the console and enumerate the possibilities myself. Luckily the local mode lets us simply choose a state we want to reproduce and simply do that.
So you boot up the device, and you have logging code around the error ID to see what it gives you for each one. Pen and paper ready you're calling each of the six configurations only to soon realize Local mode always returns 0 for the error codes. Bah!!! That's annoying! I refuse to do string comparisons on the return from paymentservice_event_get_error_text so what am I left to do? Not a lot really. I guess I could put the product online in sandbox mode and try to actually reproduce the scenarios but I simply don't have the time.
Even without that, I successfully have a beautiful interface to create merchandise in my game with easy to use handlers in a multi-threaded approach.
How about you guys? Any similar things happen to you when trying to get support for in-app purchases? Did I simply miss documentation somewhere about one of these things? Let me know, I'd be interested to get feedback on my first post-mortem.
04-13-2012 03:27 PM
Nice write-up! I'll be sure to forward your comments and found issues along.
For the Error IDs you can use the documentation from the AIR development site for now, they are the same as what should be expected for Native:
https://bdsc.webapps.blackberry.com/air/apis/net/r
I will be sure to get the native site updated shortly to include this info as well.
Thanks,
01-13-2013 10:03 AM - edited 01-13-2013 10:26 AM
Hi,
Thank you for the post, will definitely find it useful. Currently I'm hanged upon the
if (event) {
if (bps_event_get_domain(event) == paymentservice_get_domain())part of the in app sample - I just can't get the event from the proper domain...
But that's not what I wanted to write. Regarding that window group thing. What I did is:
#define private public #include "platform/blackberry/CCEGLView.h" #undef private
and later on simply:
screen_create_window_group(CCEGLView::sharedOpenGLView()->m_screenWindow, CCEGLView::sharedOpenGLView()->m_windowGroupID);
paymentservice_purchase_arguments_set_group_id(args, CCEGLView::sharedOpenGLView()->m_windowGroupID);
Still - not a very proper sounding solution, but has one advantage over yours - it's using the actual cocos header file, so you don't have to check if the file changes with subsequent cocos releases. I'm quite curious what people have to tell on this way of doing it ![]()
Edit: I still cannot find CCEGLView::getWindowGroupId method in my cocos2dx release....
Edit2: Oh, I see - I have to set the window group first, because cocos doesn't do it. Not very intuitive imho (can't the payment system just set it, since it's always required?), but it's working. Big thanks for your post, I wouldn't get it that fast otherwise.
01-13-2013 10:42 AM