I have implement in app purchase (managed) in my application using billing services when ever user wants to buy item he will make purchase request that required internet even if user has already bought the item. so for user convenience i am doing that if user has bought the item then a value will be save in shared pref. and when ever user click on that particular item then 1st it will check shared pref. value that either user has bought item or not if not then go for purchase request else show him/her item.
My Question is that is it safe way? or i have to do something else?
This is not safe. I would discourage you from implementing such a check this way. You should rather go for standard approach and use getPurchases() method. You can call this method at any time (even offline) and if a user has purchases, they will be returned back from that method. Here is a sample code:
IInAppBillingService service; // initialize it first
Bundle response = service.getPurchases(3, "you app package", "inapp", null);
int responseCode = response.getInt(KEY_RESPONSE_CODE);
if (responseCode == RESPONSE_OK) {
ArrayList<String> purchases = response.getStringArrayList(KEY_INAPP_PURCHASE_ITEM_LIST);
...
}
Of curse you need to verify that purchases are signed with correct certificate and purchase state is not cancelled. But this is much safer than storing data in shared properties. Another advantage of this approach is that after user reinstalls the app, all purchases will be automatically available there too.
Complete example of this technique is within TrivialDrive sample app and it's work for me, but Im thinking about offline case. Google Play app caches purchases and gives responses to the getPurchases(3, "pkg", "inapp", null) offline but is this works infinitly or there is something like a timeout for this cache...
Related
Until june 20th 2016 i was able to cancel test purchases done in my app.
Doing multiple in-app purchases (not consumable) from the same test account made it easy to develop and test the code without too much hazzle.
After 20th june 2016, the purchases did not show in my merchant account and
i was unable to do more than 1 purchase from my test account. All i got was the: "you already own this item" message.
I logged a request to the google developer support group and the answer was:
Beginning June 20, 2016, we changed test purchases for one-time in-app purchases (IAPs).
Previously, test purchases for one-time IAPs generated order IDs. Starting June 20, 2016, one-time IAPs do not generate official order IDs (if at all) and will not appear in the Merchant Center. This behavior already applies to subscription IAPs.
You can learn more about testing in-app billing in the Android Developers Help Center: https://developer.android.com/google/play/billing/billing_testing.html#testing-purchases
allright.. so i go to the mentioned link and theres a section there:
Canceling completed test purchases
which states:
Google Play accumulates completed test purchases for each user but does not pass them on to financial processing.
In some cases, you might want to manually cancel a test purchase to continue testing. To do so, open the app page in the Play Store. If the test purchase that you want to cancel is a subscription, you can also use the cancel() method of the Purchases.subscriptions API.
Important: The refund() and revoke() methods of the Purchases.subscriptions API don't support test purchases.
So I go to the app page in play store...and do what exactly? the webpage does not state what i am supposed to do there. anyone know?
it does say: you can also use the cancel() method of the Purchases.subscriptions API.
which indicates that using the cancel() method is not the only method.
How to solve this without adding additional code in my app?
I went into the main Google Play Console page and clicked on Order Management. Under that I was able to select all test purchases and Refund them. I'm the primary developer of the app so I have access. If you are a tester you'd probably have to contact the support team and request that they refund your order.
All managed in-app products are consumable.
as stated in the docs.
That means that you can consume an owned item instead of cancelling the purchase and buy it all over again.
I suggest querying the inventory at the app launch time:
mIabHelper.queryInventoryAsync(this);
You can then consume the owned item in the callback:
#Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Purchase purchase = inventory.getPurchase(MY_SKU);
boolean isBought = (purchase != null && verifyDeveloperPayload(purchase));
if (isBought) {
mIabHelper.consumeAsync(purchase, new OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
//Clear the purchase info from persistent storage
}
});
}
}
This is OK for testing the IAB flow but make sure to remove this code from the release version.
On Play Console, go to Developer Account -> Account Details, set the license testers (you are one by default)
Purchase items
Go to Order Management, choose those test orders, select: refund, but DON'T FORGET to check REVOKE when you are refunding. (I forgot to revoke, and now can't find a way to take them back)
Anyway, another test account will do.
I found a solution which isn't very convenient, but works. It seems like you can consume uncomsumable products and that way you can buy them again. I'm working with phonegap so I only have example code for the cordova-plugin-purchase plugin:
store.when("your.product.id").updated(product => {
if(product.owned) {
var transaction = product.transaction;
product.transaction = null;
store.inappbilling.consumePurchase(
function() { // success
alert("consume success");
},
function(err, code) { // error
alert("consume error " + err)
},
product.id,
transaction.id
);
}
});
The updated callback gets called when you call store.refresh() or buy the product. So depending on your use case you'd want to implement an additional method of checking when to consume the product.
I have no experience with the native Android in-app payments, but obviously you will be able to consume the products there as well.
Edit: Sorry, I just read that you didn't want to include additional code in your project. I don't think that's possible at the moment, but would like to keep my answer here because it might help other people trying to test in-app payments.
What worked for me was a combination of both:
Go to order management and refund
clear cache/data in Play Store app (as well as your app in you placed some shared prefs).
Also, in case you get an item already owned status, you can consume the purchase using the purchase token and calling billingClient.consumeAsync().
Didn't find a solution for this.
My workaround is simply remove the current test user from the test users list, make a real purchase, then cancel it using the merchant console.
The queryPurchaseHistoryAsync method still finds test orders I've made over the last year, despite having long ago consumed, refunded, and revoked them. One precaution I've found helpful is clearing the data in the Google Play Store app (settings/apps/google play store/storage/clear data). queryPurchaseHistoryAsync pulls obsolete purchase data from here, although only the non-networking (and completely unreliable I've found) queryPurchases is supposed to do this. You may have to add additional code to your app after all, but it doesn't have to be much.
With the dropping of support for Trivial Drive 2 (the link in the docs takes you to a '404 page does not exist' error, the github files are archived, and updating to billing:2.1.0 will give you a vending import compile error in the IabHelper class), answers to this popular question involving IabHelper might be considered obsolete. Billing is a lot simpler now if you follow the basic code pieces in the docs starting here https://developer.android.com/google/play/billing/billing_overview with no messy helper classes. One persistent issue is a 'Both methods have same erasure, yet neither overides the other' method clash error you may run into with this implementation, see my solution here 'Both methods have same erasure, yet neither overides the other' method clash error in SkuDetailsResponseListener().
Once you have the newest billing code implemented, you can create a hidden trigger in your production app to call queryPurchaseHistoryAsync to get a purchaseHistoryRecordList. Then call consumeAsync for each item in this list. Here is the barebones code to consume all test orders, allowing multiple tests of your nonconsumables:
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP,
new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(BillingResult billingResult,
List<PurchaseHistoryRecord> purchaseHistoryRecordList){
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
&& purchaseHistoryRecordList != null) {
for (PurchaseHistoryRecord purchaserecord : purchaseHistoryRecordList) {
if(purchaserecord!=null){
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaserecord.getPurchaseToken())
.setDeveloperPayload(purchaserecord.getDeveloperPayload())
.build();
billingClient.consumeAsync(consumeParams, consumelistener);
}}}}});
For people using a way based on the new TrivialDriveKotlin, consumable products are consumed during the installation of the app in the method
handleConsumablePurchasesAsync
If your purchase is not consumable, you can make it consumable by adding the corresponding sku into CONSUMABLE_SKUS in the GameSku object. Exemple:
val CONSUMABLE_SKUS = listOf(GAS, PREMIUM_CAR)
Then uninstall your app from your device and install it again, and your non consumable purchase is available again. Fast and simply.
Of course, don't forget to remove your
I set up a beta account to test IAP for google app that I am working on, the issue I have is, once I have purchased One-time products(non-recurring charge) the test IAP, I cannot 'remove it' as such, so now, even when I delete the app and re-install, it remembers the purchase, that's great in the real world for a user, but not great when trying to fix the bugs!
Is there any way (short of making a ton of gmail accounts to test with) to remove the purchase from the account?
This is an old question but if someone is still looking for a solution then go to:
Google Play console and open the Order Management tab
There you can refund / cancel test purchases. Then clear the purchase state using this command:
adb shell pm clear com.android.vending
The only way I know is to force a consume in your app. You can then remove that code.
I am using cc.fovea.cordova.purchase plugin for cordova to manage my IAP purchases. To get my test Non-Consumables to be deleted I changed my registration from Non-consumable to Consumable.
store.register({
id: this.predatorID,
alias: 'Predator Pack',
type: store.CONSUMABLE //store.NON_CONSUMABLE
});
Also, apparently there are reserved keywords you could use instead (if you're into that). - https://developer.android.com/google/play/billing/billing_testing.html
I encountered the same situation and started to research. Unfortunately, the directions made here did not produce a solution.
I want to share the solution that worked for me.
If you call the method below in the right place, the solution will be produced. Source : Link
/**
* Recall that Google Play Billing only supports two SKU types:
* [in-app products][BillingClient.SkuType.INAPP] and
* [subscriptions][BillingClient.SkuType.SUBS]. In-app products are actual items that a
* user can buy, such as a house or food; subscriptions refer to services that a user must
* pay for regularly, such as auto-insurance. Subscriptions are not consumable.
*
* Play Billing provides methods for consuming in-app products because they understand that
* apps may sell items that users will keep forever (i.e. never consume) such as a house,
* and consumable items that users will need to keep buying such as food. Nevertheless, Google
* Play leaves the distinction for which in-app products are consumable entirely up to you.
*
* If an app wants its users to be able to keep buying an item, it must call
* [BillingClient.consumeAsync] each time they buy it. This is because Google Play won't let
* users buy items that they've previously bought but haven't consumed. In Trivial Drive, for
* example, consumeAsync is called each time the user buys gas; otherwise they would never be
* able to buy gas or drive again once the tank becomes empty.
*/
private fun clearIapHistory() {
billingClient!!.queryPurchases(BillingClient.SkuType.INAPP).purchasesList
.forEach {
val params =
ConsumeParams.newBuilder().setPurchaseToken(it.purchaseToken).build()
billingClient!!.consumeAsync(params) { responseCode, purchaseToken ->
when (responseCode.responseCode) {
BillingClient.BillingResponseCode.OK -> {
}
else -> {
Log.w(LOG_TAG, responseCode.debugMessage)
}
}
}
}
}
if (inventory.getPurchase(ITEM_SKU) != null ) {
try {
mIabHelper.consumeAsync(premiumPurchase, new IabHelper.OnConsumeFinishedListener() {
#Override
public void onConsumeFinished(Purchase purchase, IabResult result) {
Toast.makeText(MainActivity.this, "Consumed the test purchase successfully", Toast.LENGTH_SHORT).show();
}
});
} catch (IabHelper.IabAsyncInProgressException e) {
e.printStackTrace();
}
}
However refund() and revoke() methods don't support test purchases and you are left with only consumeAsync() option.
Just:
Purchase unlockedPurchase = inventory.getPurchase(SKU_UNLOCKED);
// Log unlockedPurchase.getOrderId();
Go to your Google Play panel, Order management, look for that order id and refund it (it should say Test order if it is your own order).
I guess the only method working is to...
Consume It!
For further info, get to the consuming document and search for "consume": https://developer.android.com/google/play/billing/integrate
Here are the important steps for you:
Dependence setup.
Billing client connection.
Query the Purchase.
Consume(Purchase).
Good Luck~
Test non-consumable products
To perform multiple test purchases for the same non-consumable product, you can refund and revoke purchases using Google Play Console.
I had a similar issue. Fortunately, the app I'm working with is WebView-based, so I can easily inject a link or button to trigger some Javascript to call back into the application to consume the test orders. Since test orders have an empty string for the orderId, it is easy to identify them to consume them. Once consumed, the item can be "purchased" again. Removing the button requires commenting out one line of code BUT if the button accidentally makes it into the final published app, it won't cause any problems since the code only consumes test orders - that is, real orders are not affected. That button will just be embarrassing instead of a disaster.
I'm working on a device without a credit card associated with it. I set up some promo codes and use the "Redeem Code" option for my test orders. Promo codes result in no risk of money exchanging hands and I'm able to completely verify IAB functionality in my app with real products without having to resort to the IAB test codes.
Nothing shows up for me in Google Wallet as per the post by Martin Kool.
You can refund the test purchase of non-consumable product and it will work just fine.
Using the Play Console website
Open Play Console.
On All Apps page left side, select Order Management
Use the "Search orders" box to search by order ID or the user's full email address.
Open order, click on Refund button. Select Entitlement checkbox and refund.
More details here about refunds: https://support.google.com/googleplay/android-developer/answer/2741495
Google Play Purchases are stored in Google Wallet.
https://wallet.google.com
When signed, go to "Transactions" on the left. Test purchases can be cancelled from there.
I am working on In-App Prurchase in Android. After Purchasing an item, i set boolean value to be true using Shared Preference Api in Android.It works fine. Once i un-install and re-install my application,it make boolean value to be false.This is my code:
boolean buyUfo = false;
private static final String BUYUFO ="buyUfo";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference);
sharedPreference = this.getSharedPreferences(SHARED_PREFERENCE_ID, MODE_PRIVATE);
buyUfo = sharedPreference.getBoolean(BUYUFO, false);
}
//After In-App Purchase, when finish listener is called.
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener1 = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d("ufo", "Purchase finished: " + result + ", purchase: " + purchase);
if (result.isFailure()) {
if (result.getResponse() == IabHelper.BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED) {
Editor editor = sharedPreference.edit();
editor.putBoolean(BUYUFO, true);
editor.commit();
return;
}
}
I want this value to be true always for those who bought the item?..Please help
You can store it on the sdCard making a file for the same. But that can be deleted externally also.
For your concern, regarding:
I want this value to be true always for those who bought the item?
with the In-App purchase, after purchasing an item, you don't really Need to store whether it has been purchased or not. The API would do it for you, through which you can fetch the status of an item for a particular id.
To retrieve the list of product's owned by the user, your application
sends a getPurchases call to Google Play. Your application can make a
consumption request by sending a consumePurchase call. In the request
argument, you must specify the in-app product's unique purchaseToken
String that you obtained from Google Play when it was purchased.
Google Play returns a status code indicating if the consumption was
recorded successfully.
Read more on:
Consuming In-app Products, In-app Billing V3
OR
RESTORE_TRANSACTIONS in V2
See Storage options android. I recommend saving sensitive data in your server(also google recommends it). In device you could save data in Shared Preference(not secure in rooted device is deleted on uninstall) or internal storage(deleted on uninstall) or external storage and sqlite databases. Whatever you choose if your data is sensitive then always encrypt it.
Note There is no reliable way to save data in device that persists between multiple installations. If you save it in external storage it is always prone to user deletion.
That's not possible. If you tried implementing it that way, users would abuse your system by manually editing SharedPreferences.
User can clear his phone memory any time. You shouldn't rely on that. Instead, save value in your back end, and if user reinstalls app, check if it's the same user.
you have to store this value other than the sharedprefrences as,sharedprefrences are app specific and they are deleted when you uninstall and re-install the app.
What you can do is store this value on server and when the user installs the app again then you can retrieve the value and manipulate it according to its value
Saving the purchase information on sharedPreference is not the correct way. You should query the server to check if the user has purchased the item or not.
I created a google app, I put it on the play store, and now I am testing inapp purchases with my test account. But then I cancel my purchase so to test how cancellations propagate. But cancellations are not reflected in the app. I can still use my test account with full access to the goods in question. I am using managed products.
It's a whole lot of code so it would be outrageous to post it all here. But I have reviewed my code over and over, and it's doing the right thing. In fact here is a snippet: when a user first launch the app, I check for outstanding purchase and change the state of the good in question as follows
Purchase premiumPurchase = inventory.getPurchase(SKU_PREMIUM);
boolean value = (premiumPurchase != null && verifyDeveloperPayload(premiumPurchase) && 0 == premiumPurchase.getPurchaseState());
setLockStateOfPremiumPurchase(this, value);
So I am now wondering if the cancellation has propagated to my app. In other words: how long does it take google to change their database so that when I check the inventory, it returns not purchased? Or perhaps I am doing this wrong: should I be checking for something else?
When I look at the original json purchase state is 0
After you cancelled the order in Google Wallet, you need to wait from 5 min to couple hours to get data synchronized back to your device. I'm not sure what it depends on. You just need to wait.
Sometimes it helps to restart the device after you received a confirmation e-mail. After restarting, the device registers itself in Google Play anew and purchases cache gets updated faster.
I am testing in-app-billing on Android. I am able to purchase the managed products and I get the expected response from the server. However when I try to re-buy the managed product from same device , I don't get any response from google play.
When re-buying I do get the popup message titled "Item already purchased", but then there's no response.But the amazing thing is i got response all time earlier.Even if i am trying to re buy same product from same device. Can anyone help me to resolve the issue?
Also is there any way to check if a product is already purchased or not?
Code :
#Override
public void onPurchaseStateChange(PurchaseState purchaseState,
String itemId, int quantity, long purchaseTime,
String developerPayload) {}
onPurchaseStateChange() function was not called when I tried to re-buy.
Only got a RESULT_ERROR response to onRequestPurchaseResponse() from google play.But i am I am able to purchase the product on first time and got PurchaseState.PURCHASED status.In the case of re-buying no state is returned. Is this due to a server error associated with Google Play?
You're not supposed to re-buy managed items. If you want to sell something more than once use an unmanaged item.
RESULT_ERROR in response to an attempt to re-buy something you already own seems reasonable. You aren't going to request a Purchase State Changed message if no purchase has occurred.
Are you sure you haven't changed the item you're using from unmanaged to managed? Or maybe you were using a test product and now you are using an actual product? (You can buy android.test.purchased over and over)
Your app should be keeping track of the purchases that have been made so that you can mark managed items as purchased to prevent the user trying to repurchase them (and so they know they have already purchased them).
The only time you need to check if a user already owns any of your managed items is if they reinstall your app. In that case you make a Restore Transactions call and update the apps local record of what has been purchased.