com.nokia.example.pepperfarm.iap.Payment.java Source code

Java tutorial

Introduction

Here is the source code for com.nokia.example.pepperfarm.iap.Payment.java

Source

/**
 * Copyright (c) 2014 Microsoft Mobile and/or its subsidiary(-ies).
 * See the license text file delivered with this project for more information.
 */

package com.nokia.example.pepperfarm.iap;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.*;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import com.nokia.example.pepperfarm.client.MainScreenPepperListFragment;
import com.nokia.example.pepperfarm.client.ProductListFragment;
import com.nokia.example.pepperfarm.client.util.ProductDetails;
import com.nokia.example.pepperfarm.client.util.Purchase;
import com.nokia.example.pepperfarm.product.Content;
import com.nokia.example.pepperfarm.product.Content.Product;
import com.nokia.payment.iap.aidl.INokiaIAPService;
import org.json.JSONException;

import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;

public class Payment implements ServiceConnection {

    public static final int API_VERSION = 3;
    public Activity activity;
    public INokiaIAPService npay;
    public boolean npayAvailable = false;
    public static boolean purchases_asked = false;

    public static final String SHARED_PREFERENCE_KEY = "IAPDATA";
    public static final String PRODUCT_ID_KEY = "PRODUCTKEY";
    public static final String KEY_NOT_IN_PROGRESS = "NOTINPROGRESS";
    public static final String PREFLOAD_FAILED = "FAIL";

    public static final int RESULT_OK = 0;// - success
    public static final int RESULT_USER_CANCELED = 1;// - user pressed back or canceled a dialog
    public static final int RESULT_BILLING_UNAVAILABLE = 3;// - this billing API version is not supported for the type requested or billing is otherwise impossible (for example there is no SIM card inserted)
    public static final int RESULT_ITEM_UNAVAILABLE = 4;// - requested ProductID is not available for purchase
    public static final int RESULT_DEVELOPER_ERROR = 5;// - invalid arguments provided to the API
    public static final int RESULT_ERROR = 6;// - Fatal error during the API action
    public static final int RESULT_ITEM_ALREADY_OWNED = 7;// - Failure to purchase since item is already owned
    public static final int RESULT_ITEM_NOT_OWNED = 8;// - Failure to consume since item is not owned
    public static final int RESULT_NO_SIM_CARD = 9;

    public static final int RESULT_ERR = -100; //Used as a default value for Bundle.getInt, not part of the API   

    public static String PURCHASE_IN_PROGRESS_FOR_PRODUCT = "";

    private static String ITEM_TYPE_INAPP = "inapp";

    //This is the expected SHA1 finger-print in HEX format
    private static final String EXPECTED_SHA1_FINGERPRINT = "C476A7D71C4CB92641A699C1F1CAC93CA81E0396";

    private static final String ENABLER_PACKAGENAME = "com.nokia.payment.iapenabler";

    /**
     * Constructor of the Payment object. Needs to be initialized to access NIAP methods.
     *
     * @param context - The activity where Payment object is initialized
     */
    public Payment(Context context) {
    }

    /**
     * Binds to Nokia in-app payment service.
     *
     * @param ctx
     * @throws GeneralSecurityException If Nokia In-App payment enabler fingerprint is not valid
     */
    public void connectToService(Context ctx) throws GeneralSecurityException {

        activity = (Activity) ctx;

        //Verifies enabler fingerprint
        if (!verifyFingreprint()) {

            npayAvailable = false;
            errorAlert("Nokia In-App Payment Enabler is not available.");

            throw new GeneralSecurityException("Enabler fingerprint incorrect. Billing unavailable");

        } else {
            //Enabler fingerprint OK. Continue with binding. 
            Intent paymentEnabler = new Intent("com.nokia.payment.iapenabler.InAppBillingService.BIND");
            paymentEnabler.setPackage(ENABLER_PACKAGENAME);
            activity.bindService(paymentEnabler, this, Context.BIND_AUTO_CREATE);
        }
    }

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {

        npay = INokiaIAPService.Stub.asInterface(service);

        Log.i("onServiceConnected", "IAP service connected");
        npayAvailable = true;

        if (isBillingAvailable()) {
            getPurchases(false);
            fetchPrices();
        }

    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }

