接入你的 App

Google Play 訂閱驗證

適合透過 Google Play 銷售訂閱、自己沒有後端的 Android App。App 用 API key 把購買憑證發給閘道,閘道用你的服務帳戶去問 Google,告訴你這個訂閱現在是否有效。驗證免費。

#開始之前

  • 一個 Play Console 帳號,需要是帳號所有者或管理員,才能邀請使用者。
  • 一個能在 Google Cloud 控制台建專案的 Google 帳號(任何 Google 帳號都可以)。
  • App 的套件名稱(applicationId),例如 com.example.app;並且已在 Play Console 建好至少一個訂閱商品。
  • 操作大約 15 分鐘;之後 Google 讓權限生效最多要一天。

#1. 取得服務帳戶 JSON

這個 JSON 檔案就是服務帳戶的金鑰,閘道只用它向 Google 查詢你這個 App 的購買,不做別的。Google 稱它為「服務帳戶金鑰」,不是 OAuth 客戶端檔案(client_secret_….json)。

  1. 1

    開啟 Google Cloud 控制台,選一個專案

    console.cloud.google.com

    用頂部的專案選擇器。哪個專案都行;單獨新建一個專門用來驗證購買更清楚。

  2. 2

    啟用 Play API

    API 和服務(APIs & Services)→ 程式庫(Library)→ 搜尋 Google Play Android Developer API → 啟用(Enable)

    按完整名稱搜尋。如果按鈕顯示「管理(Manage)」,說明已經啟用了。

  3. 3

    建立服務帳戶

    IAM 與管理(IAM & Admin)→ 服務帳戶(Service accounts)→ 建立服務帳戶(Create service account)

    名稱隨意,例如 play-verify。後面「授予專案存取權限」「授予使用者存取權限」兩個選用步驟直接跳過,它不需要任何 Cloud 角色。點「完成(Done)」。

  4. 4

    下載它的 JSON 金鑰

    點開這個服務帳戶 → 金鑰(Keys)→ 新增金鑰(Add key)→ 建立新金鑰(Create new key)→ 選 JSON → 建立(Create)

    瀏覽器會下載一個 .json 檔案。請妥善保存:Google 不會再顯示第二次,拿到它的人就能查詢你 App 的購買記錄。

  5. 5

    複製服務帳戶電子郵件

    形如 play-verify@your-project.iam.gserviceaccount.com,在服務帳戶頁面能看到,檔案裡的 client_email 也是它。下一步要用。

「建立新金鑰」是灰色的或被拒絕?

你的 Google Cloud 機構用政策 iam.disableServiceAccountKeyCreation 禁止下載金鑰(新機構預設開啟)。請機構管理員在「IAM 與管理 → 機構政策(Organization policies)」裡對這個專案關閉它,或者換一個不在機構下的專案。

#2. 在 Play Console 給它開權限

  1. 1

    邀請服務帳戶

    play.google.com/console → 使用者和權限(Users and permissions)→ 邀請新使用者(Invite new users)

    「電子郵件地址」填上一步複製的服務帳戶電子郵件。服務帳戶不需要接受邀請。

  2. 2

    勾選這兩項權限

    應用程式權限(App permissions)→ 新增應用程式(Add app)→ 選你的 App

    勾選「檢視財務資料、訂單和取消調查問卷回覆(View financial data, orders, and cancellation survey responses)」和「管理訂單和訂閱(Manage orders and subscriptions)」,其他都不用勾。如果改在「帳號權限(Account permissions)」裡勾這兩項,就對你所有 App 生效。

  3. 3

    傳送邀請

    套用(Apply)→ 邀請使用者(Invite user)

    之後在「使用者和權限」裡能看到這個服務帳戶,狀態為「有效(Active)」。

為什麼要這兩項權限

閘道只用 Google 的 purchases.subscriptionsv2.get 讀取訂閱狀態,Google 要求讀取時具備財務資料和訂單權限。閘道不會確認、退款或取消任何訂單。

Google 最多要一天才讓權限生效

生效前驗證會回傳 google_play_permission_denied。在 Play Console 隨便改一下某個訂閱商品(改描述、儲存、再改回來),通常能讓新權限更快生效。

#3. 填到 API 金鑰上

  1. 1

    開啟 App 用的那個金鑰

    控制台 → API 金鑰 → 編輯

    建議給 App 單獨建一個金鑰(見下方)。

  2. 2

    填寫「Google Play 訂閱驗證」

    設定 Google Play 驗證

    「套件名稱」填 App 的 build.gradle 裡的 applicationId,必須完全一致;選擇第 1 步下載的 JSON 檔案,儲存。

  3. 3

    看儲存後的提示

    儲存前閘道會先拿這個檔案向 Google 試一次。如果 Google 還是拒絕,對話框會說明缺什麼,通常是第 1 步的 API 沒啟用或第 2 步的權限還沒生效。檔案加密儲存,之後不再顯示;要更換就重新上傳。

給 App 單獨用一個 key

