[Dev] Google Play In-app Billing สำหรับ Android : Part 2 การเขียนคำสั่งขั้นพื้นฐาน

จากความเดิมตอนที่แล้ว Google Play In-app Billing สำหรับ Android : Part 1 เตรียมความพร้อม เรามาต่อ Part 2 กันเลย

In-app Billing Android

ในตอนที่แล้ว เราได้เพิ่ม Google Play Billing Library เข้ามาแล้ว เราก็สามารถเขียนคำสั่งแบบพื้นฐาน เพื่อทำการ ขายของใน App ซึ่งจะมีขั้นตอนดังนี้

 

List of contents

 

In-app Purchase Flow

สำหรับขั้นตอนพื้นฐานสำหรับการใช้งาน In-app Billing Version 3 มี Flow การทำงาน ดังนี้

In-app Billing Version 3 Purchase Flow

ซึ่งจะมีขั้นตอนโดยสรุป คือ

  • isBillingSupported() – ตรวจสอบว่าอุปกรณ์รองรับรึไม่
  • getPurchases() – ตรวจสอบรายการสินค้าที่ User เป็นเจ้าของ โดยดึงข้อมูลจาก Google
  • getSkuDetails() – ขอรายละเอียดรายการสินค้าที่ต้องการจะขาย โดยดึงข้อมูลจาก Google
  • getBuyIntent() – ขอข้อมูลการสั่งซื้อของสินค้าที่เลือกมาจากรายการ เพื่อนำข้อมูลไปใช้ต่อในขั้นตอนต่อไป
  • startIntentSenderForResult() – กำหนดข้อมูลเกี่ยวกับผลการสั่งซื้อจาก getBuyIntent() จะขึ้น Dialog ของ Google play ให้ทำการซื้อได้ โดยเมื่อ Dialog ของ Google play ปิดตัวลง จะส่งข้อมูลกลับมายัง onActivityResult()

มาลองเขียนคำสั่งกัน แบบ Step by step ถ้าพร้อม แล้ว อ่านต่อกันเลยครับ

 

Declare variable

เริ่มที่การประกาศตัวแปรใน Class MainActivity ก่อน

  • ตัวแปร mService เป็นชนิด IInAppBillingService ใช้สำหรับการเชื่อมต่อกับ Google
  • ตัวแปร mServiceConn เป็นชนิด ServiceConnection ใช้สำหรับการทำงานเป็น Service

แล้วทำการสร้าง ServiceConnection ให้ตัวแปร mServiceConn

เนื่องจาก ServiceConnection เป็น Interface จึงจำเป็นต้อง Implement 2 Methods ดังนี้

  • Method onServiceDisconnected() จะถูกเรียกใช้เมื่อเลิกเชื่อมต่อกับ Service โดยที่เราจะทำการยกเลิกตัวแปร mService ดังในคำสั่งบรรทัดที่ 5
  • Method onServiceConnected() จะถูกเรียกใช้เมื่อเชื่อมต่อกับ Service โดยที่เราจะทำการกำหนดค่าให้ตัวแปร mService ดังในคำสั่งบรรทัดที่ 10

 

Bind Service & Unbind Service

หลังจากนั้น ที่ Method onCreate() ของ Class MainActivity ให้ทำการ Bind Service

  • ทำการเรียกใช้ Method bindService() ดังในคำสั่งบรรทัดที่ 7

อย่าลืมตรวจสอบผลการ bind ด้วยนะครับ ซึ่ง Method bindService() จะ return เป็น boolean

หากใครที่รันบน Emulator (ที่ไม่ได้ลง gapps ไว้) จะไม่ผ่านนะครับ

และทำการ Unbind Service เมื่อ MainActivity ถูกทำลาย ที่ Method onDestroy() ดังนี้

  • ตรวจสอบตัวแปร mService ก่อนว่า Service ได้มีการเชื่อมต่อแล้ว จึงทำการเรียกใช้ Method unbindService() ดังในคำสั่งบรรทัดที่ 4

 

Design User Interface

