Contoh Penggunaan Billing Class di Android

Untuk keperluan pembelian In-App Purchase (microtransaction), saat tulisan ini ditulis, Play Store mewajibkan setidaknya menggunakan billing library v3 ke atas.

Sedangkan dalam posting ini, penulis hanya mencoba memberikan contoh penggunaan Billing Library v4, hasil uji coba penulis dalam menambahkan fitur pembelian dalam aplikasi-aplikasi Android milik penulis di Computational Lab.

Yang mana skenarionya adalah, pertama kali dibuka, aplikasi dicek apakah sudah dibeli/berlangganan atau belum (biasanya penulis tempatkan di halaman Splashscreen). Lalu jika belum, maka pada halaman berikutnya (MainActivity) akan menampilkan iklan, dan jika sudah dibeli/berlangganan maka iklan tidak perlu di download untuk ditampilkan (method callIklan).

Kode Billing Class yang penulis gunakan adalah sebagai berikut:
package com.edugameapp.yourapp;

import android.app.Activity;

import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;

import java.util.ArrayList;
import java.util.List;

public class BillingClass
{
    private BillingClient billingClient;
    private Activity mActivity=null;

    private List<String> inappid=new ArrayList<>();
    private List<String> subappid=new ArrayList<>();
    private static List<SkuDetails> skudetails=new ArrayList<>();
    private static List<Boolean> pembelian=new ArrayList<>();
    private static boolean BERUBAH=true;

    //version 1.0.2

    public BillingClass(Activity mAct)
    {
        mActivity=mAct;
    }

