[secret sauce] มาทำระบบ receipt verification in-app purchase กันเถอะ Part II (android)

Mr.Ess
te<h @TDG
Published in
4 min readAug 1, 2021

สำหรับท่านที่ยังไม่เคยอ่าน Part I ไปจ๊ะ

ในบทความที่แล้วเราเกริ่นถึง in-app และระบบของมันอย่างคร่าว ๆ แล้ว คราวนี้เรามาต่อกันที่ android บ้างครับ

Android

flow ของระบบและการทำงานจะเหมือนเดิมทุกอย่างเราจึงยังคงใช้ service ตัวเดิมในการ verify แต่เราจะเพิ่ม ความสามารถของ verify android เข้าไปแทน

ก่อนอื่นเรามาดู api ที่ต้องใช้งานก่อน

API get Products

https://androidpublisher.googleapis.comandroidpublisher/v3/applications/{PackageName}/purchases/products/{ProductID}/tokens/{PurchaseToken}

API get Subscription

https://androidpublisher.googleapis.comandroidpublisher/v3/applications/{PackageName}/purchases/subscriptions/{ProductID}/tokens/{PurchaseToken}

จาก Endpoint ข้างต้นเราจะเห็นว่าต้องการข้อมูล productId purchaseToken และ packageName

productId, purchaseToken และ packageName หาได้จาก Receipt ที่ client ส่งมาหน้าตาจะเป็นแบบนี้ (key : “json”, value: string receipt)

{
"json":
"{\\\"orderId\\\":\\\"GPA.xxxx-xxxx-xxxx-xxxxx\\\",\\\"packageName\\\":\\\"com.xxx.xxx\\\",\\\"productId\\\":\\\"xxx.xxx.xx\\\",// \\\"purchaseTime\\\":1607721533824,\\\"purchaseState\\\":0,\\\"purchaseToken\\\":\\\"xxxx\\\",// \\\"acknowledged\\\":false}\",\"signature\":\"xxxxx\",\"skuDetails\":\"{\\\"productId\\\":\\\"xxx.xxx.xx\\\",// \\\"type\\\":\\\"inapp\\\",\\\"price\\\":\\\"\\u0e3f29.00\\\",\\\"price_amount_micros\\\":29000000,// \\\"price_currency_code\\\":\\\"THB\\\",\\\"title\\\":\\\"xxx\\\",\\\"description\\\":\\\"xxxxx\\\",// \\\"skuDetailsToken\\\":\\\"AEuhp4IhWdExxxxxxxxxxx\\\"}\"
}

เพื่อให้ง่ายต่อการดู แปลงเป็น struct ในภาษา go ได้ดังนี้

type ReceiptGoogle struct {
OrderID string `json:"orderId"`
PackageName string `json:"packageName"`
ProductID string `json:"productId"`
PurchaseState int `json:"purchaseState"`
PurchaseTime int64 `json:"purchaseTime"`
PurchaseToken string `json:"purchaseToken"`
}

คราวนี้เราก็จะได้ param ไป post api แล้ว

แต่! api ข้างต้น requires authenticated

Authenticated Oauth2

เราจะต้องแลก AccessToken มาเพื่อใช้ใน api ต่าง ๆ ของ billing google

เรามาดูวิธีแลก accesstoken กันครับ

Setup

เราจะต้องใช้ client_email และ private_key เพื่อจะเอา AccessToken

เราจะต้องมี. Google Service Account ก่อนครับ

Ref: https://developers.google.com/android-publisher/getting_started#using_a_service_account

สร้าง service account

  1. เข้าไปที่ service account โปรเจ็ค “Google Play Console Developer" (หากไม่มี project ให้สร้างจาก API access ใน Google Play Console)
  2. + Create service account.
  3. ใส่ name, description > create and continue
  4. ข้อ (2) Grant role Action Viewer

ข้อ (3) ข้ามได้เลยครับ กด DONE

4. หลังจากสร้าง account แล้วให้เราเข้าไปที่ account ที่เราสร้างเมื่อกี้
> Add Key > JSON > Create

