I have been working on integrating Google Play Billing for the past two days. Yesterday, I successfully launched the billing flow; however, to my surprise, I encountered an issue where the testing card section was not visible. Instead, I was presented with actual payment options. In a rush, I proceeded to make a purchase, hoping that this might be a new testing method and the money would not be deducted. Unfortunately, the payment went through.
Note :- I had uploaded the app bundle to Internal Testing, and the app was already published in production long before I added Google Play Billing.
However, I made a mistake by not implementing shared preferences to handle the successful purchase, which was intended to remove ads from the app. Therefore, the ads persisted in the app, and I couldn't observe the changes resulting from the successful purchase.
After realizing the oversight, I added the necessary shared preferences.
But upon testing with another ID, I still encountered the issue of real payment options appearing instead of the testing environment. So i reducing the price of the product and made another purchase to validate the shared preference implementation, But i am still unable to remove the ads.
It seems that the shared preference was never being called in the handlePurchase() and restorePurchase() methods.
This is the code i have implemented , please take the code kindly , im new to this and dont want to get rage on few of my intial questions
HelpActivity.java
`public class HelpActivity extends AppCompatActivity {
private BillingClient billingClient;
private final PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (purchases.size() > 0) {
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
} else {
Toast.makeText(HelpActivity.this, "Unable to handle purchase", Toast.LENGTH_SHORT).show();
}
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Toast.makeText(HelpActivity.this, "Purchase Canceled", Toast.LENGTH_SHORT).show();
//Note!!!! only this toast message is being shown in this entre process
} else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED ) {
if (purchases.size() > 0){
for (Purchase purchase : purchases) {
handlePurchase(purchase);
}
Toast.makeText(HelpActivity.this, "You already own the item", Toast.LENGTH_SHORT).show();
}else {
Toast.makeText(HelpActivity.this, "Unable to handle purchase(Item Owned)", Toast.LENGTH_SHORT).show();
}
}
}
};
private MaterialButton removeAdsBtn;
private MaterialButton restorePurchaseBtn;
private ProductDetails productDetails;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_help_avtivity);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); // Set vertical orientation
Window window = this.getWindow();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.setStatusBarColor(this.getResources().getColor(R.color.black));
}
restorePurchaseBtn = findViewById(R.id.restoreAdsBtn);
removeAdsBtn = findViewById(R.id.removeAdsBtn);
// Initialize BillingClient
setupBillingClient();
restorePurchaseBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
restorePurchases();
}
});
removeAdsBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Call the method to initiate the billing flow
initiateBillingFlow();
}
});
}
private void handlePurchase(Purchase purchase) {
if (!purchase.isAcknowledged()) {
billingClient.acknowledgePurchase(AcknowledgePurchaseParams
.newBuilder()
.setPurchaseToken(purchase.getPurchaseToken())
.build(), billingResult -> {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
// Grant the user access to remove ads
// Save the purchase details to SharedPreferences to remember the purchase
Toast.makeText(HelpActivity.this, "Thank you for the purchase", Toast.LENGTH_SHORT).show();
Toast.makeText(HelpActivity.this, "Restarting the app, Please wait ...", Toast.LENGTH_SHORT).show();
SharedPreferences preferences = getSharedPreferences("adremoveSP", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("ads_removed", true);
editor.apply();
Intent intent = new Intent(HelpActivity.this, MainActivity.class);
startActivity(intent);
}
});
}
}
public void setupBillingClient() {
billingClient = BillingClient.newBuilder(this).setListener(purchasesUpdatedListener).enablePendingPurchases().build();
establishConnection();
}
void establishConnection() {
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The BillingClient is ready. You can query purchases here.
QueryPurchase();
}
}
@Override
public void onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
establishConnection();
}
});
}
public void QueryPurchase() {
QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(ImmutableList.of(QueryProductDetailsParams.Product.newBuilder().setProductId("removeads_rickroll").setProductType(BillingClient.ProductType.INAPP).build())).build();
billingClient.queryProductDetailsAsync(queryProductDetailsParams, new ProductDetailsResponseListener() {
public void onProductDetailsResponse(BillingResult billingResult, List<ProductDetails> productDetailsList) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// Check if the productDetailsList is not empty
if (productDetailsList != null && !productDetailsList.isEmpty()) {
productDetails = productDetailsList.get(0);
} else {
}
} else {
}
}
});
}
public void initiateBillingFlow() {
if (productDetails != null) {
ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
ImmutableList.of(
BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build()
);
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(productDetailsParamsList)
.build();
BillingResult billingResult = billingClient.launchBillingFlow(HelpActivity.this, billingFlowParams);
}
}
public void restorePurchases() {
billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener((billingResult, list) -> {
}).build();
final BillingClient finalBillingClient = billingClient;
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingServiceDisconnected() {
establishConnection();
}
@Override
public void onBillingSetupFinished(@NonNull BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
finalBillingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(), (billingResult1, list) -> {
if (billingResult1.getResponseCode() == BillingClient.BillingResponseCode.OK) {
if (list.size() > 0) {
Toast.makeText(HelpActivity.this, "Successfully restored", Toast.LENGTH_SHORT).show();
SharedPreferences preferences = getSharedPreferences("adremoveSP", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("ads_removed", true);
editor.apply();
Intent intent = new Intent(HelpActivity.this, MainActivity.class);
startActivity(intent);
} else {
Toast.makeText(HelpActivity.this, "Oops, No purchase found.", Toast.LENGTH_SHORT).show();
SharedPreferences preferences = getSharedPreferences("adremoveSP", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("ads_removed", false);
editor.apply();
}
}
});
}
}
});
}
protected void onResume() {
super.onResume();
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder().setProductType(BillingClient.ProductType.INAPP).build(),
(billingResult, list) -> {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
for (Purchase purchase : list) {
if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED && !purchase.isAcknowledged()) {
handlePurchase(purchase);
}
}
}
}
);
}
}`
I understand that I'm including the entire file code, but it's necessary as it comprises 99% of the Google Play Billing implementation.
Another point I'd like to highlight is that when I click the 'Okay' button in the billing flow where the purchase has already been made, the app crashes after clicking the 'Okay' button twice. I'm unsure how to identify the cause of the problem since it doesn't run from the studio itself.