Luis Saavedra
Luis Saavedra

Reputation: 128

Android in app billing - How to know which users have bought a product

I have an android app with a donation system, these donations are integrated products that are automatically consumed to let the users donate more than once.

I need to know if there is some way to find out which users have donated at least once.

I appreciate any help.

EDIT:

In addition to Dima Kozhevin's answer... I used this code in onServiceConnected() event inside startSetup() method from my IabHelper.

Bundle purchaseHistoryBundle = mService.getPurchaseHistory(6,BuildConfig.APPLICATION_ID, "inapp", null, new Bundle());
ArrayList<String> mListItems = purchaseHistoryBundle.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
if (mListItems != null){
    if (mListItems.size()>0){
        //User have donated at least once
    }else{
        //User never donated
    }
}

Upvotes: 3

Views: 1476

Answers (2)

Sam
Sam

Reputation: 5392

I assume you have integrated the AIDL file and the in app billing code files for IabHelper etc.. following Android instructions for basic billing handling.

Once you are already handling billing you simply query the inventory to see if they have purchased it or not. I tend to do this in a singleton class called PurchaseManager.

I will share that class with you below. However, I only have one package, so I hard coded that package in my check for pro purchased, to be more dynamic you may want to do those checks in the calling class or in a loop.

/**
 * Created by App Studio 35 on 9/28/17.
 */
public class PurchaseManager {

    /*///////////////////////////////////////////////////////////////
    // MEMBERS
    *////////////////////////////////////////////////////////////////
    private static PurchaseManager mInstance;
    private static final String TAG = Globals.SEARCH_STRING + PurchaseManager.class.getSimpleName();
    private static String PUBLIC_LICENSING_KEY = "<YOUR PUBLIC KEY HERE>";
    private static final String PRO_PACKAGE_SKU = "pro_package_level_1";
    public static final int RESULT_KEY_PURCHASE = 9876;
    private IabHelper mHelper;
    private Boolean mIABServiceIsAvailable = false;
    private static String mAndroidId;


    /*///////////////////////////////////////////////////////////////
    // CONSTRUCTOR
    *////////////////////////////////////////////////////////////////
    private PurchaseManager(){}
    public static synchronized PurchaseManager getInstance(){
        if(mInstance == null){
            mInstance = new PurchaseManager();

        }

        return mInstance;
    }