ต่อไปเราจะออกแบบหน้าจอกัน โดยในบทความนี้ ให้สร้างปุ่มขึ้นมา 4 ปุ่ม ดังนี้ (เพื่อความรวดเร็ว สามารถ Download layout_main.xml ไปใช้ได้เลย)

ออกแบบหน้าจอให้มีปุ่ม 4 ปุ่ม
ออกแบบหน้าจอให้มีปุ่ม 4 ปุ่ม

ทำการประกาศตัวแปรที่ Class MainActivity

แล้วทำการกำหนดค่าให้อ้างอิงไปที่ปุ่มทั้ง 4 ที่ Method onCreate()

และทำการดักจับ Event Click ของทั้ง 4 ปุ่ม

 

Test Billing Support

ขั้นตอนต่อไป เราจะมาเขียนคำสั่งใน Event Click ของปุ่ม btnTest กัน

คำสั่งในปุ่มนี้ จะใช้ทำการทดสอบว่า อุปกรณ์ที่กำลังทำงานอยู่นั้น รองรับรึเปล่า ด้วย Method isBillingSupported()

จากคำสั่งด้านบน จะใช้ Method isBillingSupported() ในการตรวจสอบ โดยมีรายละเอียดการใช้งานดังนี้

Parameters:

int apiVersion คือ Version ของ Google Play Billing Library ปัจจุบันกำหนดค่าเป็น 3

String packageName คือชื่อ Package ของเรา สามารถกำหนดได้ด้วย Method getPackageName()

String type คือชนิดที่จะตรวจสอบ สามารถกำหนดได้เป็น “inapp” สำหรับการขายของใน App และ “subs” สำหรับการสมัครสมาชิก

Returns:

Return value เป็นชนิด int กรณีที่ Success จะได้ค่าเป็น 0

Exceptions:

คำสั่งนี้ สามารถ Throw Exception เป็นชนิด RemoteException ได้ จึงจำเป็นต้องเขียน Try-Catch ครอบไว้เพื่อดักจับ

 

Check Purchase

สำหรับการกดปุ่ม btnCheck เราจะเขียนคำสั่งสำหรับตรวจสอบรายการสินค้าที่เคยซื้อมาแล้ว และ User ยังเป็นเจ้าของอยู่ ด้วย Method getPurchases()

จากคำสั่งด้านบน จะใช้ Method getPurchases() ในการตรวจสอบ โดยมีรายละเอียดการใช้งานดังนี้

Parameters:

int apiVersion คือ Version ของ Google Play Billing Library ปัจจุบันกำหนดค่าเป็น 3

String packageName คือชื่อ Package ของเรา สามารถกำหนดได้ด้วย Method getPackageName()

String type คือชนิดที่จะตรวจสอบ สามารถกำหนดได้เป็น “inapp” สำหรับการขายของใน App และ “subs” สำหรับการสมัครสมาชิก

String continuationToken คือข้อมูลที่จะได้จากการเรียกใช้ Method getPurchases() ในครั้งที่แล้ว กรณีที่ User เป็นเจ้าของ ของที่ซื้อใน App จำนวนมาก ๆ โดยครั้งแรกจะต้องกำหนดค่าเป็น null

Returns:

Return value เป็นชนิด Bundle ซึ่งจะบรรจุข้อมูลต่าง ๆ ที่ Server ส่งกลับมาให้

Exceptions:

คำสั่งนี้ สามารถ Throw Exception เป็นชนิด RemoteException ได้ จึงจำเป็นต้องเขียน Try-Catch ครอบไว้เพื่อดักจับ

เมื่อ Method getPurchases() ได้ Return ค่า Bundle มาให้แล้ว จะต้องนำ Bundle มาตรวจสอบข้อมูล ดังนี้

เนื่องจาก Bundle เป็น Class ที่เก็บข้อมูลในลักษณะ Key-Value Pairs เราสามารถตรวจสอบผลการทำงานจาก Key RESPONSE_CODE ซึ่งมี Value เป็นชนิด int ด้วย Method getInt()

Key:

RESPONSE_CODE

Returns:

Return value เป็นชนิด int กรณีที่ Success จะได้ค่าเป็น 0

เมื่อผลการทำงาน Success เราสามารถอ่านข้อมูลรายการของที่ User ซื้อได้ดังนี้

