การทำ In-App Billing แบบง่ายๆด้วย Library

0

หากใครเคยลองทำ In App Billing ใน Android คงจะรู้สึกว่ามันยุ่งยากวุ่นวายอยู่พอสมควร แต่พอดีผมไปเจอ Library ตัวนึงที่ช่วยทำให้การทำ In App Billing เป็นเรื่องง่าย เรามาลองดูกันดีกว่าว่าทำยังไง

แต่เดี๋ยวก่อน!!!

สำหรับคนที่ยังไม่เคยทำ In App Billing มาก่อนเลย รบกวนอ่านบทความของคุณ Artit-K ก่อนเพื่อให้เข้าใจการทำงานของ In App Billing ว่ามันมีวิธีการทำงานอย่างไร ต้อง Set Up อะไรก่อนบ้าง อ่านเสร็จก็ลองทำด้วยนะครับ จะได้เข้าใจอย่างลึกซึ้ง ถ้าเข้าใจแล้วก็ไปกันต่อเลย

[Dev] Google Play In-app Billing สำหรับ Android : Part 1 เตรียมความพร้อม
[Dev] Google Play In-app Billing สำหรับ Android : Part 2 การเขียนคำสั่งขั้นพื้นฐาน
[Dev] Google Play In-app Billing สำหรับ Android : Part 3 การนำ Utility มาใช้ในการเขียนคำสั่ง
[Dev] Google Play In-app Billing สำหรับ Android : Part 4 ถึงเวลาขายจริง

Library Android In-App Billing v3 Library ตัวนี้อยู่ใน Github หากใครถนัดอ่านภาษาอังกฤษมากกว่า สามารถดูใน Github ได้เลยเพราะตัวอย่างเขาทำไว้ค่อนข้างอยู่แล้ว

อย่างแรกคือเราต้อง Add Library เข้ามาใน Project ก่อนโดย Add ผ่าน build.gradle

repositories {
    mavenCentral()
}
dependencies {
    compile 'com.anjlab.android.iab.v3:library:1.0.29'
}

ใน AndroidManifest .xml ต้องเพิ่ม permission เพื่อขอ In App Billing เข้าไปเหมือนเดิม

<uses-permission android:name="com.android.vending.BILLING" />

มาถึงส่วน Activity สำหรับทำ In App Billing ของเรากัน ผมขอตั้งว่า InAppActivity
แล้วกันนะครับ แต่ก่อนอื่นอย่าลืมไปสร้าง Product สำหรับ In App Billing ใน Google Play Developer Console ด้วยนะครับ เราจะได้นำ Product Id มาทดลองกัน

ใน Activity ก็ประกาศ Product ID กับ Subscribe ID และ License Key ที่ได้จาก Developer Console ด้วย

private static final String PRODUCT_ID = "product1";
private static final String SUBSCRIPTION_ID = "subs1";
private static final String LICENSE_KEY = null;
private static final String MERCHANT_ID=null;

Merchant ID เอาไว้สำหรับป้องกันการปลอมแปลง แต่ไม่ต้องใส่ก็ได้ ประกาศ BillingProcessor ที่จะเอาไว้ทำ In App Billing

private BillingProcessor bp;
private boolean readyToPurchase = false;

ที่ onCreate เราต้องเช็คก่อนว่ารองรับ In App Billing หรือไม่

if(!BillingProcessor.isIabServiceAvailable(this)) {
    showToast("In-app billing service is unavailable, please upgrade Android Market/Play to version >= 3.9.16");
}

showToast แค่เอาไว้แสดง Toast เท่านั้นครับ ไม่ได้ทำอะไรแปลกปลอม จากนั้นก็ทำการกำหนดค่าให้ bp ใน onCreate

bp = new BillingProcessor(this, LICENSE_KEY, MERCHANT_ID, new BillingProcessor.IBillingHandler() {
    @Override
    public void onProductPurchased(String productId, TransactionDetails transactionDetails) {
        showToast("onProductPurchased: " + productId);
    }

    @Override
    public void onPurchaseHistoryRestored() {
        showToast("onPurchaseHistoryRestored");
        for(String sku : bp.listOwnedProducts())
            Log.d(LOG_TAG, "Owned Managed Product: " + sku);
        for(String sku : bp.listOwnedSubscriptions())
            Log.d(LOG_TAG, "Owned Subscription: " + sku);
    }

    @Override
    public void onBillingError(int errorCode, Throwable throwable) {
        showToast("onBillingError: " + Integer.toString(errorCode));
    }

    @Override
    public void onBillingInitialized() {
        showToast("onBillingInitialized");
        readyToPurchase = true;
    }
});

onBillingInitialized เมื่อทำการ Initial เสร็จแล้วจะเข้าฟังก์ชั่นนี้ เราก็กำหนด readyToPurchase = true;
เพื่อให้ใช้งาน In App Billing ได้ตามปกติ

onPurchaseHistoryRestored จะทำงานต่อจาก onBillingInitialized โดยจะดึงของที่เราเคยซื้อทุกอย่างออกมาเป็น List ทั้งที่เป็น Product(ของที่ซื้อครั้งเดียว) และ Subscribe จะได้เป็น ID ออกมา

