在 Flutter App 中整合 Google Play In-App-Billing

閱讀時間約 13 分鐘


raw-image

source: nbcnews.com

想在 App 上賺錢有兩種方式,一種是讓 App 本身是付費下載,另一種則是應用程式內購買。如果選擇應用程式內購買的話,在 iOS 上需要整合 Apple In App Purchase,而在 Google Play 上的話,則是需要整合 Google In App Billing,以下簡稱 Google IAB,本篇主要紀錄一下整合 Google IAB 的過程。

後台設定

在開始整合 Google IAB 之前,需要先準備一個 Google 開發者帳號。當準備好之後,就能使用該開發者帳號登入 Google Play Console ,進入到應用程式內產品的的選單中,點選建立產品,然後就可以新增可購買項目。設定完成後並啟用產品,表示這個產品已經上架販售。

raw-image

當購買項目設定完成後,重新整理該頁面就會看到剛剛設定好的購買項目的列表。其中的產品 ID 是表示該購買項目的唯一 ID,稍後也會在程式碼使用中,用以獲取購買項目的詳細資訊。

raw-image

值得注意的是,在設定購買項目之前,需要先上傳包含內購權限的 apk 到 Google Play 上。沒有完成這一步,在設定購買項目的頁面出會發現下面這個訊息,Google Play 提醒你需要上傳有內購權限的 apk。若是 App 還沒有準備要上架,可以先上傳 apk 到內部測試群組。

raw-image

除了自己手動到 Google Play 的後台去設定產品,開發人員也可以透過 Google In App Products API 對產品列表做新增修改刪除,方便開發者客製化自己的後台產品上架流程。

整合 Google Play In App Billing

安裝套件

在 pub.dev 上,與應用程式內購買的相關的套件有不少,我們選擇使用 Flutter 官方提供的套件。這個套件除了可以用來整合 Google IAB,也同時包含了 Apple IAP。

in_app_purchase 在 Google IAB 的實作是基於 Android BillingClient。所以在整合的時候,可以適時參考 整合 Android BillingClient 的文章,有助於更了解 Google IAB 的核心流程。

啟用 PendingPurchase

在 2019 年的 Google I/O 上,Google 宣布 Google Play 將提供 Pending Transactions 功能,讓使用者擁有更多樣的付款方式。在整合 Google IAB 時,我們需要加上 enablePendingPurchase 的程式碼,讓 App 支援 Pending Transactions 功能。

void main() {
if (defaultTargetPlatform == TargetPlatform.android) {
InAppPurchaseAndroidPlatformAddition.enablePendingPurchases();
}
runApp(MyApp());
}

確保與 Google Play 正常連線

App 透過這個 API 來確保與 Google Play 正常連線。當手機上沒有 Google Play 或者 Google Play 尚未登入之類的情況,這個 API 回傳的就會是 false。而回傳 false 時,App 也就無法正常購買產品,甚至連產品列表都無法獲得。

await InAppPurchase.instance.isAvailable();

獲取產品列表

透過這個 API,App 可以獲取產品的詳細資訊,像是價格與貨幣資訊 …等。其中需要把產品 ID 列表做為參數傳給這個 API,這個產品 ID 列表需要與 Google Play Console 中設定的一致。取得的回傳值 ProductDetails 也會是稍後購買產品 API 的參數。

var productIds = ["prodcut_1", "product_2"];
ProductDetailsResponse response =
await InAppPurchase.instance.queryProductDetails(productIds);
if (response.notFoundIDs.isNotEmpty) {
// Handle the error.
}
List<ProductDetails> products = response.productDetails;

購買產品

in_app_purchase 套件提供兩個 API 讓開發者實現購買功能: buyConsumable 和 buyNonConsumable。如何選擇要使用哪個方法,則是需要開發人員根據產品販售策略來決定。以下是一些比較簡單的原則提供大家參考。

  • 當產品會重複販售給使用者多次,使用 buyConsumable。
  • 當產品只能販售給每個使用者一次,使用 buyNonConsumable。
final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);
if (_isConsumable(productDetails)) {
InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
} else {
InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
}