    /**
     * Checks SHA1 fingerprint of the enabler
     *
     * @return true if signature matches, false if package is not found or signature does not match.
     */
    private boolean verifyFingreprint() {

        try {
            PackageInfo info = activity.getBaseContext().getPackageManager().getPackageInfo(ENABLER_PACKAGENAME,
                    PackageManager.GET_SIGNATURES);

            if (info.signatures.length == 1) {

                byte[] cert = info.signatures[0].toByteArray();
                MessageDigest digest;
                digest = MessageDigest.getInstance("SHA1");
                byte[] ENABLER_FINGERPRINT = digest.digest(cert);
                byte[] EXPECTED_FINGERPRINT = hexStringToByteArray(EXPECTED_SHA1_FINGERPRINT);

                if (Arrays.equals(ENABLER_FINGERPRINT, EXPECTED_FINGERPRINT)) {
                    Log.i("isBillingAvailable", "NIAP signature verified");
                    return true;
                }
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }
        return false;
    }

    private static byte[] hexStringToByteArray(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    public boolean isBillingAvailable() {

        boolean isAvailable = false;

        try {

            int responseCode = RESULT_ERR;

            if (npayAvailable)
                responseCode = npay.isBillingSupported(API_VERSION, activity.getPackageName(), ITEM_TYPE_INAPP);

            switch (responseCode) {
            case RESULT_OK:
                isAvailable = true;
                break;
            case RESULT_NO_SIM_CARD:
                errorAlert("No SIM. Please Insert SIM card.");
                break;
            case RESULT_ERR:
                errorAlert("Nokia In-App Payment Enabler is not available.");
                break;
            default:
                errorAlert("Billing is not supported. " + responseCode);
                break;
            }
        } catch (RemoteException e) {
            Log.e("isBillingAvailable", e.getMessage(), e);
            errorAlert("Billing is not supported. " + e.getMessage());
        }
        return isAvailable;
    }

    /**
     * Fetches the prices asynchronously
     */
    public void fetchPrices() {

        if (!isBillingAvailable())
            return;

        for (Product p : Content.ITEMS) {
            if (p.getPrice() != "") {
                Log.i("fetchPrices", "Prices already available. Not fetching.");
                return;
            }
        }

        AsyncTask<Void, String, Void> pricesTask = new AsyncTask<Void, String, Void>() {

            @Override
            protected Void doInBackground(Void... params) {

                ArrayList<String> productIdArray = new ArrayList<String>(Content.ITEM_MAP.keySet());
                Bundle productBundle = new Bundle();
                productBundle.putStringArrayList("ITEM_ID_LIST", productIdArray);

                try {
                    Bundle priceInfo = npay.getProductDetails(API_VERSION, activity.getPackageName(),
                            ITEM_TYPE_INAPP, productBundle);

                    if (priceInfo.getInt("RESPONSE_CODE", RESULT_ERR) == RESULT_OK) {
                        ArrayList<String> productDetailsList = priceInfo.getStringArrayList("DETAILS_LIST");

                        for (String productDetails : productDetailsList) {
                            parseProductDetails(productDetails);
                        }

                    } else {
                        Log.e("fetchPrices", "PRICE - priceInfo was not ok: Result was: "
                                + priceInfo.getInt("RESPONSE_CODE", -100));
                    }

                } catch (JSONException e) {
                    Log.e("fetchPrices", "PRODUCT DETAILS PARSING EXCEPTION: " + e.getMessage(), e);
                } catch (RemoteException e) {
                    Log.e("fetchPrices", "PRICE EXCEPTION: " + e.getMessage(), e);
                }
                return null;
            }

            private void parseProductDetails(String productDetails) throws JSONException {

                ProductDetails details = new ProductDetails(productDetails);

                Log.i("fetchPrices", productDetails);

                Product p = Content.ITEM_MAP.get(details.getProductId());

                if (p != null) {
                    p.setPrice(details.getPriceFormatted());
                    Log.i("fetchPrices", "PRICE RECEIVED - " + details.getPrice() + " " + details.getCurrency());
                } else {
                    Log.i("fetchPrices",
                            "Unable to set price for product " + details.getProductId() + ". Product not found.");
                }
            }

            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                if (ProductListFragment.purchaseListAdapter != null) {
                    ProductListFragment.purchaseListAdapter.notifyDataSetChanged();
                }
                if (MainScreenPepperListFragment.reference.adapter != null) {
                    MainScreenPepperListFragment.reference.adapter.notifyDataSetChanged();
                }
            }

        };
        pricesTask.execute();
    }

    /**
     * Check the products that we have already purchased
     */
    public void getPurchases(boolean ignoreLifeCycleCheck) {

        if (!isBillingAvailable())
            return;

        if (ignoreLifeCycleCheck == false) {
            if (purchases_asked) {
                Log.i("getPurchases", "Restorables already asked.");
                return;
            }
        }

        MainScreenPepperListFragment.getpeppers.setVisibility(View.INVISIBLE);
        MainScreenPepperListFragment.fetch_peppers_progressbar.setVisibility(View.VISIBLE);
        MainScreenPepperListFragment.fetching_peppers.setVisibility(View.VISIBLE);

        AsyncTask<Void, String, Void> restoreTask = new AsyncTask<Void, String, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                ArrayList<String> productIdArray = new ArrayList<String>(Content.ITEM_MAP.keySet());
                Bundle productBundle = new Bundle();
                productBundle.putStringArrayList("ITEM_ID_LIST", productIdArray);

                try {
                    Bundle purchases = npay.getPurchases(API_VERSION, activity.getPackageName(), ITEM_TYPE_INAPP,
                            productBundle, null);
                    Log.i("getPurchases",
                            "GET PURCHASES RESPONSE CODE: " + purchases.getInt("RESPONSE_CODE", RESULT_ERR));

                    if (purchases.getInt("RESPONSE_CODE", RESULT_ERR) == RESULT_OK) {

                        ArrayList<String> purchaseDataList = purchases
                                .getStringArrayList("INAPP_PURCHASE_DATA_LIST");

                        for (String purchaseData : purchaseDataList) {
                            parsePurchaseData(purchaseData);
                        }
                        purchases_asked = true;
                    } else {
                        Log.e("getPurchases", "GET PURCHASES - response was not ok: Result was: "
                                + purchases.getInt("RESPONSE_CODE", RESULT_ERR));
                    }

                } catch (JSONException e) {
                    Log.e("getPurchases", "PURCHASE DATA PARSING EXCEPTION: " + e.getMessage(), e);
                } catch (RemoteException e) {
                    Log.e("getPurchases", "EXCEPTION: " + e.getMessage(), e);
                }
                return null;
            }

            private void parsePurchaseData(String purchaseData) throws JSONException {

                Purchase purchase = new Purchase(purchaseData);

                Product p = Content.ITEM_MAP.get(purchase.getProductId());

                if (p != null) {
                    p.setPurchased(purchase);
                    Log.i("getPurchases", "Restoring product " + purchase.getProductId() + " Purchase token: "
                            + purchase.getToken());
                } else {
                    Log.i("getPurchases",
                            "Unable to restore product " + purchase.getProductId() + ". Product not found.");
                }

            }

            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                MainScreenPepperListFragment.getpeppers.setVisibility(View.VISIBLE);
                MainScreenPepperListFragment.fetch_peppers_progressbar.setVisibility(View.GONE);
                MainScreenPepperListFragment.fetching_peppers.setVisibility(View.GONE);

                if (ProductListFragment.purchaseListAdapter != null) {
                    ProductListFragment.purchaseListAdapter.notifyDataSetChanged();
                }
                if (MainScreenPepperListFragment.reference.adapter != null) {
                    MainScreenPepperListFragment.reference.adapter.notifyDataSetChanged();
                }
            }

        };
        restoreTask.execute();
    }

    /**
     * Starts the payment process for the given product id. Application is suspended and the Npay service will complete the purchase.
     *
     * @param caller     - The response from the purchase is sent to the caller activity. Caller activity must override "protected void onActivityResult(int requestCode, int resultCode, Intent data)" method to get the response.
     * @param product_id - The product id that is being purchased.
     * @throws Exception
     */
    public void startPayment(Activity caller, String product_id) throws Exception {

        if (!isBillingAvailable())
            return;

        Bundle intentBundle = npay.getBuyIntent(API_VERSION, activity.getPackageName(), product_id, ITEM_TYPE_INAPP,
                "");
        PendingIntent purchaseIntent = intentBundle.getParcelable("BUY_INTENT");

        //Set purchase in progress
        setPurchaseInProgress(product_id);

        caller.startIntentSenderForResult(purchaseIntent.getIntentSender(), Integer.valueOf(0), new Intent(),
                Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0));

    }

    public boolean consumeProduct(String productId, String token) {

        if (!isBillingAvailable())
            return false;

        boolean consumed = false;

        try {
            Log.i("consumeProduct", "Consuming product: " + productId + " PurchaseToken: " + token);
            int response = npay.consumePurchase(API_VERSION, activity.getPackageName(), productId, token);

            if (response == RESULT_OK)
                consumed = true;

        } catch (RemoteException e) {
            Log.e("isBillingAvailable", e.getMessage(), e);
        }
        return consumed;
    }

    public void setPurchaseInProgress(String productId) {
        if (productId.equals(KEY_NOT_IN_PROGRESS))
            Log.i("setPurchaseInProgress", "Resetting purcahse progress");
        else
            Log.i("setPurchaseInProgress", "Starting purchase for product " + productId);

        SharedPreferences prefs = activity.getSharedPreferences(SHARED_PREFERENCE_KEY, Context.MODE_PRIVATE);
        prefs.edit().putString(PRODUCT_ID_KEY, productId).commit();
    }

    public String getPurchaseInProgress() {
        SharedPreferences prefs = activity.getSharedPreferences(SHARED_PREFERENCE_KEY, Context.MODE_PRIVATE);
        String progress = prefs.getString(PRODUCT_ID_KEY, PREFLOAD_FAILED);
        Log.i("getPurchaseInProgress", "Purchase in progress is: " + progress);
        return progress;
    }

    /**
     * Shows an alert dialog when billing is not available.
     */
    public void errorAlert(String msg) {
        Log.i("errorAlert", "Displaying error alert");
        new AlertDialog.Builder(activity).setTitle("PepperFarm").setMessage(msg).setNeutralButton("Close", null)
                .show();
    }

    public void cleanUp() {
        try {
            activity.unbindService(this);
            npayAvailable = false;
            Log.i("Payment", "Service disconnected");
        } catch (Exception ignored) {
            Log.i("cleanUp", "Service was cleared up already previously");
            //Unable to clean up. This means it is not registered or another error. Ignored exception
        }
    }

}