Note: I'm talking about a one-time purchase that removes ads from the app.
Google says you should call BillingClient.queryPurchases() in your onResume() and onCreate() but I'm not sure how to do that.
First off here's the code for the actual in-app purchase, I just got it from a youtube tutorial and I don't even know if it's complete:
private void setupBillingClient(Context c) { //connect to google play
billingClient = BillingClient.newBuilder(c)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
//The BillingClient is setup successfully
loadAllSkus();
}
}
@Override
public void onBillingServiceDisconnected() {
//TODO: implement retry logic to handle lost connections to Google Play by calling startConnection() again
}
});
}
private void loadAllSkus() {
if (billingClient.isReady()) { //first check if BillingClient is ready
final SkuDetailsParams params = SkuDetailsParams.newBuilder()
.setSkusList(skuList)
.setType(BillingClient.SkuType.INAPP)
.build();
billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(@NonNull final BillingResult billingResult, @Nullable List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
assert skuDetailsList != null;
for (Object skuDetailsObject : skuDetailsList) {
final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
if (skuDetails.getSku().equals(sku)) { //if it found the sku in play store you can start billing flow
Purchase.PurchasesResult result = billingClient.queryPurchases(BillingClient.SkuType.INAPP); //use play store cache, no network
List<Purchase> purchases = result.getPurchasesList();
boolean isOwned = false;
if (purchases != null) //first check if he has already purchased
for (Purchase purchase : purchases) {
String thisSKU = purchase.getSku();
if (thisSKU.equals(sku)) {
isOwned = true;
Toast.makeText(getContext(), "You are a premium user", Toast.LENGTH_LONG).show();//TODO: Delete this toast
//TODO: Remove purchase options and ads here
break;
}
}
if (!isOwned) {
buyButton.setEnabled(true);
buyButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
BillingFlowParams billingFlowParams = BillingFlowParams
.newBuilder()
.setSkuDetails(skuDetails)
.build();
if (getActivity() != null)
billingClient.launchBillingFlow(getActivity(), billingFlowParams);
dismiss();
}
});
}
}
/* else if (skuDetails.getSku().equals("something else")) { //add other products
}*/
}
}
}
});
} else Toast.makeText(getContext(), R.string.billing_not_ready, Toast.LENGTH_LONG).show();
}
@Override
public void onPurchasesUpdated(@NonNull BillingResult billingResult, @Nullable List<com.android.billingclient.api.Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (Purchase purchase : purchases) {
handlePurchase(purchase); //acknowledge the purchase
}
} else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
//already owned
} else if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
}
}
private void handlePurchase(Purchase purchase) {
if (purchase.getSku().equals(sku) && purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
if (!purchase.isAcknowledged()) {
AcknowledgePurchaseParams acknowledgePurchaseParams =
AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build();
billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
Toast.makeText(getContext(), "Purchase Acknowledged", Toast.LENGTH_LONG).show();//TODO: Delete this maybe?
}
}
});
}
Toast.makeText(getContext(), R.string.purchase_done, Toast.LENGTH_LONG).show();
}
}
So apparently to call BillingClient.queryPurchases() I have to rewrite all of the above code in each and every activity?
The method BillingClient.queryPurchases() as you know returns a list of all the currently owned purchases items.
Replicating the same code again and again is always an incorrect approach / design. What you need to do is to encapsulate your code and make it as independent as possible from any Activity or any other component, so it can be reused.
For a better approach check the official billing samples from Google. You will see that all the billing logic is encapsulated so it can be easily reused.
Java: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiJava
Kotlin: https://github.com/android/play-billing-samples/tree/main/ClassyTaxiAppKotlin
Some clarifications, if you find that the samples are about subscriptions and you need non-consumables, or vice versa. The billing library is the same for both, the difference is only the data to be queried, for example for subscriptions use BillingClient.SkuType.SUBS and for non-consumables BillingClient.SkuType.INAPP. But the rest, how to connect with billing, how to acknowledge, initiate the purchase flow, etc., is the exactly same for both. There are no separate APIs or different way to implement it, you use the same API and the same implementation just changing what is being queried.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With