    /*///////////////////////////////////////////////////////////////
    // EXTERNAL METHODS
    *////////////////////////////////////////////////////////////////
    public boolean getIsIABServiceAvailable(){
        return mIABServiceIsAvailable;
    }
    public void checkForPurchasesOrTrials(final Context context, final IPurchaseSyncListener listener) {
        mHelper = new IabHelper(context, PUBLIC_LICENSING_KEY);

        if(!BuildConfig.DEBUG) {
            mHelper.enableDebugLogging(true, TAG);

        }

        //Setup Purchase Processor
        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
            @Override
            public void onIabSetupFinished(IabResult result) {
                mIABServiceIsAvailable = result.isSuccess();
                if (!result.isSuccess()) {
                    String error = "Problem setting up In-app Billing: " + result;
                    A35Log.d(TAG, error);
                    notifyUserOfError(listener, error);
                    return;

                }

                ArrayList<String> skus = new ArrayList<String>();
                skus.add(PRO_PACKAGE_SKU);
                checkExistingPurchasesForSkus(context, listener, skus);

            }

        });

    }
    public void attemptPurchaseOfPro(Activity activity, final IPurchaseConsumeListener listener){
        mHelper.launchPurchaseFlow(activity, PRO_PACKAGE_SKU, RESULT_KEY_PURCHASE, new IabHelper.OnIabPurchaseFinishedListener() {
            @Override
            public void onIabPurchaseFinished(IabResult result, Purchase info) {
                if (result.isSuccess()) {
                    mHelper.consumeAsync(info, new IabHelper.OnConsumeFinishedListener() {
                        @Override
                        public void onConsumeFinished(Purchase purchase, IabResult result) {
                            if (result.isSuccess()) {
                                Log.d(TAG, "Successfully synced purchases" + result);
                                notifyUserOfPurchaseSuccess(listener, purchase, result, PRO_PACKAGE_SKU);

                            } else {
                                String error = "Could not sync purchases. Error: " + result;
                                Log.d(TAG, error);
                                notifyUserOfPurchaseError(listener, error, result, PRO_PACKAGE_SKU);

                            }
                        }

                    });
                }else{
                    notifyUserOfPurchaseError(listener, "Purchase Incomplete", result, PRO_PACKAGE_SKU);

                }
            }
        });



    }


    /*///////////////////////////////////////////////////////////////
    // INTERNAL METHODS
    *////////////////////////////////////////////////////////////////
    private void checkExistingPurchasesForSkus(final Context context, final IPurchaseSyncListener listener, final ArrayList<String> skus) {
        mHelper.queryInventoryAsync(true, skus, new IabHelper.QueryInventoryFinishedListener() {
            @Override
            public void onQueryInventoryFinished(IabResult result, Inventory inv) {
                if (!result.isSuccess()) {
                    String error = "Unable to query inventory. Error: " + result;
                    A35Log.d(TAG, error);
                    notifyUserOfError(listener, error);
                    return;

                }

                ArrayList<Purchase> purchaseList = new ArrayList<Purchase>();
                if (inv.getPurchase(PRO_PACKAGE_SKU) != null) {
                    purchaseList.add(inv.getPurchase(PRO_PACKAGE_SKU));

                }

                if (!purchaseList.isEmpty()) {
                    A35Log.d(TAG, "Attempting to sync purchases" + result);
                    attemptToSyncPurchases(context, listener, purchaseList);

                } else {
                    A35Log.d(TAG, "We didn't see any purchases, attempting to check for Trials");
                    if(mAndroidId == null) {
                        getAdvertiserIDThenCheckTrialsForDevice(context, listener, skus);

                    }else{
                        checkTrialsForDeviceID(context, listener, skus);

                    }

                }

            }

        });

    }
    private void attemptToSyncPurchases(final Context context, final IPurchaseSyncListener listener, final ArrayList<Purchase> purchaseList) {
        for(Purchase purchase : purchaseList) {
            mHelper.consumeAsync(purchase, new IabHelper.OnConsumeFinishedListener() {
                @Override
                public void onConsumeFinished(Purchase purchase, IabResult result) {
                    if (result.isSuccess()) {
                        Log.d(TAG, "Successfully synced purchases" + result);
                        notifyUserOfPurchasedPackages(listener, purchaseList);

                    } else {
                        String error = "Could not sync purchases. Error: " + result;
                        Log.d(TAG, error);
                        notifyUserOfError(listener, error);

                    }
                }

            });

        }

    }
    private void getAdvertiserIDThenCheckTrialsForDevice(final Context context, final IPurchaseSyncListener listener, final ArrayList<String> skus){
        //If no purchases then check for trial times for skus get Advertiser ID for identifying device
        new GetAdvertiserIDAsyncTask(context){
            @Override
            protected void onPostExecute(String advertisementID) {
                mAndroidId = (advertisementID == null ? "unknownID" : advertisementID);
                checkTrialsForDeviceID(context, listener, skus);

            }

        }.execute();

    }
    private void checkTrialsForDeviceID(final Context context, final IPurchaseSyncListener listener, final ArrayList<String> skus){
        //Use device ID to check for Trials
        new GetTrialTimeAsyncTask(context, mAndroidId){
            @Override
            protected void onPostExecute(ActiveTrialsListResponseModel activeTrialsListResponseModel) {
                super.onPostExecute(activeTrialsListResponseModel);
                A35Log.v(TAG, "onPostExecute");

                if(activeTrialsListResponseModel.getErrorMessage() != null) {
                    String error = "Error getting trial time: " + activeTrialsListResponseModel.getErrorMessage();
                    A35Log.e(TAG, error);
                    notifyUserOfError(listener, error);
                    return;
                }

                notifyUserOfTrialCheckCompleteForPackages(listener, activeTrialsListResponseModel);

            }
        }.execute();
    }


    /*///////////////////////////////////////////////////////////////
    // NOTIFY USER CALLBACKS
    *////////////////////////////////////////////////////////////////
    private void notifyUserOfError(IPurchaseSyncListener listener, String message){
        if(listener != null){
            listener.onPurchaseManagerError(message);
        }
    }
    private void notifyUserOfPurchasedPackages(IPurchaseSyncListener listener, ArrayList<Purchase> purchasedSkus){
        if(listener != null){
            listener.onPackagePurchased(purchasedSkus);
        }
    }
    private void notifyUserOfTrialCheckCompleteForPackages(IPurchaseSyncListener listener, ActiveTrialsListResponseModel activeTrialsListResponseModel){
        if(listener != null){
            listener.onTrialRetrievalComplete(activeTrialsListResponseModel);
        }
    }
    private void notifyUserOfPurchaseSuccess(IPurchaseConsumeListener listener, Purchase purchase, IabResult result, String sku){
        if(listener != null){
            listener.onPurchaseSuccessful(purchase, result, sku);
        }
    }
    private void notifyUserOfPurchaseError(IPurchaseConsumeListener listener, String message, IabResult result, String sku){
        if(listener != null){
            listener.onPurchaseFailure(message, result, sku);
        }
    }

    /*///////////////////////////////////////////////////////////////
    // INTERFACE
    *////////////////////////////////////////////////////////////////
    public interface IPurchaseSyncListener {
        void onPackagePurchased(ArrayList<Purchase> sku);
        void onTrialRetrievalComplete(ActiveTrialsListResponseModel activeTrialsListResponseModel);
        void onPurchaseManagerError(String message);
    }
    public interface IPurchaseConsumeListener {
        void onPurchaseSuccessful(Purchase purchase, IabResult result, String sku);
        void onPurchaseFailure(String message, IabResult result, String sku);
    }

}

Three things to note about my shared code as well.

  1. I am using trials for my pro package so that is my async task to confirm that they are not in trials for any package, you won't do that piece.
  2. I do not have authenticated users, I rely on the device advertiser id for knowing if they have a trial or not, this won't matter to you. Also advertiser ids can be reset by the user in Google Settings if they are crafty enough they can figure out how to get another free trial, but I'm not that concerned about the power user going that far to save a dollar haha.
  3. I did my startup inside the checkfor purchases method because it is ONLY called one time on app startup and it is the first call. A more generic way may be to do it in the first getInstance if helper is null.

Goodluck.

Upvotes: 1

Dima Kozhevin
Dima Kozhevin

Reputation: 3732

You should use getPurchaseHistory() method. Signature of the method:

Bundle getPurchaseHistory(int apiVersion,
                          String packageName,
                          String type,
                          String continuationToken,
                          Bundle extraParams);

Your code will look like this:

Bundle purchaseHistoryBundle = service.getPurchaseHistory(6, BuildConfig.APPLICATION_ID, "subs", null, new Bundle());

In addition, guy from Google suggests in that example use queryPurchaseHistoryAsyncmethod:

This library also allows to get purchase history even though it's not demonstrated inside the sample. Please use this method to get all purchases history (up to 1 record per SKU).

Upvotes: 1

Related Questions