You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Firebase Functions结合Purchases.products:get验证应用内购买求助

Validate Android Purchases with Firebase Functions & Google Play Publisher API

Got it, let's walk through how to set up purchase validation using Firebase Functions and the Purchases.products:get endpoint, covering authorization scopes, authentication setup, and request building step by step.

1. Required Authorization Scope

The only scope you need to access purchase data via the Google Play API is:
https://www.googleapis.com/auth/androidpublisher
This grants your service account permission to read and manage your Google Play Developer account's purchase records and related resources.

2. Set Up Service Account Authentication

First, you need a service account key to authenticate your Firebase Function with the Google Play API:

  • Head to your Google Play Console > Settings > Developer account > API access
  • Create a new service account, then download the JSON key file linked to it
  • Store this key in your Firebase Functions config using the CLI:
    firebase functions:config:set androidpublisher.service_account="$(cat path/to/your-service-account-key.json)"
    

3. Complete Firebase Functions Code

Here's the full, working implementation with explanations for each part:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const { google } = require('googleapis');

admin.initializeApp(functions.config().firebase);

// Helper to initialize authenticated Android Publisher client
const getPublisherClient = async () => {
  // Pull service account config from Firebase Functions environment
  const serviceAccount = JSON.parse(functions.config().androidpublisher.service_account);
  
  // Create JWT auth with the required scope
  const auth = new google.auth.JWT(
    serviceAccount.client_email,
    null,
    serviceAccount.private_key,
    ['https://www.googleapis.com/auth/androidpublisher'],
    null
  );
  
  // Authorize the client
  await auth.authorize();
  // Use API v3 (v2 is deprecated)
  return google.androidpublisher({ version: 'v3', auth });
};

exports.validatePurchases = functions.https.onCall(async (data, context) => {
  // Optional but recommended: Ensure the user is authenticated
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'You must be logged in to validate purchases.');
  }

  // Extract required parameters from client request
  const { packageName, productId, purchaseToken } = data;
  
  if (!packageName || !productId || !purchaseToken) {
    throw new functions.https.HttpsError('invalid-argument', 'Missing required parameters: packageName, productId, or purchaseToken.');
  }

  try {
    const publisher = await getPublisherClient();
    
    // Call the Purchases.products:get endpoint
    const purchaseResponse = await publisher.purchases.products.get({
      packageName: packageName,
      productId: productId,
      token: purchaseToken // Note: the parameter name here is "token", not "purchaseToken"
    });

    const purchaseData = purchaseResponse.data;
    
    // Validate the purchase state (0 = Purchased, 1 = Canceled, 2 = Pending)
    if (purchaseData.purchaseState === 0) {
      // Purchase is valid — you can save this to Firestore, unlock features, etc.
      await admin.firestore().collection('user_purchases').doc(context.auth.uid).set({
        [productId]: {
          purchaseToken: purchaseToken,
          purchaseTime: purchaseData.purchaseTimeMillis,
          expiryTime: purchaseData.expiryTimeMillis || null,
          lastValidated: admin.firestore.FieldValue.serverTimestamp()
        }
      }, { merge: true });

      return { 
        success: true, 
        message: 'Purchase validated successfully.',
        purchaseDetails: {
          productId: productId,
          purchaseTime: purchaseData.purchaseTimeMillis
        }
      };
    } else {
      throw new functions.https.HttpsError('failed-precondition', 'This purchase is no longer valid (canceled or pending).');
    }
  } catch (error) {
    console.error('Validation error:', error);
    // Handle specific API errors (e.g., invalid token, expired purchase)
    if (error.code === 404) {
      throw new functions.https.HttpsError('not-found', 'Invalid purchase token or product ID.');
    }
    throw new functions.https.HttpsError('internal', 'Failed to validate purchase. Please try again later.');
  }
});

Key Details to Note:

  • We use API v3 instead of v2 because v2 is deprecated and no longer receives updates.
  • The getPublisherClient function handles authentication reuse, so you don't re-authenticate on every request.
  • The endpoint expects token (not purchaseToken) as the parameter name for the purchase token — easy to miss!
  • Added error handling for common issues like invalid tokens, unauthenticated users, and missing parameters.

4. Testing the Function

You can test this using the Firebase Functions shell, or call it directly from your Android app using Firebase Callable Functions. Just pass the packageName, productId, and purchaseToken you receive from the Google Play Billing flow on the client side.

内容的提问来源于stack exchange,提问作者Guanaco Devs

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:31:41