    public void setup()
    {
        if(mActivity==null)return;

        billingClient = BillingClient.newBuilder(mActivity).enablePendingPurchases().setListener(new BillingListener()).build();
        billingClient.startConnection(new BillingClientStateListener() {
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() ==  BillingClient.BillingResponseCode.OK){
                    if(BERUBAH)calculate();
                    onSetupFinished();
                }else{
                    requestSnackbar("Billing Response failed!: "+billingResult.getResponseCode());
                    onSetupFailed();
                }
            }

            public void onBillingServiceDisconnected() {
                requestSnackbar("Checking billing service disconnection!");
            }
        });
    }

    public void calculate()
    {
        if(BERUBAH) {
            new SubCheck(inappid, BillingClient.SkuType.INAPP) {
                public void nextTask() {
                    new SubCheck(subappid, BillingClient.SkuType.SUBS) {
                        public void nextTask() {
                            checkPurchased();
                        }
                    };
                }
            };
        }
    }

    public void onSetupFinished()
    {

    }

    public void onSetupFailed()
    {

    }

    public void resetAllCheck()
    {
        skudetails=new ArrayList<>();
        pembelian=new ArrayList<>();
        BERUBAH=true;
    }

    public class SubCheck
    {
        public SubCheck(List<String> skuList,String type)
        {
            if(billingClient==null)return;

            SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
            params.setSkusList(skuList).setType(type);
            billingClient.querySkuDetailsAsync(params.build(),
                    new SkuDetailsResponseListener() {
                        public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && skuDetailsList != null) {
                                for (SkuDetails skuDetails : skuDetailsList) {
                                    boolean ketemu = false;
                                    for (int j = 0; j < skudetails.size(); j++) {
                                        if (skudetails.get(j).getSku().equals(skuDetails.getSku())) {
                                            ketemu = true;
                                            break;
                                        }
                                    }
                                    if (!ketemu) {
                                        skudetails.add(skuDetails);
                                        pembelian.add(false);
                                    }
                                }
                            }
                            nextTask();
                        }
                    });
        }

        public void nextTask()
        {

        }
    }

    public void Buy(String skuid)
    {
        int id=-1;
        for(int i=0;i<skudetails.size();i++){
            if(skuid.equals(skudetails.get(i).getSku())){
                id=i;
                break;
            }
        }
        if(id>=0) {
            BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                    .setSkuDetails(skudetails.get(id))
                    .build();
            billingClient.launchBillingFlow(mActivity, flowParams);
        }else requestSnackbar("Item not available!");
    }

    private class BillingListener implements PurchasesUpdatedListener
    {
        public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK  && purchases != null) {
                for (Purchase purchase : purchases) {
                    handlePurchase(purchase);
                }
            } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) {
                requestSnackbar("User canceled purchase");
            } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
                requestSnackbar("Item Owned");
            }else {
                requestSnackbar("Purchase failed!");
            }
        }
    }

    private void checkPurchased()
    {
        checkPurchased(BillingClient.SkuType.INAPP);
    }

    private void checkPurchased(final String type)
    {
        billingClient.queryPurchasesAsync(type,new PurchasesResponseListener(){
            public void onQueryPurchasesResponse(BillingResult billingResult,List<Purchase> list) {
                for(int i=0;i<list.size();i++){
                    Purchase p=list.get(i);
                    for(int j=0;j<p.getSkus().size();j++){
                        for(int k=0;k<skudetails.size();k++) {
                            if (p.getSkus().get(j).equals(skudetails.get(k).getSku())) {
                                pembelian.set(k,true);
                            }
                        }
                    }
                }
                if(type.equals(BillingClient.SkuType.INAPP)) checkPurchased(BillingClient.SkuType.SUBS);
                else {
                    BERUBAH=false;
                    OnCheckPurchaseFinished();
                }
            }
        });
    }

    private void handlePurchase(Purchase purchase)
    {
        if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED){
            // Grant entitlement to the user.
            for(int j=0;j<purchase.getSkus().size();j++){
                for(int k=0;k<skudetails.size();k++){
                    if (purchase.getSkus().get(j).equals(skudetails.get(k).getSku())) {
                        pembelian.set(k,true);
                        break;
                    }
                }
            }
            // Acknowledge the purchase if it hasn't already been acknowledged.
            if (!purchase.isAcknowledged()) {
                AcknowledgePurchaseParams acknowledgePurchaseParams =
                        AcknowledgePurchaseParams.newBuilder()
                                .setPurchaseToken(purchase.getPurchaseToken())
                                .build();
                billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() {
                    public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
                        requestDialog("Thank you for purchased Computational Lab app. You need restart this app to hide all ads.");
                    }
                });
            }
        }
    }

    public void requestSnackbar(final String txt)
    {
        
    }

    public void requestDialog(final String txt)
    {

    }

    public void OnCheckPurchaseFinished()
    {

    }

    public void addInnAppId(String a)
    {
        inappid.add(a);
        BERUBAH=true;
    }

    public void addSubAppId(String a)
    {
        subappid.add(a);
        BERUBAH=true;
    }

    public static boolean isPurchased(String sku)
    {
        for (int i=0;i<skudetails.size();i++){
            if(skudetails.get(i).getSku().equals(sku)){
                return pembelian.get(i);
            }
        }
        return false;
    }

    public static String getTitle(String sku)
    {
        for (int i=0;i<skudetails.size();i++){
            if(skudetails.get(i).getSku().equals(sku)){
                return skudetails.get(i).getTitle();
            }
        }
        return "";
    }

    public static String getDescription(String sku)
    {
        for (int i=0;i<skudetails.size();i++){
            if(skudetails.get(i).getSku().equals(sku)){
                return skudetails.get(i).getDescription();
            }
        }
        return "";
    }

    public static String getPrice(String sku)
    {
        for (int i=0;i<skudetails.size();i++){
            if(skudetails.get(i).getSku().equals(sku)){
                return skudetails.get(i).getPrice();
            }
        }
        return "";
    }

    public static String getPeriod(String sku)
    {
        for (int i=0;i<skudetails.size();i++){
            if(skudetails.get(i).getSku().equals(sku)){
                String[] pilih=new String[]{"P1W","one week","P1M","one month","P3M","three months","P6M","six months","P1Y","one year"};
                int id=-1;
                for (int j=0;j<pilih.length/2;j++){
                    if(pilih[2*j+0].equals(skudetails.get(i).getSubscriptionPeriod())){
                        id=j;
                        break;
                    }
                }
                if(id>=0)return pilih[2*id+1];
            }
        }
        return "";
    }

    public void printSKU()
    {
        String txt="";
        for (int i=0;i<skudetails.size();i++){
            txt+=skudetails.get(i).toString();
            txt+="pembelian:"+pembelian.get(i);
        }
        requestSnackbar(txt);
    }
}
Sedangkan berikut ini beberapa cuplikan kode penggunaan Class di atas:

Pada file Splashscreen.java

package com.edugameapp.yourapp;

/*
.....
*/

