Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why onPurchasesUpdated isn't being called after launchBillingFlow

I'm in the process of implementing in app billing for Android and have got to the point where I can retrieve a list of products from the store. And can activate the Google purchase dialog via calling the launchBillingFlow() method. The documentation indicates that once this has been called, the onPurchasesUpdated is then called with the result. However this isn't happening for me. The logging confirms that the purchase is requested (from within my method: startPurchaseFlow()). My onPurchasesUpdated() is also called when the activity first runs and provides a OK result (0) to confirm connection set up.

But why isn't it being called after launchBillingFlow()?

Class that holds purchase mechanics:

public class BillingManager implements PurchasesUpdatedListener {
    private final BillingClient mBillingClient; // Billing client used to interface with Google Play
    private final Store mActivity; // Referenced in constructor

   // Structure to hold the details of SKUs returned from querying store
    private static final HashMap<String, List<String>> SKUS;
    static
    {
        SKUS = new HashMap<>();
        SKUS.put(BillingClient.SkuType.INAPP, Arrays.asList("com.identifier.unlock")); // Strings for in app permanent products
    }

    public List<String> getSkus(@BillingClient.SkuType String type) {
        return SKUS.get(type);
    }

    // Constructor
    public BillingManager(Store activity) {
        mActivity = activity;
        mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build(); // Initialise billing client and set listener
        mBillingClient.startConnection(new BillingClientStateListener() { // Start connection via billing client
            @Override
            public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponse) { // Actions to complete when connection is set up
                if (billingResponse == BillingClient.BillingResponse.OK) {
                    Log.i("dev", "onBillingSetupFinished() response: " + billingResponse);
                    mActivity.getProducts();
                } else {
                    Log.w("dev", "onBillingSetupFinished() error code: " + billingResponse);
                }
            }
            @Override
            public void onBillingServiceDisconnected() { // Called when the connection is disconnected
                Log.w("dev", "onBillingServiceDisconnected()");
            }
        });
    }

    // Receives callbacks on updates regarding future purchases
    @Override
    public void onPurchasesUpdated(@BillingClient.BillingResponse int responseCode,
                                   List<Purchase> purchases) {
        Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
        if (responseCode == 0 && !purchases.isEmpty()) {
            String purchaseToken;
            for (Purchase element : purchases) {
                purchaseToken = element.getPurchaseToken();
                mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
            }
        }
    }

    // Used to query store and get details of products args include products to query including type and list of SKUs and a listener for response
    public void querySkuDetailsAsync(@BillingClient.SkuType final String itemType,
                                     final List<String> skuList, final SkuDetailsResponseListener listener) {
        // Create a SkuDetailsParams instance containing args
        SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
                .setSkusList(skuList).setType(itemType).build();
        //Query the billing client using the SkuDetailsParams object as an arg
        mBillingClient.querySkuDetailsAsync(skuDetailsParams,
                new SkuDetailsResponseListener() {

            // Override the response to use the listener provided originally in args
            @Override
                    public void onSkuDetailsResponse(int responseCode,
                                                     List<SkuDetails> skuDetailsList) {
                        listener.onSkuDetailsResponse(responseCode, skuDetailsList);
                    }
                });
    }

    // Start purchase flow with retry option
    public void startPurchaseFlow(final String skuId, final String billingType) {
        Log.i("dev", "Starting purchaseflow...");

        // Specify a runnable to start when connection to Billing client is established
        Runnable executeOnConnectedService = new Runnable() {
            @Override
            public void run() {
                BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
                        .setType(billingType)
                        .setSku(skuId)
                        .build();
                mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
                Log.i("dev", "Just called launchBillingFlow..." + skuId);

            }
        };

        // If Billing client was disconnected, we retry 1 time
        // and if success, execute the query
        startServiceConnectionIfNeeded(executeOnConnectedService);
    }
    // Starts connection with reconnect try
    private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
        if (mBillingClient.isReady()) {
            if (executeOnSuccess != null) {
                executeOnSuccess.run();
            }
        } else {
            mBillingClient.startConnection(new BillingClientStateListener() {
                @Override
                public void onBillingSetupFinished(@BillingClient.BillingResponse int billingResponse) {
                    if (billingResponse == BillingClient.BillingResponse.OK) {
                        Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
                        if (executeOnSuccess != null) {
                            executeOnSuccess.run();
                        }
                    } else {
                        Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
                    }
                }
                @Override
                public void onBillingServiceDisconnected() {
                    Log.w(TAG, "onBillingServiceDisconnected()");
                }
            });
        }
    }

} // End of class