Google Play 設定是按 key 存的。給 App 單獨建一個 key,只開放 App 用到的模型,並設額度上限。

#4. 在 App 裡驗證購買

購買完成後馬上驗證一次;之後每次開啟 App,用儲存的憑證再驗證一次。不要讓 App 啟動等著結果:先按上次儲存的結果顯示,驗證回傳後再更新。同一憑證 1 分鐘內直接回傳快取結果。每個 key 預設每分鐘最多驗證 120 次,目前上限顯示在 key 的 Google Play 設定裡,需要更多可以聯絡客服為這個 key 單獨調高。超過上限的請求回傳 429,不會記進請求日誌,所以要沿用上次儲存的結果。

google_play.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:in_app_purchase/in_app_purchase.dart';

const apiKey = 'sk-YOUR_KEY';

Future<Map<String, dynamic>?> checkSubscription(String purchaseToken) async {
  final res = await http.post(
    Uri.parse('https://www.aisplice.store/v1/google-play/verify'),
    headers: {
      'Authorization': 'Bearer $apiKey',
      'Content-Type': 'application/json',
      // Request signing on for this key? Then this call must be signed too:
      // ...signedHeaders('POST', '/v1/google-play/verify'),
    },
    body: jsonEncode({'purchase_token': purchaseToken}),
  );
  if (res.statusCode != 200) return null; // keep the last saved answer
  return jsonDecode(res.body) as Map<String, dynamic>;
}

// In your purchaseStream listener:
Future<void> onPurchase(PurchaseDetails purchase) async {
  if (purchase.status != PurchaseStatus.purchased &&
      purchase.status != PurchaseStatus.restored) return;
  final token = purchase.verificationData.serverVerificationData;
  final status = await checkSubscription(token);
  if (status != null && status['active'] == true) {
    await saveEntitlement(token, status); // your local storage
    unlockPaidFeatures();
  }
  if (purchase.pendingCompletePurchase) {
    await InAppPurchase.instance.completePurchase(purchase); // acknowledges on Android
  }
}

#5. 決定解鎖什麼

驗證結果App 怎麼做常見原因
active: true, in_trial: true解鎖,並按 expiry_time 顯示「試用到期時間」。試用中
active: true解鎖,有效到 expiry_time。已付費,包括已取消自動續訂但還沒到期的
subscribed: true, active: false不解鎖,顯示續訂頁面。已過期、已暫停、帳戶保留
subscribed: false不解鎖,顯示訂閱頁面。無法識別的憑證、付款還沒完成
HTTP 502 / 503沿用上次儲存的結果,稍後重試。Google 沒回應,或服務帳戶有問題

把 expiry_time 和結果一起儲存。兩次驗證之間,到這個時間之前都當作可用,這樣手機離線也能正常用。

#6. 每筆購買都要確認

未確認的購買 3 天後會被自動退款

閘道只讀取訂閱狀態,不會替你確認購買。App 必須自己呼叫 completePurchase(Flutter)或 acknowledgePurchase(Play Billing)。如果購買已經過了幾分鐘,驗證仍回傳 acknowledged: false,說明 App 還沒確認。

#7. 保護 App 裡的金鑰

放在 App 裡的 key 只要攔截流量就能看到。給這個 key 開啟請求簽名,這樣別人複製走 key 也無法單獨使用;建置時做混淆,並給 key 設額度上限。

簽名對這個 key 的所有呼叫都生效

開啟簽名後,/v1/google-play/verify 和這個 key 的其他請求一樣,必須帶 X-Timestamp、X-Nonce、X-Signature,否則回傳 401 signature_required。簽名時用它自己的路徑:signedHeaders('POST', '/v1/google-play/verify')。等會簽名的 App 版本發布後再開啟簽名。

App 請求簽名 →

#8. 檢視驗證記錄

每次驗證(包括命中快取和失敗的)都會出現在控制台 → 請求日誌裡,記為這個 key 下一筆免費的「Google Play 驗證」。可以按完整購買憑證、憑證末尾幾位,或使用者從 Google 收據郵件裡發給你的訂單編號(GPA.…)搜尋。

  • google_play_api_disabled:JSON 檔案所屬的專案沒有啟用第 1 步的 Play API。
  • google_play_permission_denied:第 2 步的邀請沒做、沒勾那兩項權限、加到了別的 App,或者還沒生效。
  • google_play_credentials_invalid:這個 JSON 金鑰已在 Google Cloud 被刪除或停用。新建一個金鑰,重新上傳到 API 金鑰上。
  • 儲存時提示「不是服務帳戶金鑰」:你選的是 OAuth 客戶端檔案。請到服務帳戶的「金鑰」分頁下載。
  • 每次都提示找不到購買:金鑰上填的套件名稱和 App 的 applicationId 不一致,或者購買憑證來自別的 App。
  • 授權測試人員的測試購買會回傳 test_purchase: true。

全部欄位和錯誤碼 →

還是沒解決?

把請求 ID 或錯誤訊息傳給我們,我們來查。