** ส่วนนี้เราจะต้องเก็บไฟล์ไว้ให้ดีและปลอดภัยครับ ไม่สามารถโหลดซ้ำได้ ต้องสร้างใหม่เท่านั้น

สร้าง key จาก service account

ตัวอย่างของ file json ที่ได้มา สิ่งที่เราต้องการคือ client_email และ private_key นั้นเองงง

json key จาก service account

ยัง ยังไม่จบเราจะต้อง Grant permission ให้ accounts นี้ก่อนครับ

5. ไปที่ API access ของ Google Play Console เราจะเจอ Service accounts ที่เราสร้างมาเมื่อกี้ กด Grant access

Grant access ให้ service account

** Account permission

  • Role ที่ต้องการ คือ View app information ..(Visibility) / View financial data และ Manage orders and subscriptions

** App permission

  • Add app ที่ต้องการให้ permission ครับ

เรียบร้อย ! เราก็จะได้ Service Account ที่สามารถนำไปแลก AccessToken ที่ต้องการได้แล้ว

ถึงเวลา Get Access Token

วิธีการก็จะมีหลากหลายแต่วิธีที่เลือกมาวันนี้คือ ใช้ lib oauth2 ของ google ครับ

"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"

ใส่ Scopes : https://www.googleapis.com/auth/androidpublisher

นี้คือตัวอย่างการใช้งาน lib ในการ get access token ครับ โดยที่ lib ตัวนี้ จะ cached token ให้ ถ้าไม่ valid จะ new token ให้ใหม่เลยด้วยครับ

Note: clientEmail , privateKey จากไฟล์ json ตอนเราสร้าง service account ครับ

หลักจากได้ AccessToken สุดท้ายหน้าตาของ request ที่ต้องการก็จะเป็นแบบนี้

ถ้า success response จะได้ ประมาณนี้

{
"purchaseTimeMillis": "1621956153498",
"purchaseState": 0,
"consumptionState": 1,
"developerPayload": "",
"orderId": "GPA.3369-2294-3100-xxxx",
"purchaseType": 0,
"acknowledgementState": 1,
"kind": "androidpublisher#productPurchase",
"regionCode": "TH"
}

เพิ่มเติมได้ที่ Purchase Product

response ของ subscription จะเป็นประมาณนี้

{
"startTimeMillis": "1623061338874",
"expiryTimeMillis": "1623061756809",
"autoRenewing": true,
"priceCurrencyCode": "THB",
"priceAmountMicros": "119000000",
"countryCode": "TH",
"developerPayload": "",
"paymentState": 1,
"orderId": "GPA.3329-6601-9591-xxxx",
"purchaseType": 0,
"acknowledgementState": 1,
"kind": "androidpublisher#subscriptionPurchase"
}

เพิ่มเติมได้ที่ subscription

ถ้าหาก error จะได้ response ประมาณนี้

{  "error": {   "errors": [    {     "domain": "global",     "reason": "invalid",     "message": "Invalid Value"    }   ],   "code": 400,   "message": "Invalid Value"  }

Note: PurchaseToken จาก payload ควรใช้เป็น transection ID สามารถใช้ไปอ้างอิงได้ และใช้ประโยชน์ใน api อื่น ๆ ได้อีก เช่น ใช้ในการ clawbacks voided-purchases (รายละเอียดจะมาพูดต่อใน blog เรื่อง Refund ครับ)

นี้คือ flow ทั้งหมดของการ purchase verify ครับ คราวนี้ก็ขึ้นอยู่กับการ design ระบบของเราแล้วละ หวังว่าจะช่วยให้เริ่มต้นได้ง่ายนะครับ ><

หากภาษามนุษย์ไม่เข้าใจสามารถอ่าน code ตัวอย่างได้นี้ที่ครับ

สำหรับ blog ต่อไปเราจะมาต่อกันเรื่อง subscription บ้าง ขอบคุณครับ ^^

--

--