Friday, April 19

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

Google+ Pinterest LinkedIn Tumblr +

หากใครเคยลองทำ 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

[xml] repositories {
mavenCentral()
}
dependencies {
compile ‘com.anjlab.android.iab.v3:library:1.0.29’
}
[/xml]

ใน AndroidManifest .xml ต้องเพิ่ม permission เพื่อขอ In App Billing เข้าไปเหมือนเดิม
[xml]<uses-permission android:name="com.android.vending.BILLING" />[/xml]

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

ใน Activity ก็ประกาศ Product ID กับ Subscribe ID และ License Key ที่ได้จาก Developer Console ด้วย
[java] 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;
[/java]

Merchant ID เอาไว้สำหรับป้องกันการปลอมแปลง แต่ไม่ต้องใส่ก็ได้ ประกาศ BillingProcessor ที่จะเอาไว้ทำ In App Billing
[java] private BillingProcessor bp;
private boolean readyToPurchase = false;
[/java] ที่ onCreate เราต้องเช็คก่อนว่ารองรับ In App Billing หรือไม่
[java] if(!BillingProcessor.isIabServiceAvailable(this)) {
showToast("In-app billing service is unavailable, please upgrade Android Market/Play to version >= 3.9.16");
}
[/java] showToast แค่เอาไว้แสดง Toast เท่านั้นครับ ไม่ได้ทำอะไรแปลกปลอม จากนั้นก็ทำการกำหนดค่าให้ bp ใน onCreate
[java] 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;
}
});
[/java] 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 ด้วย
[java] @Override
protected void onDestroy() {
if (bp != null)
bp.release();
super.onDestroy();
}
[/java] ต้องใส่ onActivityResult ด้วยเพราะการซื้อของด้วย In App Billing จะส่ง Intent ออกไปนอก App ของเรา
[java] @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (!bp.handleActivityResult(requestCode, resultCode, data))
super.onActivityResult(requestCode, resultCode, data);
}
[/java] เท่านี้เราก็จะ Set Up ค่าต่างๆ เสร็จหมดแล้ว ลอง Run ดูนะครับ ถ้ารันผ่านขึ้นว่า Initialize สำเร็จก็ไปกันต่อได้ ถ้ายังไม่ได้ลองดูตัวเต็มด้านล่างครับ ว่ามีอะไรต่างกัน
[java] 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);
}
}
[/java] มาต่อกันที่การซื้อขายดีกว่า ผมไปทำ Button เอาไว้ที่ Layout เพื่อใช้สำหรับกดคำสั่งต่างๆ
android in app billing
การซื้อ Product
การซื้อของที่ซื้อได้ครั้งเดียว เช่น ซื้อเพื่อลบ Ads จะใช้คำสั่งด้านล่าง โดยใส่ Product ID
[java] bp.purchase(this,PRODUCT_ID);
[/java] การซื้อ Consume
Consume คือการซื้อพวกของสิ้นเปลืองเช่น ซื้อ เพชรในเกม Cookie Run
[java] Boolean consumed = bp.consumePurchase(PRODUCT_ID);
if (consumed)
showToast("Successfully consumed");
[/java] การดูรายละเอียดของ Product
รายละเอียดของ product จะแสดงทั้งชื่อ รายละเอียด ราคาทั้งแบบเป็นดอลล่าและแบบราคาตามพื้นที่เช่น ไทยก็จะเป็น บาท
[java] SkuDetails sku = bp.getPurchaseListingDetails(PRODUCT_ID);
showToast(sku != null ? sku.toString() : "Failed to load SKU details");
[/java] การซื้อ Subscribe
จะเป็นการสมัคร Subscribe โดยผู้ใช้สามารถยกเลิกการ Subscribe ได้ที่เมนู Account ใน App Google Play และเราสามารถนำ product Token ไปยกเลิกผ่าน Google API ได้อีกด้วย
[java] bp.subscribe(this,SUBSCRIPTION_ID);
[/java]

การดูรายละเอียดของ Subscribe
การทำงานจะคล้ายๆกันการดูรายละเอียดของ Product
[java] SkuDetails subs = bp.getSubscriptionListingDetails(SUBSCRIPTION_ID);
showToast(subs != null ? subs.toString() : "Failed to load subscription details");
[/java] การทำ Restore Purchase
เราจะใช้คำสั่งด้านล่างเพื่อโหลดข้อมูลจาก Google และใช้คำสั่งใน updateStatus() เพื่อเช็ดว่าเราซื้อของที่เป็น Product ID หรือ Subscribe ID นี้ไปหรือยัง
[java] 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"));
}
[/java] จะเห็นว่า Library จะช่วยลดขั้นตอนและลดการเขียน Code ลงไปได้มาก ส่วนขั้นตอนอื่นๆก็ทำเหมือนปกตินะครับ Library แค่ช่วยลดจำนวน Code เท่านั้น แต่ถ้าเราไม่เข้าใจ Copy Code ไปใช้เลย อาจจะทำงานผิดพลาดได้ โดยเฉพาะเรื่องเงินๆทองๆแบบนี้ด้วยแล้ว ควรจะระวังเป็นอย่างดี

[java] 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;
}
}

}

[/java]

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

Facebook Comments
Share.

About Author

สวัสดีครับ ผมไอซ์ กมลวัฒน์ ผู้ก่อตั้งช่อง Youtube Kamonway และเว็บไซต์ kamonway.com เพื่อช่วยแนะนำความรู้จากเกม และเป็นช่องทางที่ช่วยให้ทุกท่านเล่นเกมอย่างสนุกสนานยิ่งขึ้น

Comments are closed.