Key:

INAPP_PURCHASE_ITEM_LIST

Returns:

Return value เป็นชนิด ArrayList<String> ซึ่งบรรจุรายการของ Product ID ของ ของที่ User ซื้อไปแล้ว

Key:

INAPP_PURCHASE_DATA_LIST

Returns:

Return value เป็นชนิด ArrayList<String> ซึ่งบรรจุรายการของรายละเอียดการสั่งซื้อ ซึ่งมีรูปแบบเป็น JSON

Key:

INAPP_DATA_SIGNATURE

Returns:

Return value เป็นชนิด ArrayList<String> ซึ่งบรรจุรายการของ Signature ของการซื้อ

Key:

INAPP_CONTINUATION_TOKEN

Returns:

Return value เป็นชนิด String ซึ่งเป็นข้อมูลที่ใช้เป็น input ในการเรียกใช้งาน Method getPurchases() ในรอบต่อไปในกรณีที่อ่านข้อมูลมายังไม่ครบ กรณีที่ได้ข้อมูลครบแล้ว จะ มีค่าเป็น null

ตัวอย่าง JSON จาก Key INAPP_PURCHASE_DATA_LIST

 

Buy

สำหรับการกดปุ่ม btnBuy เราจะเขียนคำสั่งสำหรับการซื้อ โดยจะต้องส่งคำสั่งไปขอข้อมูลสินค้าจาก Google ก่อน ด้วย Method getSkuDetails()

ก่อนอื่น ให้ประกาศตัวแปรสำหรับเก็บข้อมูล Product ID ก่อน โดยประกาศไว้ที่ Class MainActivity

ในตัวอย่างนี้ เราจะใช้ Product ID Test ที่ Google เตรียมไว้ให้ ก่อน ดังนี้

แล้วก็มาเขียน Code ใน Event Click ของปุ่ม btnBuy ต่อ โดยจะมีหลายขั้นตอน โดยในขั้นตอนแรก จะเป็นการขอรายละเอียดของรายการที่จะขาย ดังนี้

  • บรรทัดที่ 1-2 จะเป็นการกำหนดรายการของ Product ID ที่จะขอรายละเอียด
  • บรรทัดที่ 3-4 จะเป็นการเก็บข้อมูลรายการ Product ID ลงตัวแปรชนิด Bundle ซึ่งจะเป็น Input ในการใช้งาน Method getSkuDetails() ในการตรวจสอบ ในบรรทัดที่ 8 โดยมีรายละเอียดการใช้งานดังนี้

Parameters:

int apiVersion คือ Version ของ Google Play Billing Library ปัจจุบันกำหนดค่าเป็น 3

String packageName คือชื่อ Package ของเรา สามารถกำหนดได้ด้วย Method getPackageName()

String type คือชนิดที่จะตรวจสอบ สามารถกำหนดได้เป็น “inapp” สำหรับการขายของใน App และ “subs” สำหรับการสมัครสมาชิก

Bundle skusBundle คือข้อมูลที่บรรจุรายการของ Product ID ที่จะขอรายละเอียด โดยจะต้องมี Key เป็น ITEM_ID_LIST และ Value เป็นชนิด ArrayList<String>

Returns:

Return value เป็นชนิด Bundle ซึ่งจะบรรจุข้อมูลต่าง ๆ ที่ Server ส่งกลับมาให้

Exceptions:

คำสั่งนี้ สามารถ Throw Exception เป็นชนิด RemoteException ได้ จึงจำเป็นต้องเขียน Try-Catch ครอบไว้เพื่อดักจับ

เมื่อ Method getSkuDetails() ได้ Return ค่า Bundle มาให้แล้ว จะต้องนำ Bundle มาตรวจสอบข้อมูล ดังนี้

Key:

RESPONSE_CODE

Returns:

Return value เป็นชนิด int กรณีที่ Success จะได้ค่าเป็น 0

เมื่อผลการทำงาน Success เราสามารถอ่านข้อมูลรายละเอียดของ Product ได้ดังนี้

Key:

DETAILS_LIST

Returns:

Return value เป็นชนิด ArrayList<String> ซึ่งบรรจุรายละเอียดของ Product ID ซึ่งมีรูปแบบเป็น JSON

ตัวอย่าง JSON จาก Key DETAILS_LIST

เมื่อได้รายละเอียดของ Product ID มาแล้ว เราสามารถนำไปใช้ในการแสดงบนหน้าจอ เพื่อให้ User ได้ดูก่อนก็ได้ แต่ในตัวอย่างนี้ จะข้ามขั้นตอนนั้นไป

เนื่องจากรายละเอียดของ Product ID ที่ Return กลับมา เป็นชนิด ArrayList<String> จึงต้องใช้ Loop for-each อ่านค่าออกมาในแต่ละรอบ ดังนี้

  • บรรทัดที่ 1 ใช้ Loop for-each เพื่อดึงแต่ละรายการมาไว้ที่ตัวแปร thisResponse เพราะฉะนั้น ตัวแปรนี้จะมีข้อมูลในรูปแบบ JSON อยู่ในตัวแปรชนิด String
  • บรรทัดที่ 3 ทำการแปลง String เป็น JSONObject ด้วย Class JSONObject
  • บรรทัดที่ 5-7 เป็นตัวอย่างการอ่านข้อมูลแต่ละตัวออกมา โดยจุดสำคัญอยู่ที่ตัวแปร sku หรือค่า Product ID นั่นเอง

เมื่อได้ข้อมูลของสินค้าที่ขายได้มาแล้ว ให้ตรวจสอบค่า Product ID ที่อ่านออกมาได้ เทียบกับ Product ID ที่ส่งเข้าไปด้วย หลังจากนั้น จึงเรียกใช้งาน Method getBuyIntent()

  • บรรทัดที่ 1 ทำการตรวจสอบค่า Product ID ก่อน
  • บรรทัดที่ 3 ค่อยเรียกใช้ Method getBuyIntent() โดยมีรายละเอียดการใช้งานดังนี้

Parameters:

int apiVersion คือ Version ของ Google Play Billing Library ปัจจุบันกำหนดค่าเป็น 3

String packageName คือชื่อ Package ของเรา สามารถกำหนดได้ด้วย Method getPackageName()

String sku คือ Product ID ที่ User จะซื้อ

String type คือชนิดที่จะตรวจสอบ สามารถกำหนดได้เป็น “inapp” สำหรับการขายของใน App และ “subs” สำหรับการสมัครสมาชิก

String developerPayload คือข้อมูลที่เราสามารถ Generate ขึ้นมาได้เอง หรือกำหนดเป็นค่าอะไรก็ได้ จะใช้อ้างอิงเมื่อรายการสั่งซื้อสำเร็จ เพื่อเอาไว้ตรวจสอบย้อนกลับได้ว่าการซื้อถูกต้องหรือไม่

Returns:

Return value เป็นชนิด Bundle ซึ่งจะบรรจุข้อมูลต่าง ๆ ที่ Server ส่งกลับมาให้

Exceptions:

คำสั่งนี้ สามารถ Throw Exception เป็นชนิด RemoteException ได้ จึงจำเป็นต้องเขียน Try-Catch ครอบไว้เพื่อดักจับ

เมื่อ Method getBuyIntent() ได้ Return ค่า Bundle มาให้แล้ว จะต้องนำ Bundle มาตรวจสอบข้อมูล ดังนี้

Key:

RESPONSE_CODE

Returns:

Return value เป็นชนิด int กรณีที่ Success จะได้ค่าเป็น 0

เมื่อผลการทำงาน Success เราสามารถอ่านข้อมูลจาก Key BUY_INTENT ซึ่งจะได้ข้อมูลเป็นชนิด PendingIntent แล้วจึงนำไปใช้ต่อด้วย Method startIntentSenderForResult()

  • บรรทัดที่ 2 ทำการอ่านค่า PendingIntent ออกมาด้วย Key BUY_INTENT
  • บรรทัดที่ 3 เรียกใช้งาน Method startIntentSenderForResult() โดยมีรายละเอียดการใช้งานดังนี้

Parameters:

IntentSender intent คือค่า Intent ที่ต้องนำค่าจาก PendingIntent ส่งให้ ด้วยคำสั่ง pendingIntent.getIntentSender()

int requestCode เป็นหมายเลขร้องขอ โดยเรากำหนดขึ้นได้เอง และควรมีค่ามากกว่า 0 เพื่อใช้ในการตรวจสอบ เมื่อสิ้นสุด Dialog ของ Google play ด้วย Method onActivityResult()

Intent fillInIntent ให้กำหนดค่า Intent เปล่า ๆ เข้าไป

int flagsMask ให้กำหนดค่าเป็น 0

int flagsValues ให้กำหนดค่าเป็น 0

int extraFlags ให้กำหนดค่าเป็น 0

Exceptions:

คำสั่งนี้ สามารถ Throw Exception เป็นชนิด SendIntentException ได้ จึงจำเป็นต้องเขียน Try-Catch ครอบไว้เพื่อดักจับ

เมื่อเรียกใช้ Method startIntentSenderForResult() แล้วจะเกิด Dialog ของ Google play สำหรับแจ้งรายการสั่งซื้อ

In-app Billing Android

เมื่อแตะที่ปุ่ม BUY เพื่อซื้อ หรือยกเลิก จะเกิด Method onActivityResult() เพื่อตรวจสอบผลการซื้อ ดังนี้

  • ที่ Method onActivityResult() จะมี Parameter ส่งคืนมาให้ 3 ค่า ดังในบรรทัดที่ 2
  • บรรทัดที่ 3 ทำการตรวจสอบ requestCode ว่าเป็นเลขเดียวกับที่กำหนดตอนเรียกใช้ Method startIntentSenderForResult()
  • บรรทัดที่ 4 ทำการตรวจสอบผลที่ Return กลับมา

Parameters:

int requestCode คือหมายเลขร้องขอ ที่ส่งคืนกลับมา จากการย้อนกลับมาที่ Activity นี้

int resultCode คือรหัสผลการทำงาน กรณีสำเร็จ จะ Return เป็น RESULT_OK (-1)

Intent data คือข้อมูลที่ส่งคืนกลับมา

เมื่อเราตรวจสอบ requestCode และ resultCode ตามตัวอย่างด้านบนแล้ว ก็สามารถตรวจสอบผลการสั่งซื้อเพิ่มเติมได้ โดยการอ่าน Extra ออกมาจาก Intent ดังนี้

Key:

RESPONSE_CODE

Returns:

Return value เป็นชนิด int กรณีที่ Success จะได้ค่าเป็น 0

เมื่อผลการทำงาน Success เราสามารถอ่านข้อมูลรายละเอียดของผลการสั่งซื้อได้ดังนี้

Key:

INAPP_PURCHASE_DATA

Returns:

Return value เป็นชนิด String ซึ่งบรรจุรายละเอียดการสั่งซื้อ ซึ่งมีรูปแบบเป็น JSON เช่นเดียวกับที่เรียกใช้ Method getPurchases()

Key:

INAPP_DATA_SIGNATURE

Returns:

Return value เป็นชนิด String ซึ่งบรรจุ Signature ของการซื้อ

ซึ่งเราสามารถตรวจสอบค่าต่าง ๆ ที่ได้มา เพื่อตรวจสอบความสมบูรณ์ของการสั่งซื้อได้ จากข้อมูลต่าง ๆ ที่กำหนดในขั้นตอนก่อนหน้านี้ เช่นค่า developerPayload เป็นต้น และยังมีอีก 1 ค่าที่สำคัญ หากต้องการให้ Product ID ที่จะขายนั้นเป็นแบบสิ้นเปลือง นั่นคือค่า purchaseToken ซึ่งจะได้เห็นในหัวข้อต่อไป

 

In-app Consumption Flow

สำหรับบาง App เช่น เกม ที่มีการขาย Item ประเภทสิ้นเปลือง (ซื้อแล้วซื้อซ้ำได้อีก) หลังจากคำสั่งซื้อเสร็จสมบูรณ์แล้ว จำเป็นจะต้องส่งคำสั่งไปบอก Google ว่า Item ชิ้นไหน ที่ซื้อซ้ำได้อีก ซึ่งมี Flow การทำงาน ดังนี้