public class SplashScreen extends AppCompatActivity
{
    private BillingClass mbillingClass;

    protected void onCreate(android.os.Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.spalshscreen);

        if(isNetworkConnected()){
            mbillingClass=new BillingClass(this){
                public void requestSnackbar(final String txt)
                {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            addLaporan(txt);
                        }
                    });
                }

                @Override
                public void OnCheckPurchaseFinished() {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            checkBuyorSub();
                        }
                    });
                }

                public void onSetupFailed()
                {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            //do another action
                        }
                    });
                }
            };
            mbillingClass.addInnAppId(MainActivity.inappid);
            mbillingClass.addSubAppId(MainActivity.subappid);
            addLaporan("Checking purchased!..");
            mbillingClass.setup();
        }else errorDialog("Sorry. This app need internet connection!",true);
    }

    private void checkBuyorSub()
    {
        boolean dibeli=mbillingClass.isPurchased(MainActivity.inappid);
        if(!dibeli){
            dibeli=mbillingClass.isPurchased(MainActivity.subappid);
            if(dibeli){
                addLaporan("Subcriber");
                maininitialized();
            }else{
                addLaporan("Free user");
                String txt=mbillingClass.getPrice(MainActivity.subappid);
                if(!txt.isEmpty()) {
                    txt="No advertisement only "+txt+" for "+mbillingClass.getPeriod(MainActivity.subappid);
                    addLaporan(txt);
                }
				
                //do another action
				
            }
        }else{
            addLaporan("Paid user");
            maininitialized();
        }
    }

    private void maininitialized()
    {
        addLaporan("Go to app..");
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                startActivity(new Intent(SplashScreen.this,MainActivity.class));
                finish();
            }
        },500);
    }

    private void errorDialog(String msg)
    {
        errorDialog(msg,false);
    }

    private void errorDialog(String msg,boolean sertakan)
    {
        AlertDialog alertDialog = new AlertDialog.Builder(SplashScreen.this).create();
        alertDialog.setTitle("EEA Initialization Error");
        //alertDialog.setIcon(R.mipmap.ic_launcher);
        alertDialog.setCancelable(false);
        android.view.View view = LayoutInflater.from(SplashScreen.this).inflate(R.layout.notification, null);
        ((TextView)view.findViewById(R.id.note_text)).setText(msg);
        alertDialog.setView(view);
        alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "OK",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        android.os.Process.killProcess(android.os.Process.myPid());
                    }
                });
        /*
        if(sertakan)
        alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Go offline",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        maininitialized();
                    }
                });
         */
        if(!isFinishing())alertDialog.show();
    }

    private boolean isNetworkConnected()
    {
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        return cm.getActiveNetworkInfo() != null;
    }

    private void addLaporan(String msg)
    {
        Toast.makeText(SplashScreen.this,msg,Toast.LENGTH_LONG).show();
    }
}

Pada file MainActivity.java

package com.edugameapp.yourapp;

/*
....
*/

