Someone Somewhere
Someone Somewhere

Reputation: 23787

Android Billing; how to resolve "The item you were attempting to purchase could not be found"

With all the recent changes to the Google Billing library and the Developer Console it's hard to apply answers from 2014, so I am posting on this topic again with the hope of finding a modern answer.

I am using Google Billing library 2.2.0

The Issue: enter image description here


I've set up the licenses: subscriptions


I've also set up License testing: license testing


I've also published a release build to the internal test track (signed with release cert): release build


Relevant Code Sample:

private final String SKU_TEST_PROD_1YR = "test_prod_id_1_year";
private final String SKU_TEST_PROD_6MN = "test_prod_id_6_month";
private final String SKU_TEST_PROD_1MN = "test_prod_id_1_month";
private BillingClient billingClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    List<String> iapProdIdList = Arrays.asList(SKU_TEST_PROD_1YR, SKU_TEST_PROD_6MN, SKU_TEST_PROD_1MN);
    setupBillingClient(this, iapProdIdList);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    billingClient.endConnection();
}

private void setupBillingClient(Context context, List<String> iapProdList) {
    billingClient = BillingClient.newBuilder(context).setListener(this).build();
    billingClient.startConnection(new BillingClientStateListener() {
        @Override
        public void onBillingSetupFinished(BillingResult billingResult) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                loadAllSubscriptionProductIdDetails(iapProdList);
            }
        }

        @Override
        public void onBillingServiceDisconnected() { }
    });
}

private void loadAllSubscriptionProductIdDetails(List<String> iapProdList) {
    if (billingClient.isReady()) {
        SkuDetailsParams params = SkuDetailsParams.newBuilder().setSkusList(iapProdList).setType(BillingClient.SkuType.SUBS).build();

        billingClient.querySkuDetailsAsync(params, (billingResult, prodDetailList) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && !prodDetailList.isEmpty()) {

                for (SkuDetails prodDetail : prodDetailList) {

                    String productSku = prodDetail.getSku();

                    switch(productSku) {
                        case SKU_TEST_PROD_1YR : {
                            final BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(prodDetail).build();
                            buttonIap1Yr.setOnClickListener(v -> {
                                // trigger purchase - Google needs the parent activity to overlay with their UI
                                billingClient.launchBillingFlow(this, billingFlowParams);
                            });
                            break;
                        }
                        case SKU_TEST_PROD_6MN : {
                            final BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(prodDetail).build();
                            buttonIap6mth.setOnClickListener(v -> {
                                // trigger purchase - Google needs the parent activity to overlay with their UI
                                billingClient.launchBillingFlow(this, billingFlowParams);
                            });
                            break;
                        }
                        case SKU_TEST_PROD_1MN : {
                            final BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setSkuDetails(prodDetail).build();
                            buttonIap1mth.setOnClickListener(v -> {
                                // trigger purchase - Google needs the parent activity to overlay with their UI
                                billingClient.launchBillingFlow(this, billingFlowParams);
                            });
                            break;
                        }
                        default :
                            Toast.makeText(this, "Did not find Product in-app: "+ productSku, Toast.LENGTH_SHORT).show();
                    }
                }//end of FOR
            }//end of IF
        });
    } else {
        Toast.makeText(this, "billingClient is NOT ready", Toast.LENGTH_SHORT).show();
    }
}

@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> list) {

    if (billingResult != null && list != null) {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            for (Purchase purchase : list) {
                if(!purchase.isAcknowledged()) acknowledgePurchase(purchase.getPurchaseToken());
            }
        }
        else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
            Toast.makeText(this, "User Canceled", Toast.LENGTH_SHORT).show();
        }
        else if(billingResult.getResponseCode()== BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            Toast.makeText(this, "Already Purchased", Toast.LENGTH_SHORT).show();
        }
    }
}

private void acknowledgePurchase(String purchaseToken) {
    AcknowledgePurchaseParams params = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build();
    billingClient.acknowledgePurchase(params, billingResult -> {
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
            Toast.makeText(this, "Purchase Acknowledged", Toast.LENGTH_SHORT).show();
        }
    });
}

When I click any of my [Buy Subscription] buttons the billingClient.launchBillingFlow(this, billingFlowParams); is correctly triggered and the SKU is correct.

As far as I am aware, all of the Dev Console is set up correctly. I want to demo IAP to my manager, however, "The item you were attempting to purchase could not be found" is cramping my style ! What am I doing wrong ?

Upvotes: 4

Views: 2544

Answers (2)

Someone Somewhere
Someone Somewhere

Reputation: 23787

The code I posted in my question is fully functional. The problem was 100% due to the developer console configuration :

To fix it:

1) promote the app to alpha as a minimum (Internal test track doesn't work!)

2) click [Manage] on the Alpha track and email the Opt-in link to your testers

3) Your testers will say "when I click this link, the Play Store says the app isn't found". Tell them to relax for 30 minutes - Google Console needs 30 minutes to process this complexity.

Have them download the early release version and suddenly IAP works!

Upvotes: 2

from56
from56

Reputation: 4127

I think your loadAllSubscriptionProductIdDetails() method has a wrong design.

You only have to call querySkuDetailsAsync() when the user clicks, with a single item in setSkusList() argument, just the one to be purchased, and then :

  @Override
  public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
       if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList.size() > 0) {
                    BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                            .setSkuDetails(skuDetailsList.get(0))
                            .build();

                    billingClient.launchBillingFlow(activity, flowParams);

            }

      }

Upvotes: 0

Related Questions