Class that implements interface and initiates request for purchases and displays product information:

public class Store extends AppCompatActivity {
    SharedPreferences prefs; // used to access and update the pro value
    BillingManager billingManager; // Used to process purchases

    // Following are used to store local details about unlock product from the play store
    String productSku = "Loading"; // Holds SKU details
    String productBillingType = "Loading";
    String productTitle = "Loading"; // Will be used to display product title in the store activity
    String productPrice = "Loading"; // Used to display product price
    String productDescription = "Loading"; // Used to display the product description

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

        // Set up toolbar
        Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
        setSupportActionBar(myToolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
        getSupportActionBar().setDisplayShowHomeEnabled(true);

        // Create billing manager instance
        billingManager = new BillingManager(this);
        // Set up the shared preferences variable
        prefs = this.getSharedPreferences(
                "com.identifier", Context.MODE_PRIVATE); // Initiate the preferences

        // set up buttons
        final Button btnBuy = findViewById(R.id.btnBuy);
        btnBuy.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                billingManager.startPurchaseFlow(/*productSku*/ "android.test.purchased", productBillingType); // Amended for TEST
            }
        });
        final Button btnPro = findViewById(R.id.btnPro);
        btnPro.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                getProducts();
            }
        });
        getProducts();
        updateDisplay();
    } // End of onCreate

    // Used to unlock the app
    public void unlock() {
        Log.d("dev", "in unlock(),  about to set to true");

        prefs.edit().putBoolean("pro", true).apply();
        MainActivity.pro = true;

    }

    // Go back if back/home pressed
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {

            // Respond to the action bar's Up/Home button
            case android.R.id.home:
             NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
// Used to request details of products from the store from this class
    public void getProducts() {
        List<String> inAppSkus = billingManager.getSkus(BillingClient.SkuType.INAPP); // Create local list of Skus for query
        billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, inAppSkus, new SkuDetailsResponseListener() {
            @Override
            public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
                if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
                    for (SkuDetails details : skuDetailsList) {
                        productSku = details.getSku();
                        productTitle = details.getTitle();
                        productDescription = details.getDescription();
                        productPrice = details.getPrice();
                        productBillingType = details.getType();
                    }
                    updateDisplay();
                }
            }
        });
    }
// Helper method to update the display with strings
    private void updateDisplay() {
        final TextView titleText = findViewById(R.id.txtTitle);
        final TextView descriptionText = findViewById(R.id.txtDescription);
        final TextView priceText = findViewById(R.id.txtPrice);
        titleText.setText(productTitle);
        descriptionText.setText(productDescription);
        priceText.setText(productPrice);
    }
}
like image 253
laurie Avatar asked Oct 17 '22 23:10

laurie


1 Answers

Ok, so this (replacing the onPurchasesUpdated method above) is now working/responding as expected. Why, I don't know, but it is.

 @Override
 public void onPurchasesUpdated(@BillingClient.BillingResponse int responseCode,
                         List<Purchase> purchases) {
     if (responseCode == BillingClient.BillingResponse.OK
             && purchases != null) {
         for (Purchase purchase : purchases) {
             Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
             Log.i("dev", "successful purchase...");
             String purchasedSku = purchase.getSku();
             Log.i("dev", "Purchased SKU: " + purchasedSku);
             String purchaseToken = purchase.getPurchaseToken();
             mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
             mActivity.unlock();
         }
     } else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
         // Handle an error caused by a user cancelling the purchase flow.
         Log.d(TAG, "onPurchasesUpdated() response: User cancelled" + responseCode);
     } else {
         // Handle any other error codes.
     }
 }
like image 177
laurie Avatar answered Nov 15 '22 05:11

laurie