onProductPurchased จะทำงานเมื่อซื้อของสำเร็จ จะได้ product ID และ transactionDetail โดย transactionDetail สามารใช้หา productToken เพื่อส่งให้ฝั่ง Server นำไปเช็ควันหมดอายุของ Subscribe กับทาง Google API ได้ โดยใช้คำสั่ง transactionDetails.purchaseToken

onBillingError จะทำงานเมื่อเราซื้อไม่สำเร็จ เช่น ไม่ได้ต่ออินเตอร์เนตหรือไม่ได้ลงทะเบียน Product ID

ยัง!!! ยังไม่เสร็จครับ ต้องใส่คำสั่งใน onDestoy ด้วย

@Override
protected void onDestroy() {
    if (bp != null)
        bp.release();
    super.onDestroy();
}

ต้องใส่ onActivityResult ด้วยเพราะการซื้อของด้วย In App Billing จะส่ง Intent ออกไปนอก App ของเรา

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (!bp.handleActivityResult(requestCode, resultCode, data))
        super.onActivityResult(requestCode, resultCode, data);
}

เท่านี้เราก็จะ Set Up ค่าต่างๆ เสร็จหมดแล้ว ลอง Run ดูนะครับ ถ้ารันผ่านขึ้นว่า Initialize สำเร็จก็ไปกันต่อได้ ถ้ายังไม่ได้ลองดูตัวเต็มด้านล่างครับ ว่ามีอะไรต่างกัน

public class InAppActivity extends AppCompatActivity {

    private static final String LOG_TAG = "InAppBilling";
    private static final String PRODUCT_ID = "com.kamonway.product1";
    private static final String SUBSCRIPTION_ID = "com.kamonway.subs1";
    private static final String LICENSE_KEY = null;
    private static final String MERCHANT_ID=null;

    private BillingProcessor bp;
    private boolean readyToPurchase = false;

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

        if(!BillingProcessor.isIabServiceAvailable(this)) {
            showToast("In-app billing service is unavailable, please upgrade Android Market/Play to version >= 3.9.16");
        }

        bp = new BillingProcessor(this, LICENSE_KEY, MERCHANT_ID, new BillingProcessor.IBillingHandler() {
            @Override
            public void onProductPurchased(String productId, TransactionDetails transactionDetails) {
                showToast("onProductPurchased: " + productId);
            }

            @Override
            public void onPurchaseHistoryRestored() {
                showToast("onPurchaseHistoryRestored");
                for(String sku : bp.listOwnedProducts())
                    Log.d(LOG_TAG, "Owned Managed Product: " + sku);
                for(String sku : bp.listOwnedSubscriptions())
                    Log.d(LOG_TAG, "Owned Subscription: " + sku);
            }

            @Override
            public void onBillingError(int errorCode, Throwable throwable) {
                showToast("onBillingError: " + Integer.toString(errorCode));
            }

            @Override
            public void onBillingInitialized() {
                showToast("onBillingInitialized");
                readyToPurchase = true;
            }
        });

    }

    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }

    @Override
    protected void onDestroy() {
        if (bp != null)
            bp.release();
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (!bp.handleActivityResult(requestCode, resultCode, data))
            super.onActivityResult(requestCode, resultCode, data);
    }
}

มาต่อกันที่การซื้อขายดีกว่า ผมไปทำ Button เอาไว้ที่ Layout เพื่อใช้สำหรับกดคำสั่งต่างๆ
android in app billing
การซื้อ Product
การซื้อของที่ซื้อได้ครั้งเดียว เช่น ซื้อเพื่อลบ Ads จะใช้คำสั่งด้านล่าง โดยใส่ Product ID

bp.purchase(this,PRODUCT_ID);

การซื้อ Consume
Consume คือการซื้อพวกของสิ้นเปลืองเช่น ซื้อ เพชรในเกม Cookie Run

Boolean consumed = bp.consumePurchase(PRODUCT_ID); 
if (consumed)
    showToast("Successfully consumed");

การดูรายละเอียดของ Product
รายละเอียดของ product จะแสดงทั้งชื่อ รายละเอียด ราคาทั้งแบบเป็นดอลล่าและแบบราคาตามพื้นที่เช่น ไทยก็จะเป็น บาท

SkuDetails sku = bp.getPurchaseListingDetails(PRODUCT_ID);
showToast(sku != null ? sku.toString() : "Failed to load SKU details");

การซื้อ Subscribe
จะเป็นการสมัคร Subscribe โดยผู้ใช้สามารถยกเลิกการ Subscribe ได้ที่เมนู Account ใน App Google Play และเราสามารถนำ product Token ไปยกเลิกผ่าน Google API ได้อีกด้วย

bp.subscribe(this,SUBSCRIPTION_ID);

การดูรายละเอียดของ Subscribe
การทำงานจะคล้ายๆกันการดูรายละเอียดของ Product

SkuDetails subs = bp.getSubscriptionListingDetails(SUBSCRIPTION_ID);
showToast(subs != null ? subs.toString() : "Failed to load subscription details");

