07-01-2013 12:08 PM
I am currently adding in-app purchases to one of my apps and I wanted to see if there were any best practices or suggested methods for dealing with existing purchases.
My app is a BB10 cascades app. What i am doing currently is when the app starts I call getExistingPurchases from the payment sdk and I am checking the returned id's ( if any ) and then setting some values in my apps QSettings based on whether certain upgrades have been purchased or not. I then use those values in QSettings to determine which features should be available. These settings are also being updated upon successful purchase in order to offer the new features right away. My reason for this design was to minimize the number of requests to the payment service and limit network/data usage.
So my question is, is this a good method. Is this going to be reliable enough for the user to always have the proper features available, while still secure enough to ensure that there is no way a user can circumvent the system and change the QSettings with out purchasing.
Any input would be greatly appreciated.
07-03-2013 09:01 AM
Great question! Best practices would be to 100% keep the goods stored locally, so using QSettings is a great option. So when the app is first launched you may want to make a call to getExistingPurchases(false) to poll BBW client cache, then if nothing is returned call getExistingPurchases(true) to call the BBW server, any results would be stored using QSettings.
There are a few scenarios where your QSettings can become out of sync:
1) The user makes a purchase on one device (ex PlayBook) then tries to use purchase on another (ex Z10)
2) The user restores their device to a backup made prior to when certain purchases were made
For 1 and 2 it make be a good idea to either provide a manual way for users to retrieve past purchases (button or menu item on the store screen) or perform periodic getExistingPurchases(true) queries.
getExistingPurchases is being improved in the back end so that when you call getExistingPurchases(false) it will be able to more logically determine if the BBW client cache may be stale and then perform a server call to retrieve past purchases. Once this is done you can expect a blog post from me on it
07-03-2013 09:22 AM
07-04-2013 08:33 AM - edited 07-04-2013 08:36 AM
Thanks for that info too.
I have currently implemented your suggestions and a sample Cascades app in draft mode, installed via appworld to test it with sandbox account. Everything seems to work, but when I try requestExistingPurchases(true) even after a successfull purchase, the server returns:
"ErrorCode 3", "The parent application could not be located within the reconcile cache"
Once this error occures, requestExistingPurchases(false) doe's not work either and throws the same error.
getPrice(), and purchase() are working so far.
07-04-2013 08:38 AM
Could you start a new thread with your question? Actually, may be worth searching the board for similar questions first. If you don't find an answer please add a post and I can help from there.
07-04-2013 08:55 AM - edited 07-04-2013 08:58 AM
Sorry Garett, I made some research, but have overlooked the existing thread. Now i have found it, and the answer. Thanks.
But one suggestion, regarding the topic. Freemium apps are highly recommendet by BB, therefore this is an important question. But the examples and the documentation do not answer the question. There is no COMPLETE example for a freemium app available which shows, how to handle it. Only puzzlepieces. A complete example would be great.
07-04-2013 09:00 AM
How about this one?
It may still hit a similar bug, but is a fairly complete sample, at least as far as showing the freemium portion.
Keep in mind that there are *many* ways to implement a freemium model, the above sample is just one.
07-04-2013 11:03 AM
yes, i know this example. But it doe's not really show, how to handle the question OP asked. I don't see, where the SKUowned reply is handled, maybe i missed it. And paymentServiceControl.getExisting(false) is triggered onCreationcomplete all the time, while paymentServiceControl.getExisting(true) is never used.
Instead of this it contains a lot of other things which are not necessary for the most common usecase: A simple upgrade from free to premium.
And the PaymentServiceControl class differs from others in other examples which was confusing me too.
I am currently writing an example which is only 200 lines of QML code on one single page (and the PaymentServiceControl Class) and contains everything you need to make a simple upgrade transaction for a premium version and handles also the suggestions you mentioned.
07-04-2013 11:14 AM
"But it doe's not really show, how to handle the question OP asked." <-- It really does
"I don't see, where the SKUowned reply is handled":
It is connected to the purchaseMade call.
"And paymentServiceControl.getExisting(false) is triggered onCreationcomplete all the time" <-- Check the C++ implementation, this method first checks QSettings, then makes the local cache call if no values are found, making it very efficient.
"while paymentServiceControl.getExisting(true) is never used":
"And the PaymentServiceControl class differs from others in other examples which was confusing me too." <-- It's a custom C++ implementation, they aren't meant to be standard. Once we have all controls exposed via QML the standardized functions will be available.
"Instead of this it contains a lot of other things which are not necessary for the most common usecase: A simple upgrade from free to premium." <-- This scenario is covered within the app. Rather than unlocking a "laser beam" or "removing ads" the app could "unlock the full features of your app". It's not an overly large step. As well this has been asked in the past on this board and a community member posted their sample for your exact requirement, upgrade from free to paid.
Please start a new thread with links to your sample when it gets completed, it would be great to have another sample that shows the free to premium upgrade model!
07-04-2013 12:01 PM
Thanks for your instruction. Now I get the picture of this sample. Maybe I was too impatient to go carefully through the whole code, therefore I missed these parts...
I'll post my simple sample when finished.