public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener
{
    private static boolean DIBELI=false;
    public static String inappid = "buy_yourapp",subappid = "sub_yourapp";
    private BillingClass mbillingClass;

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

        DIBELI=BillingClass.isPurchased(inappid);
        if(!DIBELI)DIBELI=BillingClass.isPurchased(subappid);

        if(!DIBELI) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (!INITIALIZE) {
                        try {
                            Thread.sleep(1500);
                        } catch (InterruptedException e) {
                            break;
                        }
                    }
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            callIklan();
                        }
                    });
                }
            }).start();
        }
    }

    private void showSnackbar(String msg)
    {
        //show msg as snackbar
    }

    private void showMsg(String msg)
    {
        //show msg as dialog
    }

    private void callIklan()
    {
        //calliklan
    }

    public void BuyApp()
    {
        if (DIBELI) {
            showSnackbar("You are paid user!");
            return;
        }

        String ps=BillingClass.getPrice(subappid);
        String pb=BillingClass.getPrice(inappid);

        if(ps.isEmpty()|| pb.isEmpty()){
            showSnackbar("Purchase item not available!");
            return;
        }

        AlertDialog alertDialog = new AlertDialog.Builder(this).create();
        alertDialog.setTitle(getString(R.string.app_name));
        alertDialog.setIcon(R.mipmap.ic_launcher);
        View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.buyoption, null);
        ((Button)view.findViewById(R.id.btn_sub)).setText("Subscribe "+ps);
        ((Button)view.findViewById(R.id.btn_sub)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                BuyApp(subappid);
                alertDialog.dismiss();
            }
        });
        ((Button)view.findViewById(R.id.btn_buy)).setText("Paid "+pb);
        ((Button)view.findViewById(R.id.btn_buy)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                BuyApp(inappid);
                alertDialog.dismiss();
            }
        });
        alertDialog.setView(view);

        alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Other Edugameapp",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/dev?id=9085656042751581233"));
                        startActivity(browserIntent);
                        dialog.dismiss();
                    }
                });

        alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        if(!this.isFinishing())alertDialog.show();
    }

    public void BuyApp(final String idbuy)
    {
        String judul=BillingClass.getTitle(idbuy);
        String desc=BillingClass.getDescription(idbuy);
        String price=BillingClass.getPrice(idbuy);

        if(judul.isEmpty()){
            showSnackbar("Purchase item not available");
            return;
        }

        AlertDialog alertDialog = new AlertDialog.Builder(this).create();
        alertDialog.setTitle(getString(R.string.app_name));
        alertDialog.setIcon(R.mipmap.ic_launcher);
        View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.buylayout, null);
        ((TextView)view.findViewById(R.id.judul)).setText(judul);
        ((TextView)view.findViewById(R.id.desc)).setText(desc);
        ((TextView)view.findViewById(R.id.price)).setText(price);
        alertDialog.setView(view);
        /*
        alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "Other Edugameapp",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/dev?id=9085656042751581233"));
                        startActivity(browserIntent);
                        dialog.dismiss();
                    }
                });
        */
        alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "Cancel",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });
        alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "Buy",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        callBuyFlow(idbuy);
                    }
                });
        if(!this.isFinishing())alertDialog.show();
    }

    public void callBuyFlow(final String id)
    {
        if(mbillingClass==null) {
            mbillingClass = new BillingClass(this) {
                public void requestSnackbar(final String txt) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            showSnackbar(txt);
                        }
                    });
                }

                public void requestDialog(final String txt) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            showMsg(txt);
                        }
                    });
                }

                @Override
                public void onSetupFinished() {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            callBuyFlow(id);
                        }
                    });
                }
            };
            mbillingClass.setup();
        }else{
            mbillingClass.Buy(id);
        }
    }
}

Dengan buylayout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:background="@drawable/backgroundbuy"
        android:layout_margin="12dp"
        android:padding="16dp">

    <TextView
        android:id="@+id/judul"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textSize="16dp"
        android:layout_marginBottom="16dp"
        android:layout_weight="0"
        android:text="gram"/>
    <TextView
        android:id="@+id/desc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:textSize="16dp"
        android:layout_weight="0"
        android:text="gram"/>
    <TextView
        android:id="@+id/price"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="0"
        android:textSize="16dp"
        android:textStyle="bold"
        android:text="gram"/>
    </LinearLayout>
</LinearLayout>

dan backgroundbuy.xml:

<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#FFFFFF"/>
    <stroke android:width="1dp" android:color="#B1BCBE" />
    <corners android:radius="10dp"/>
    <padding android:left="10dp" android:top="10dp" android:right="10dp" android:bottom="10dp" />
</shape>

Sedangkan di file build.grandle untuk Modul App-nya perlu ditambahkan syntax dibawah ini:

dependencies {
    //...
    implementation 'com.android.billingclient:billing:4.0.0'
}
Tentu kode di atas tidak lengkap karena sekedar contoh.

Namun penulis rasa, kode-kode di atas cukup memberikan gambaran cara penggunaan Billing Library v4, di mana kode di atas telah terbukti dapat berjalan dengan baik di aplikasi-aplikasi Android penulis.

Komentar



Postingan populer dari blog ini

Kumpulan Source Code Greenfoot

Algorithma Java Mencari KPK dan FPB

Algorithma Coretan Abstrak dengan HTML5 Canvas

Kode Greenfoot Game Snake Sederhana

Cara Membuat Halaman HTML Sederhana

Algorithma Perhitungan Weton Jodoh dengan Javascript

Menambahkan reCAPTCHA v2 pada Login Form

Algorithma Collision Detection Sederhana

Algoritma Tombol Putar dengan Greenfoot

Honeycomb Style Wallpaper dengan HTML5 Canvas