การทำ Restore Purchase
เราจะใช้คำสั่งด้านล่างเพื่อโหลดข้อมูลจาก Google และใช้คำสั่งใน updateStatus() เพื่อเช็ดว่าเราซื้อของที่เป็น Product ID หรือ Subscribe ID นี้ไปหรือยัง

if (bp.loadOwnedPurchasesFromGoogle()) {
    showToast("Subscriptions updated.");
    updateStatus();
}

private void updateStatus() {
    Log.d(LOG_TAG,String.format("%s is%s purchased", PRODUCT_ID, bp.isPurchased(PRODUCT_ID) ? "" : " not"));
    Log.d(LOG_TAG, String.format("%s is%s subscribed", SUBSCRIPTION_ID, bp.isSubscribed(SUBSCRIPTION_ID) ? "" : " not"));
}

จะเห็นว่า Library จะช่วยลดขั้นตอนและลดการเขียน Code ลงไปได้มาก ส่วนขั้นตอนอื่นๆก็ทำเหมือนปกตินะครับ Library แค่ช่วยลดจำนวน Code เท่านั้น แต่ถ้าเราไม่เข้าใจ Copy Code ไปใช้เลย อาจจะทำงานผิดพลาดได้ โดยเฉพาะเรื่องเงินๆทองๆแบบนี้ด้วยแล้ว ควรจะระวังเป็นอย่างดี

public class InAppActivity extends AppCompatActivity {

    private static final String LOG_TAG = "InAppBilling";
    private static final String PRODUCT_ID = "product1";
    private static final String SUBSCRIPTION_ID = "subs1";
    private static final String LICENSE_KEY = null;
    private static final String MERCHANT_ID=null;

    private BillingProcessor bp;
    private boolean readyToPurchase = false;

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

        if(!BillingProcessor.isIabServiceAvailable(this)) {
            showToast("In-app billing service is unavailable, please upgrade Android Market/Play to version >= 3.9.16");
        }

        bp = new BillingProcessor(this, LICENSE_KEY, MERCHANT_ID, new BillingProcessor.IBillingHandler() {
            @Override
            public void onProductPurchased(String productId, TransactionDetails transactionDetails) {
                showToast("onProductPurchased: " + productId);
            }

            @Override
            public void onPurchaseHistoryRestored() {
                showToast("onPurchaseHistoryRestored");
                for(String sku : bp.listOwnedProducts())
                    Log.d(LOG_TAG, "Owned Managed Product: " + sku);
                for(String sku : bp.listOwnedSubscriptions())
                    Log.d(LOG_TAG, "Owned Subscription: " + sku);
            }

            @Override
            public void onBillingError(int errorCode, Throwable throwable) {
                showToast("onBillingError: " + Integer.toString(errorCode));
            }

            @Override
            public void onBillingInitialized() {
                showToast("onBillingInitialized");
                readyToPurchase = true;
            }
        });

    }

    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
    }

    private void updateStatus() {
        Log.d(LOG_TAG,String.format("%s is%s purchased", PRODUCT_ID, bp.isPurchased(PRODUCT_ID) ? "" : " not"));
        Log.d(LOG_TAG, String.format("%s is%s subscribed", SUBSCRIPTION_ID, bp.isSubscribed(SUBSCRIPTION_ID) ? "" : " not"));
    }

    @Override
    protected void onDestroy() {
        if (bp != null)
            bp.release();
        super.onDestroy();
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (!bp.handleActivityResult(requestCode, resultCode, data))
            super.onActivityResult(requestCode, resultCode, data);
    }

    public void onClick(View v) {
        if (!readyToPurchase) {
            showToast("Billing not initialized.");
            return;
        }
        switch (v.getId()) {
            case R.id.purchaseButton:
                bp.purchase(this,PRODUCT_ID);
                break;
            case R.id.consumeButton:
                Boolean consumed = bp.consumePurchase(PRODUCT_ID);
                updateStatus();
                if (consumed)
                    showToast("Successfully consumed");
                break;
            case R.id.productDetailsButton:
                SkuDetails sku = bp.getPurchaseListingDetails(PRODUCT_ID);
                showToast(sku != null ? sku.toString() : "Failed to load SKU details");
                break;
            case R.id.subscribeButton:
                bp.subscribe(this,SUBSCRIPTION_ID);
                break;
            case R.id.updateSubscriptionsButton:
                if (bp.loadOwnedPurchasesFromGoogle()) {
                    showToast("Subscriptions updated.");
                    updateStatus();
                }
                break;
            case R.id.subsDetailsButton:
                SkuDetails subs = bp.getSubscriptionListingDetails(SUBSCRIPTION_ID);
                showToast(subs != null ? subs.toString() : "Failed to load subscription details");
                break;
            default:
                break;
        }
    }

}

บทความที่น่าสนใจ
A/B Testing in Google Play Store
ทำชีวิตให้ง่ายด้วย Build Variants
ตัวอย่างการใช้ Vibrator ใน Android

Facebook Comments
Share.

About Author

Comments are closed.