In-app Billing Version 3 Consumption Flow

ซึ่งจะมีขั้นตอนโดยสรุป คือ

  • isBillingSupported() – ตรวจสอบว่าอุปกรณ์รองรับรึไม่
  • getPurchases() – ตรวจสอบรายการสินค้าที่ User เป็นเจ้าของ โดยดึงข้อมูลจาก Google
  • consumePurchase() – ส่งคำสั่งไปยัง Google เพื่อบอกว่า สินค้าที่ซื้อมานั้น เป็นแบบสิ้นเปลือง

สำหรับส่วนของ Method isBillingSupported() และ getPurchases() นั้น ก็คือ Flow เดียวกันกับ  In-app Purchase Flow นั่นเอง ส่วนที่เพิ่มเข้ามาคือส่วนของ Method consumePurchase()

เราจะเขียนคำสั่งเรียกใช้ Method consumePurchase() สำหรับการกดปุ่ม btnConsume กัน

จากคำสั่งด้านบน จะใช้ Method consumePurchase() ในการแจ้งกลับไปทาง Google ว่าเป็นชนิดสิ้นเปลือง โดยมีรายละเอียดการใช้งานดังนี้

Parameters:

int apiVersion คือ Version ของ Google Play Billing Library ปัจจุบันกำหนดค่าเป็น 3

String packageName คือชื่อ Package ของเรา สามารถกำหนดได้ด้วย Method getPackageName()

String purchaseToken คือข้อมูลที่ได้จากการซื้อ ซึ่งใช้ในอ้างอิงถึง Product ที่ซื้อแล้ว แล้วจะแจ้งกลับไปทาง Google ว่าเป็นชนิดสิ้นเปลือง เพื่อที่จะสามารถซื้อซ้ำได้อีก

Returns:

Return value เป็นชนิด int กรณีที่ Success จะได้ค่าเป็น 0

Exceptions:

คำสั่งนี้ สามารถ Throw Exception เป็นชนิด RemoteException ได้ จึงจำเป็นต้องเขียน Try-Catch ครอบไว้เพื่อดักจับ

ก็ครบถ้วนแล้วนะครับ ตอนแรกตั้งใจจะเขียนให้จบใน 1-2 วัน แต่ปาเข้าไป 5 วัน เพราะเขียนไปด้วย ลองไปด้วย ทำงานอื่นไปด้วย ใครมีข้อสงสัยตรงไหน สอบถามได้นะครับ และถ้ามีจุดไหนผิดพลาด ขาดตกบกพร่อง หรือต้องการเสริมในจุดใด ก็แจ้งมาได้เลยครับ

สำหรับ Source code ในบทความนี้ สามารถ Download ได้ที่ Link ด้านล่างนี้เลยครับ

Download MainActivity.java

หรือจะลองโหลด App ที่ Compile แล้ว ได้ที่ Google Play Store

Ref. In-app Billing Version 3


ในตอนต่อไป Google Play In-app Billing สำหรับ Android : Part 3 การนำ Utility มาใช้ในการเขียนคำสั่ง ตามไปอ่านกันได้เลยครับ

3 Comments


  1. เราสามารถทดสอบผ่าน emulators เช่น Genymotion โดยไม่อัพโหลด APK ไปที่ Android Developer ได้หรือไม่คะ เนื่องจากทดสอบผ่าน emulators เมื่อกดปุ่ม Buy ค่า response (response = skuDetails.getInt(“RESPONSE_CODE”)) มีค่าเท่ากับ 0 แต่เมื่อ Log ค่าของ DETAILS_LIST ( ArrayList responseList = skuDetails.getStringArrayList(“DETAILS_LIST”)) ค่าที่ได้เป็น [] โดยการทดสอบใช้ android.test.purchased เป็น product id ค่ะ

    Reply
    1. gplus-profile-picture

      ถ้า Genymotion ต้องติดตั้ง Google Play Services ลงไปด้วยครับ แนะนำให้ทดสอบกับ Device จริงมากกว่า

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.