除此之外,這兩個方法在實作購買流程上會有些許不同,如果是使用 buyNonConsumable 購買產品,購買成功後需要呼叫 completePurchase,讓 Google Play 知道此次購買已經完成。

await InAppPurchase.instance.completePurchase(purchaseDetails);

若是使用 buyNonConsumable 後未呼叫 completePurchase,則會造成 Google Play 認為此筆購買未完成,將導致該筆付款在 3 天後退還給使用者。

等待完成購買流程

當呼叫了購買 API 之後,就需要透過 purchaseStream 來監聽購買的狀態,無論是使用者取消購買、購買成功、付款失敗 …等,都會透過 purchaseStream 來通知 App 購買的狀態。

_subscription = InAppPurchase.instance.purchaseStream.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}

而我們能從 PurchaseDetail 中的 status 確認購買狀態並決定 App 相應的行為,例如:處於 PurchaseStatus.pending 時要顯示等待畫面、處於 PurchaseStatus.purchased 時顯示購買成功 …等,開發者需要依照實際需求實作。

void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
// handle pending
}

if (purchaseDetails.status == PurchaseStatus.error) {
// handle error
}

if(purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
// handle purchased
}
});
}

當購買成功時,Google 在整合 BillingClient 的文章中有提到,建議把購買成功時的 Purchase Token 送到後端驗證,後端能夠透過 Google Purchase API 驗證訂單是否合法,在合法的狀況才把產品交付給使用者。

if (purchaseDetails.status == PurchaseStatus.purchased || 
purchaseDetails.status == PurchaseStatus.restored) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
_deliverProduct(purchaseDetails);
} else {
_handleInvalidPurchase(purchaseDetails);
}
}

以上講解的是從 App 端接收訂單資訊並處理,可同時處理當下就購買成功或者 Pending 的訂單,然而 Google Play 也提供了另一種 Pending 處理訂單資訊方式。Google Play 可以透過 Google Pub / Sub 傳送訂單資訊給後端,讓後端接收訂單資訊並驗證訂單,App 則可以省去使用者等待的時間,讓使用者可以繼續體驗 App呼叫,把驗證工作交給後端,更詳細的實作可以參考這邊

測試購買流程

當寫完程式碼之後,我們理所當然地需要在測試機裝 Debug 版本測試一下。為了讓 Google Play 允許 Debug 版本也可以測試 Google IAB,我們必須在 Google Play 後台設定授權測試,在設定中的帳號使用測試信用卡付費,能夠避免實際付費。透過這個方法,我們就不必頻繁的上傳 App 到 Google Play 上測試,能夠增加開發速度。

raw-image

當授權測試帳號設定完成後,需要先到測試機上的 Google Play 登入該帳號。測試付款流程時,就會看到付款選項中出現 Test Card 的選單。點開之後有更多選項,可以用來測試不同的情境,例如:Slow test card 的選項就可以用來測試 Pending 的情況。

最後要提醒的是,如果使用模擬器來測試,請確保模擬器上有 Google Play 且登入想要用來測試的帳號,這樣才能夠正常測試。否則在確保與 Google Play 連線的步驟會不成功,導致後續流程無法進行。開發人員可以在 AVD Manager 中確認模擬器是否含有 Google Play。

結論

本篇紀錄了在 Flutter 上整合 Google IAB 的過程,從商店設定、簡化的流程處理到如何測試付費。其中在流程處理的部分,實際應用上可能會有許多場景需要特別處理,例如:不同支付方式的 Pending 處理、退款的後續處理流程、例外狀況的處理 …等,需要開發者根據實際情況中思考如何處理。

參考

