使用Firebase Functions结合Purchases.products:get验证应用内购买求助
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
getPublisherClientfunction handles authentication reuse, so you don't re-authenticate on every request. - The endpoint expects
token(notpurchaseToken) 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

