I am working on an Android Game. I got stuck at In app purchase programming.
I have decided to use Soomla Unity IAP plugin.
I tried their sample program of muffins, that worked well.
But I did not get idea how would I know if some one purchased coins(Or any good) from my game.
I have seen some videos on youtube, I have gone through git hub page of SOOMLA but didn't find anything which can clear my doubts.
Please help me out guys Or refer any worthy material you know.
Thank you !!
Your question isn't specific enough so I'll try to address several ways to go:
First, assuming you are selling coins for real money (market purchases), you want to use SOOMLA's event system to handle events when they're dispatched. A common event to register a handler for is OnMarketPurchase in which you will get notified when users purchase things with Google Play, Apple App Store, or Amazon, depending on your platform:
StoreEvents.OnMarketPurchase += onMarketPurchase;
public void onMarketPurchase(PurchasableVirtualItem pvi, string payload,
Dictionary<string, string> extra) {
// pvi - the PurchasableVirtualItem that was just purchased
// payload - a text that you can give when you initiate the purchase operation and
// you want to receive back upon completion
// extra - contains platform specific information about the market purchase
// Android: The "extra" dictionary will contain "orderId" and "purchaseToken"
// iOS: The "extra" dictionary will contain "receipt" and "token"
// ... your game specific implementation here ...
}
Second, you can use the StoreInventory class to query a user's inventory and thus know his \ her balances and what they've purchased:
StoreInventory.GetItemBalance("currency_coins");
Third - the method SoomlaStore.RefreshInventory (which runs by default on Android builds, but not iOS) should all also restore the user's previous transactions for lifetime goods, in which you can also handle triggered events, so that's another way of telling if a user has previously purchased something ("Remove Ads" for example).
Related
Our app has an in-app purchase to upgrade to our "Pro" product, in Google-speak this is a one-time non-consumable product. We have launched this product and are actually generating revenue...so aside from our own testing we know that across the globe users are successfully seeing on our in-app sales page 1) the price of the product, and 2) an enabled "buy now" button (please note these two items both are derived from this BillingManager query:
launch {
val sku = billingManager.querySKU(BillingManager.SKUProPack1)
if (sku != null) {
iapPriceText.text = getString(R.string.settings_pro_upgrade_price, sku.price)
skuDetails = sku
upgradeButton.isEnabled = true
} else {
iapPriceText.text = ""
upgradeButton.isEnabled = false
}
}
I'll point out here: skuDetails collected in the query above is also the source of data needed in the actual Play Store purchase flow...so merely enabling the button would only delay the disappointment. The first indication a user sees that there is a problem is on the sales page: no price and a greyed-out button that is disabled.
We have three confirmed cases where the user has contacted us saying "I'd like to buy but the buy-it button doesn't do anything". We have found an ugly-ish workaround which is to have the user clear the cache of their Google Play Services app (and then reset the device). After they do that, they can get back to the sales page and all is well: the price is displayed and the button is enabled.
I'm thinking the step of clearing the cache is a clue to perhaps something that could/should be done within our app to better serve the customer (and, of course, we hope generates more revenue). It's early days for us in this Pro version but the three confirmed cases would be about 2% of the amount who have gotten through the purchase process...which doesn't sound like a lot, but who knows how many have fruitlessly pounded on the buy-it button and then just walked away.
On this, I'm going to file a ticket with Google and will keep all posted here if that yields anything. AND HERE IT IS:
As promised, here is Google's non-response. The link they provided was nice but it did not include the troubleshooting step that we found that works for us (clearing the Play Services cache). Here is Google's response: Hi,
Thanks for your patience.
I'm sorry to hear that some of your users are having a issue making a purchase within your app. Please note after looking into your issue, it looks like the issue is being caused by user end issues. As not all of your users are having the same issue, it would not be a issue within the coding. We are aware that cache and connectivity can cause issues within apps; and we have made a user help article to help guide users on how to fix these issues: https://support.google.com/googleplay/answer/1050566?hl=en
If you begin seeing more users with this issue, you can use log reporting to help determine the issue, you can use the Android logging system which provides a mechanism for collecting and viewing system debug output.
To extract the logs you will need to use "logcat" command as an adb command or directly in a shell prompt of your emulator or connected device:
adb logcat -v time > applog.txt
If you have any other questions about the Play Console, please let me know and I will be happy to assist you further.
Have a great day.
Regards,
Sabrina.
Google Play Developer Support.
Google provides a convenient API to implement "in-app purchase" features on an Android app.
Along with these docs, there is also a dedicated chapter regarding the security level of this system and the good ways to design it.
The web is full of articles about this step, from public key protection to remote server validation, but I really can't understand why all of these techniques should work when the main problem is, simply, code hacking.
Maybe there is a better term to explain it, but let me do a quick example. The basic idea of my application is that, at certain points, the user can't proceed unless he has purchased an item.
Something like:
public void accessTheVeryCoolFeature() {
boolean haveIt = checkIfPurchased("verycoolfeature");
if (haveIt) {
// YEAH! let's open this very cool feature I paid 200 bucks for
}
else {
// ok... where is my wallet?
boolean purchased = startPurchaseFlow("verycoolfeature");
if (purchased) {
// my wallet is now empty but happy
}
}
}
Following the previous guidelines, the developer can protect his app during the purchase process, letting the startPurchaseFlow method to query a remote, trusted, server that validates the receipt.
Purchases done using a "fake marketplace" should be avoided by this.
Another method is to protect the unlocked content by obfuscating the code. This is really simple with tools like ProGuard and should make the life of an "hacker" a bit harder.
Now, I tried to act the part of an hacker that want to read my code, especially the billing phase.
It took me like 1 minute to spot the code I wrote in the previous example. Now the best part: what if I edit the (obfuscated) source code to do this?
public void atvf() {
boolean hi = cip("verycoolfeature");
hi = true; // <------------------------ AHAH!
if (hi) {
// YEAH! let's open this very cool feature for free
}
// ...
}
All the good words about remote verification and code obfuscation are totally gone. So why spend hours on trying to implement them when the very first problem is in a boolean value?
Am I missing something?
Unless your app is heavily dependent on its functionality being in a server - as in each functionality stays on the server and the app is just a client tool to call those server APIs, there is nothing you can do. If indeed it's a server-based app - you can check each incoming request (e.g. the app can send a one time session hash) if a valid transaction exists for it and is paid. If not, deny the request.
The app's code is running on the client's phone. If the hacker gains access to that code and is free to modify it to override any billing validations - there is nothing you can do. You should make sure he doesn't gain access to that source code in the first place.
some might remember me from previous questions. I'm building an app for Android and it's going well. Most of the functions I wanted work great. I learned the basics by myself (and with the help of a few generous people here on StackOverflow!) but I still consider myself a beginner (today's question will show you how much of a beginner I am!).
My app is a dynamic map that shows the history of a country at a specific point in time. On Google Play, the user can download for free the base app (mostly empty), then he can buy packs (France, USA, UK, etc.) with in-app billing. That's where I am stuck.
I've bought the Milkman AndroidIAB ANE and read carefully the documentation (this one). I've managed to add the ANE to my library and update my application manifest. I've modified the example file to add my public key and the IDs for the purchasable packs. (I don't post the code here because I don't know if I'm allowed since the ANE is licensed.)
My app works that way:
First screen : Logo with a link to the website and a "Enter" button.
When clicked, the user arrive on a screen with a few buttons (one for each pack/country).
If the user click on a pack he owns, he is send to the chosen country's map.
If he doesn't own it, he is asked if he'd like to buy it and send to buy it.
Problems: (warning: some of those are worthy of noob of the month status, but I'm here to learn right?)
The code adapted from the Milkman's example is in an outside .as file used as DocumentClass.How do I link my screen 2 buttons with the functions from the .as file?
I tried this, but it doesn't work:
franceBtn.addEventListener(MouseEvent.CLICK, checkAndPurchase);
function checkAndPurchase (e:MouseEvent):void{
purchasePack1();
//function from the example, it checks if the pack is owned
// and send the user to the store if not
};
EDIT 1: It is probably really easy to do, but I'm just not experienced enough to understand what I need to do I guess.
I have a series of buttons in my app (say "franceBtn", "usaBtn" and "ukBtn"). Those buttons, when clicked, need to check if the pack ("francePack", "usaPack" and "ukPack") is owned by the user and if it is not, start the in-app purchase. I have tried to add an EventListener to the buttons, but nothing happens. Not on screen, not in the log.
2.Let's say the problem 1 is fixed. My app is meant to be used offline (except for buying additional packs). The way I understood what I read is that the "inventory" of in-app purchased packs is obtained via Google Play, which means (if I'm not mistaken) that the user needs to be online. Is there a way to create a file of some kind inside the device that stores this "inventory" so it can be accessed offline?
EDIT 2: I want the user to be able to use the app from everywhere, without the need to be online (except for purchasing packs obviously). But I guess that the app checks with Google Play in order to know which pack is already owned. So I'm looking for a way to store the "inventory" of owned packs directly inside the device/app (so it can be accessed offline and updated everytime internet starts.
I hope it is more clear, and thank you for pointing out it was not.
I've read the doc quite a few times, and I'm really stuck, so please, I'd really appreciate any help. ;)
Thanks in advance,
Jeryl
EDIT 3: Here is the portion of my code relating to IAB (made by following this tutorial :here but I didn't really understand it. I'm willing to learn but this is an intermediate level tuto, and I've found nothing on Internet that explains what to do for real beginners. If you have links I couldn't find, I'm all hears :D )
import com.milkmangames.nativeextensions.android.*;
import com.milkmangames.nativeextensions.android.events.*;
import flash.events.MouseEvent;
if (AndroidIAB.isSupported()) {
AndroidIAB.create();
}
// listeners for billing service startup
AndroidIAB.androidIAB.addEventListener(AndroidBillingEvent.SERVICE_READY, onReady);
AndroidIAB.androidIAB.addEventListener(AndroidBillingEvent.SERVICE_NOT_SUPPORTED, onUnsupported);
// start the service
AndroidIAB.androidIAB.startBillingService("my_key");
function onReady(e: AndroidBillingEvent): void {
trace("service now ready- you can now make purchases.");
}
function onUnsupported(e: AndroidBillingEvent): void {
trace("sorry, in app billing won't work on this phone!");
}
// listen for inventory events
AndroidIAB.androidIAB.addEventListener(AndroidBillingEvent.INVENTORY_LOADED, onInventoryLoaded);
AndroidIAB.androidIAB.addEventListener(AndroidBillingErrorEvent.LOAD_INVENTORY_FAILED, onInventoryFailed);
function onInventoryLoaded(e: AndroidBillingEvent): void {
for each(var purchase: AndroidPurchase in e.purchases) {
trace("You own the item:" + purchase.itemId);
}
}
function onInventoryFailed(e: AndroidBillingErrorEvent): void {
trace("Something went wrong loading inventory: " + e.text);
}
// load the player's current inventory
AndroidIAB.androidIAB.loadPlayerInventory();
// listen for purchase events
AndroidIAB.androidIAB.addEventListener(AndroidBillingEvent.PURCHASE_SUCCEEDED, onPurchaseSuccess);
AndroidIAB.androidIAB.addEventListener(AndroidBillingErrorEvent.PURCHASE_FAILED, onPurchaseFailed);
function onPurchaseSuccess(e: AndroidBillingEvent): void {
var purchase: AndroidPurchase = e.purchases[0];
trace("you purchased the item " + purchase.itemId);
AndroidIAB.androidIAB.loadPlayerInventory();
}
function onPurchaseFailed(e: AndroidBillingErrorEvent): void {
trace("Something went wrong with the purchase of " + e.itemId + ": " + e.text);
}
franceBtn.addEventListener(MouseEvent.CLICK, onPackOneButtonClicked);
function onPackOneButtonClicked(e: MouseEvent) {
if (purchase.itemId == "packone") {
franceMap.visible = true;
} else {
AndroidIAB.androidIAB.purchaseItem("packone");
}
}
Thanks for your patience! Jeryl
Before proceeding, I am assuming that you have properly set up the products in IAP section of Google play.
Now for your first question, I am not sure about the problem as clicking on a button and something to be done on that event should have worked out of the box. Your code should be something along the lines:
( Assuming you have two packs and their itemIds are com.yourcomapny.packOne and com.yourcompany.packTwo)
franceBtn.addEventListener( MouseEvent.CLICK, onPackOneButtonClicked );
function onPackOneButtonClicked( event:MouseEvent )
{
if( !isPackOnePurchased() )
{
purchasePackOne();
}
}
// Similarly for pack two
This is pretty much the same code , you are trying to write. Perhaps we can help you more with your problem, if you can post some portion of the code which is not working.
For your second part, if you want to store the "inventory" information offline, you may proceed in the following way:
1) Lets say you create an empty file.
2) Lets say if a person buys com.yourcompany.packTwo and you received purchase Successful event, you just add com.yourcompany.packTwo to the file and thus marking it as purchased.
3) Whenever you call isPackOnePurchased or isPackTwoPurchased, it checks whether corresponding itemId is present in the file and decide whether this package needs to be bought or it is already bought.
This will get you started. Another thing for enhanced user experience and security is that whenever user clicks on a button to buy the pack ( and he/she is connected to the internet ), always check whether that pack is in the inventory or not and show the feedback according to that. Always sync your local state with inventory, if there is any inconsistency. The inconsistency may arise if user deletes their application data and then tries to open the pack. This check will ensure that your application is synced with server state.
If you want next level of security you can either encrypt the information( item Ids in this case ) and store it a secure database.
Encryption can be done using numerous encryption methods. One such method is Rijndael encryption. You can use this tool this online tool for generating encrypted strings for your itemIds. This will give you a feel of what I am trying to say.
For how to store information in local database in an AIR based applications, see this link from Adobe
I am trying to implement in-app. In android, in-app is working perfectly.But, when I remove the app from device and reinstall it, then after if I try to purchase, it displays this message (product is Managed and in-app API Version 2)
and in log, I have an error RESULT_DEVELOPER_ERROR , then I googled and I found the Version 3 has more number of response codes like BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED
So if product is already purchased, the response must be like BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED and this is not possible in Version 2.
Then what should I do ? Do I need to implement Version 3 ?
if you want force to buy product by every time then product should be Unmanaged.
Products can be of 3 types
1) Managed
2) Unmanaged
3) Subcription
Managed means google it self keep record. so by using same email id user not have to paid its charge e.g. remove ads
Unmanaged means user have to buy every time and will be a charged. e.g. Pocker chips
This 2 example i have read on developer site..
and refer http://developer.android.com/google/play/billing/billing_admin.html
Have you looked into RESTORE_TRANSACTIONS?
I had pretty much the same problem you describe above. My code is, for the most part, taken from the Market Billing Sample Application.
I added a RESTORE button to my purchase activity which calls the BillingService.restoreTransactions routine.
If you search for RESTORE_TRANSACTIONS within this page: https://developer.android.com/google/play/billing/v2/api.html, you'll find that calling that routine re-triggers the PURCHASE_STATE_CHANGED broadcast.
Which, in my code, triggered the onPurchaseStateChange routine and it worked the same way it would on the initial purchase with the PurchaseState was set to PURCHASED.
I ended up with calling BillingService.restoreTransactions() after catching RESULT_DEVELOPER_ERROR. Looks odd, but works fine.
How do i check if a in app purchase has been done before?
So that my user doesnt need to repurchase the in app purchase upon uninstalling and reinstalling the app?
I have set my in app item to managable in the android market publish page.
i have read about RESTORE_TRANSACTION but I do not know what i need to look for inside this response and also how to test this.
Any help would be greatly appreaciated.
You need to restore the transactions, using the RESTORE_TRANSACTION flag you specified above. You should only do this once, when the application starts for the first time or if the user clears the data.
I would advice to make this process simpler for yourself, you looking into the AndroidBillingLibrary, which allows you to interface with the Android In App Billing in a much simpler manner.
Here is a snippet of how the transactions are restored:
private void restoreTransactions() {
if (!mBillingObserver.isTransactionsRestored()) {
BillingController.restoreTransactions(this);
Toast.makeText(this, R.string.restoring_transactions, Toast.LENGTH_LONG).show();
}
}