I am developing an application which will allow user to purchase using In App Purchase and I want to remove ads after purchase. I can purchase succesfully with code below
BillingFlowParams flowParams = BillingFlowParams.newBuilder()
.setSku("android.test.purchased")
.setType(BillingClient.SkuType.INAPP)
.build();
mBillingClient.launchBillingFlow(getActivity(), flowParams);
But I cannot see the result from queryPurchaseHistoryAsync when I open app again and call this method below.
mBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
purchasesList.size();
}
});
purchasesList.size() == 0
Is "queryPurchaseHistoryAsync" method cannot show test purchase or Am I doing something wrong?
Edit: Is queryPurchaseHistoryAsync method check purchase after delete and install app again.
Yes queryPurchaseHistoryAsync method check purchase after deleting and installing the app again against particular user
mBillingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(#NonNull BillingResult billingResult, #Nullable List<PurchaseHistoryRecord> list) {
}
});
Try this it will give all purchase items.
mBillingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(responseCode, skuDetailsList);
}
});
mBillingClient.queryPurchases() is all you need. Call it at every app start and, for example, every time your main activity resumes. This way your (reinstalled) app will eventually detect all user's purchases.
Related
I was wondering could you help. I followed the instructions at https://developer.android.com/google/play/billing/integrate, but I cannot seem to get the purchase flow working. The billing seems to setup ok, but when I try to query for my in-app products, the list is always returning empty. Can someone please help?
In my app level build.gradle file, I have included the Google Billing SDK:
implementation 'com.android.billingclient:billing:3.0.0'
Then I have created an activity to test out the code. It first initialises the BillingClient and starts the connection. The connection seems to finish the setup correctly. Once setup correctly, I then try to query the products that I have available in my Google Play Console under 'Store presence' > 'In-app products' > 'Manage products'
The following is then the code in the Activity that should kick off the process and return the SkuDetails list, but unfortunately it is returning back empty.
private BillingClient billingClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_billing);
this.billingClient = BillingClient.newBuilder(this)
.enablePendingPurchases()
.setListener(this.purchaseUpdateListener)
.build();
this.billingClient.startConnection(billingClientStateListener);
}
private PurchasesUpdatedListener purchaseUpdateListener = new PurchasesUpdatedListener() {
#Override
public void onPurchasesUpdated(#NonNull BillingResult billingResult, #Nullable List<Purchase> list) {
Log.d("Billing", "onPurchasesUpdated - List Size: " + list.size());
}
};
private BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Log.d("Billing", "onBillingSetupFinished - OK");
queryProducts();
} else {
Log.d("Billing", "onBillingSetupFinished - Something wrong response Code: " + billingResult.getResponseCode());
}
}
#Override
public void onBillingServiceDisconnected() {
Log.d("Billing", "Service disconnected");
}
};
private void queryProducts() {
List<String> productIdsList = new ArrayList<>();
productIdsList.add("test.billing.001");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(productIdsList).setType(BillingClient.SkuType.INAPP);
this.billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(#NonNull BillingResult billingResult, #Nullable List< SkuDetails > list) {
Log.d("Billing", "onSkuDetailsResponse - List Size: " + list.size());
}
});
}
So for anyone who is having similar issues, it seems that (well in my case anyways) that my app needed to be successfully published before I could retrieve the in-app products from the app. Once my app was published, I was then able to query and use the in-app products.
According to Maxim Alov comment for Billing 5.0.0, the problem is in invitations for testers.
My steps to fix this problem:
Open tester's invitation link and accept
Open app via link from previous step
After two these steps (in my case second helped) all products started to come
I'm using billing-lib-5.0.0 and also had the same issue -
queryProductDetails() was always empty on my release builds, let alone
debug builds. I'd actually added all my test gmail emails to list of
testers for Closed Testing, and also LICENSED all of them. No effect.
Eventually, I recognized that the link to my test app is not generated
on Play Console Closed Testing track page. I recreated the track, this
time for Internal Testing, and the link has appeared. Then I logged in
to each of my gmails and accepted to be a tester from that link. After
doing that, products started to come, in IDE
I have kept a donate tab and want to let the users buy the items over and over again. I have implemented a code but it lets the user buy the specific item only once. I have used managed products in play console for products.
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(billingClient.isReady()){
SkuDetailsParams params=SkuDetailsParams.newBuilder()
.setSkusList(Arrays.asList("purchase_aaa","purchase_bbb","purchase_ccc","purchase_ddd"))
.setType(BillingClient.SkuType.INAPP).build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if(responseCode==BillingClient.BillingResponse.OK)
{
loadProductToRecyclerView(skuDetailsList);
}
else{
Toast.makeText(Donate.this, "Cannot query product", Toast.LENGTH_SHORT).show();
}
}
});
}
else
{
Toast.makeText(Donate.this, "Not ready", Toast.LENGTH_SHORT).show();
}
}
});
#Override
public void onPurchasesUpdated(int responseCode, #Nullable List<Purchase> purchases) {
if(purchases!=null){
Toast.makeText(this, "Purchased"+purchases.size(), Toast.LENGTH_SHORT).show();
}
}
That's by design and cannot be changed, in-app managed products can only be purchased once.
If you want the user who has paid more to have more features enabled, you will have to create as many in-app managed products as levels exist.
If it is a game in which, for example, the user is consuming items then when he no longer has any, you consume the in-app product so he can buy it again.
Or you can also consume the product immediately after the purchase and keep track of how many he has purchased through your own means, an own server or perhaps through firebase, but this already means that you will have to implement a user authentication system for your app.
Consume a purchase:
ConsumeResponseListener consumeListener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
}
};
String token = purchase.getPurchaseToken();
ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(token).build();
billingClient.consumeAsync(consumeParams, consumeListener);
I have gone through the Play Billing Library
https://developer.android.com/google/play/billing/billing_library_overview
You must acknowledge all purchases within three days. Failure to properly acknowledge purchases results in those purchases being refunded.
The process is doesn't provide any clarity how to acknowledge purchases.
This is what i tried
Is this the correct way to do it.
Thanks in Advance
#Override
public void onPurchasesUpdated(BillingResult billingResult, #Nullable List<Purchase> purchases) {
if(billingResult.getResponseCode()== BillingClient.BillingResponseCode.OK&&purchases!=null){
Toast.makeText(this, "Purchase Successful", Toast.LENGTH_SHORT).show();
for(Purchase purchase:purchases){
handlePurchase(purchase);
}
}else if(billingResult.getResponseCode()== BillingClient.BillingResponseCode.USER_CANCELED){
Toast.makeText(this, "Purchase Cancelled", Toast.LENGTH_SHORT).show();
}else if(billingResult.getResponseCode()== BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED){
Toast.makeText(this, "Already Purchased", Toast.LENGTH_SHORT).show();
} else{
Toast.makeText(this, billingResult.getDebugMessage(), Toast.LENGTH_SHORT).show();
}
//in handlePurchase()
if(!purchase.isAcknowledged())
{
AcknowledgePurchaseParams acknowledgePurchaseParams
= AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.setDeveloperPayload(purchase.getDeveloperPayload())
.build();
client.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if(billingResult.getResponseCode()== BillingClient.BillingResponseCode.OK){
Toast.makeText(RemoveAdsActivity.this, "Purchase Acknowledged", Toast.LENGTH_SHORT).show();
}
}
});
}
It mentions acknowledging purchases near half way through that link. There are different ways to acknowledge the purchase depending on the type.
private BillingClient mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build();
//For non-consumables:
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener());
//For Consumables:
client.consumeAsync(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
The link I posted includes a sample on how to handle subscriptions.
UPDATE
Here's how to acknowledge both non-consumable and consumable purchases, staring with non-consumable:
First, create the AcknowledgePurchaseParams Class object. For this you need the purchase token which you should be able to get easily as you should be calling this in your onPurchasesUpdated method or another method that you passed purchase to after onPurchasesUpdated:
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
Next create your listener that will be used as the second parameter. This will allow you to do something after the purchase is acknowledged. I am displaying a snackbar message in this example (As per worbel's comment you can, and probably should, check the result of this billingResult):
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
#Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
getMessage("Purchase acknowledged");
}
};
With these created, use your BillingClient to call the acknowledgePurchase method:
mBillingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
The purchase should be successfully acknowledged.
This uses acknowledgePurchase for non-consumable items.
Consumable purchases
This is similar only what they are called is changed - See the explanation for what they are in the above example:
First parameter - Params - set-up:
ConsumeParams consumeParams = ConsumeParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
Second parameter - Listener - set-up:
ConsumeResponseListener consumeResponseListener = new ConsumeResponseListener() {
#Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
getMessage("Purchase acknowledged");
}
}
Now use your BillingClint and consumeAsync:
mBillingClient.consumeAsync(consumeParams, consumeResponseListener);
If you are new and using billing library 4.0.0 then above codes will not work since now all billing process has been put in background thread so make sure you do not call any ui updating code during billing process.
Use:
purchase.getSkus.contains("sku here");
instead of
purchase.getSku.equals("sku here");
QueryInventoryFinishedListener of IabHelper has not returned the expired subscription items.
On the other hand, PurchaseHistoryResponseListener of Google Play Billing Library seems to receive all purchased items, which is including expired items.
On Google Play Billing Library, we have to check the purchased date of PurchaseHistoryResponseListener and each expiration date of items?
queryPurchases vs queryPurchaseHistoryAsync
Generally, we should use queryPurchases(String skuType), which does not returns expired items. queryPurchaseHistoryAsync returns enabled and disabled items, as you see the documentation like following.
queryPurchases
Get purchases details for all the items bought within your app. This method uses a cache of Google Play Store app without initiating a network request.
queryPurchaseHistoryAsync
Returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed.
About queryPurchaseHistoryAsync
I could not image the use case for queryPurchaseHistoryAsync. If we need to use queryPurchaseHistoryAsync, we need the implementation to check if it is expired or not.
private PurchaseHistoryResponseListener listener = new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(int responseCode, List<Purchase> purchasesList) {
for (Purchase purchase : purchasesList) {
if (purchase.getSku().equals("sku_id")) {
long purchaseTime = purchase.getPurchaseTime();
// boolean expired = purchaseTime + period < now
}
}
}
};
Purchase object does not have the information of period, so the above period must be acquired from BillingClient.querySkuDetailsAsync or be hard-coded. The following is sample implementation to use querySkuDetailsAsync.
List<String> skuList = new ArrayList<>();
skuList.add("sku_id");
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(BillingClient.SkuType.SUBS);
billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if (skuDetailsList == null) {
return;
}
for (SkuDetails skuDetail : skuDetailsList) {
if (skuDetail.getSku().equals("sku_id")) {
String period = skuDetail.getSubscriptionPeriod();
}
}
}
});
I have an Android app that launches the purchase flow from a dialog within a Fragment.
The purchase flow is launched like:
config.getIabHelper().launchPurchaseFlow(
faActivity,
sku,
Constantes.SOLICITUD_COMPRA,
faActivity.mPurchaseFinishedListener,
purchaseIdentifier);
Where:
config.getIabHelper() returns an instance of the IabHelper class as implemented in the google documentation (the same used in the trivialgame example)
faActivity is the parent activity of the fragment
Constantes.SOLICITUD_COMPRA is a positive integer
mPurchaseFinishedListener is the listener, which is implemented in the parent activity.
So the current flow should work like:
Make a purchase.
Acknowledge that the purchase was bought.
Update the UI so that it enables the user to use the purchased item rather than to buy it.
However it works like:
Make a purchase.
The application freezes and stops.
When the application is relaunched, the inventory is queried and the UI gets updated.
It seems like mPurchasedListener is never called.
To make it more weird, everything seems to work fine with test responses. I am testing the app in alpha with real responses and that is where the trouble appears.
Any ideas?
i've had this issue before my self and there are multiple things that could be causing this issue.
Firstly, please make sure that you've done the following for aplha stage testing and that you've added all the required permissions and aidl files.
1) Are you using a version of the app you've put on your device through android studio? If so, this could be your issue. Usually you'll receive a message notifying you that the version of the app you're using isn't compatible with in-app purchases. Alpha stage apps are actually downloaded from the google play store. You should have set up a google group that has access to a specific link that will allow you to open the google play store and download your alpha stage app.
2) Are you connected to the internet? Obviously launching a purchase flow with a real SKU will require an internet connection to the google play servers.
3) Did you set up this in app product up as a managed product? For simplicity, i highly recommend doing so.
if you've correctly set up everything as I mentioned above and are still having a problem, then likely it's an issue with how you setup and attempt to use your purchase flow.
Here's the steps i took to launching a purchase flow from within a fragment
Instead of setting up the IAB Helper in the fragment, set it up in the faActivity Class. We will then call the purchase flow method within the faActivity class from within the fragment in which you're viewing via Dialog Box.
This is how i set up in app purchases :
faActivity.java
IabHelper mHelper;
In the oncreate method :
String base64EncodedPublicKey = "your in app purchase key";
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.startSetup(new
IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result)
{}});
Add these methods as well :
#Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data)
{
if (!mHelper.handleActivityResult(requestCode,
resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
}
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
= new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result,
Purchase purchase)
{
if (result.isFailure()) {
return;
}
else if (purchase.getSku().equals(ITEM_SKU)) {
consumeItem();
}
}
};
public void consumeItem() {
mHelper.queryInventoryAsync(mReceivedInventoryListener);
}
IabHelper.QueryInventoryFinishedListener mReceivedInventoryListener
= new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result,
Inventory inventory) {
if (result.isFailure()) {
// Handle failure
} else {
mHelper.consumeAsync(inventory.getPurchase(ITEM_SKU),
mConsumeFinishedListener);
}
}
};
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener =
new IabHelper.OnConsumeFinishedListener() {
public void onConsumeFinished(Purchase purchase,
IabResult result) {
/*The purchase was successfully consumed, now update the
//ui/award the user with their purchase (I suggest storing the fact that
they're premium or whatever within the shared prefrences of your app)*/
}};
String ITEM_SKU = "";
//Launching the purchase Flow
public void makeThePurchase() {
//Assign the SKU Name of your managed product
ITEM_SKU="premiumMembership";
mHelper.launchPurchaseFlow(this, ITEM_SKU, 10000,
mPurchaseFinishedListener, "mypurchasetoken");
}
Now in your fragment, use this code to show a dialogBox asking the user if they wish to purchase the product and launch the purchase flow if they wish to do so
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
builder.setView(inflater.inflate(R.layout.dialog_signin, null))
.setPositiveButton("Purchase", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
((faActivity)getActivity()).makeThePurchase();
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
builder.setTitle("Purchase");
builder.setMessage("Purchase premium membership?");
builder.create().show();
And that is how i managed to launch a purchase flow from within a fragment. I'm sure there is a better way to do so, but when setting up the purchase flow from within the fragment, i always recieved issues as well when trying to launch a purchase.
Hopefully this solved your issue!
Good Luck!!! :)
NOTE: The code i've listed above is setup to consume the purchase (meaning that the fact that they own it won't show up when querying the inventory) Like i said above although, if you're not concerned about the users having to repurchase their items/premium purchases in the occurance of them getting a new device or uninstalling your app, then just keep track of what they've purchased and consumed within the shared preferences by calling this code below from within the onConsumePurchasedFinished listener :
SharedPreferences pref = getActivity().getApplicationContext().getSharedPreferences("MyPref", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean("hasBoughtPremium",true);
editor.apply();
Then when reloading the app, check to see if they own the premium item :
SharedPreferences pref = getActivity().getApplicationContext().getSharedPreferences("MyPref", Context.MODE_PRIVATE);
Boolean isPremium = pref.getBoolean("hasBoughtPremium",false);