分享各種 Flutter 開發技巧
留言0
查看全部
發表第一個留言支持創作者!
本文探討如何使用 Flutter 的 Widget 測試來驗證應用程式的 Routing 功能,確保重構後仍然正常運作。我們將通過具體的範例,從設定 MockNavigatorObserver 到驗證 Routing 參數,提供清晰步驟與建議,以提高測試的可讀性和效能,是開發人員必備的測試技巧。
這篇文章介紹如何在多臺 MacBook 上同步開發工具與設定,以提高開發效率。文章重點在於如何同步 IntelliJ、IdeaVim 和 Alfred 配置,並解決因設定不同而影響開發效率的問題。透過簡單的步驟,開發者可以在不同設備上無縫運作,持續專注於開發工作,而不必因為熱鍵或工具失效而浪費時間。
本文探討在客戶端程式開發中,如何有效處理根據後端不同資料型態變化的畫面顯示。透過列舉 Shortgun Surgery 問題及其對代碼維護的影響,分析各種設計模式,包括轉接器模式和策略模式,來改善資料的處理方式。最終提出根據具體情況選擇合適解法的重要性,以確保開發效率與代碼可維護性。
Flutter 習慣在最頂層的 MaterialApp 或 CupertinoApp 中統一定義整個 app 的路由管理。當我們把所有頁面的路由管理都放在最頂層時,就會讓它變得很長,不容易維護。或許應該適時思考,是否某些頁面的路由不應該被管理在最頂層。
本文探討如何使用 Flutter 的 Widget 測試來驗證應用程式的 Routing 功能,確保重構後仍然正常運作。我們將通過具體的範例,從設定 MockNavigatorObserver 到驗證 Routing 參數,提供清晰步驟與建議,以提高測試的可讀性和效能,是開發人員必備的測試技巧。
這篇文章介紹如何在多臺 MacBook 上同步開發工具與設定,以提高開發效率。文章重點在於如何同步 IntelliJ、IdeaVim 和 Alfred 配置,並解決因設定不同而影響開發效率的問題。透過簡單的步驟,開發者可以在不同設備上無縫運作,持續專注於開發工作,而不必因為熱鍵或工具失效而浪費時間。
本文探討在客戶端程式開發中,如何有效處理根據後端不同資料型態變化的畫面顯示。透過列舉 Shortgun Surgery 問題及其對代碼維護的影響,分析各種設計模式,包括轉接器模式和策略模式,來改善資料的處理方式。最終提出根據具體情況選擇合適解法的重要性,以確保開發效率與代碼可維護性。
Flutter 習慣在最頂層的 MaterialApp 或 CupertinoApp 中統一定義整個 app 的路由管理。當我們把所有頁面的路由管理都放在最頂層時,就會讓它變得很長,不容易維護。或許應該適時思考,是否某些頁面的路由不應該被管理在最頂層。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
本章節旨在教導讀者如何在 Kotlin 中引入套件。將會涵蓋如何引用第三方套件,如何引用自定義模組,以及如何創建和引用自定義套件。此外,還會列出一些常用的套件,以供讀者參考。
Thumbnail
# 簡介 身為一位專注於 Vue.js 的前端開發者,這是我第一次嘗試構建 Flutter 網頁應用。讓我們開始吧! ## 第一次嘗試 ### 第一步:創建一個 Flutter 應用 首先,通過運行以下命令來創建一個新的 Flutter 項目: ```sh flutter
Thumbnail
雲端已經成為App開發的核心,而Amazon的AWS(Amazon Web Services是開發者常用的平台,可以幫助開發者建立、整合和擴展App。
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
本課程將學習如何在 Activity 中註冊監聽 Fragment 點擊事件。
本課程學習如何添加 Room 資料庫相關依賴庫配置。
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
本章節旨在教導讀者如何在 Kotlin 中引入套件。將會涵蓋如何引用第三方套件,如何引用自定義模組,以及如何創建和引用自定義套件。此外,還會列出一些常用的套件,以供讀者參考。
Thumbnail
# 簡介 身為一位專注於 Vue.js 的前端開發者,這是我第一次嘗試構建 Flutter 網頁應用。讓我們開始吧! ## 第一次嘗試 ### 第一步:創建一個 Flutter 應用 首先,通過運行以下命令來創建一個新的 Flutter 項目: ```sh flutter
Thumbnail
雲端已經成為App開發的核心,而Amazon的AWS(Amazon Web Services是開發者常用的平台,可以幫助開發者建立、整合和擴展App。
Thumbnail
Request內容 package main import ( "fmt" "log" "net/http" "strings" ) func request(w http.ResponseWriter, r *http.Request) { //這些資訊是輸出到伺服器端的列印訊息
本課程將學習如何在 Activity 中註冊監聽 Fragment 點擊事件。
本課程學習如何添加 Room 資料庫相關依賴庫配置。