ユーザーのアプリ内購入とサブスクリプションのサーバー側検証を 推奨 として開発しようとしていますが、そのためにFirebaseFunctionsを使用したいと思います。基本的には、購入トークンを受け取り、Play Developer APIを呼び出して購入を確認し、その結果を処理するHTTPトリガー関数である必要があります。
ただし、多くのGoogle API( Play Developer API を含む)を呼び出すには、重要な認証が必要です。必要な設定を理解する方法は次のとおりです。
問題は、人間が読める形式のドキュメントやガイダンスがまったく存在しないことです。 Firebaseの入力トラフィックが無料プランに含まれていることを考えると(FirebaseFunctionsのGoogleAPIの使用を推奨していると思います)、その事実はかなり残念です。私はあちこちでいくつかの情報を見つけることができましたが、Google APIの経験が少なすぎるため(ほとんどの場合、APIキーを使用するだけで済みます)、それをまとめるのに助けが必要です。
これが私がこれまでに理解したことです:
Firebase Functionsからのアプリ内購入の確認は一般的な作業のように思われるため、これが質問も文書化もされていないことにかなり驚いています。誰かが以前にそれを成功させたことがありますか、それともFirebaseチームが答えるために介入しますか?
私はそれを自分で理解しました。また、重いクライアントライブラリを捨てて、それらのいくつかのリクエストを手動でコーディングしました。
ノート:
scope
フィールドのみが異なります。Authentication: Bearer
で直接提供できます。Playストアにリンクされているサービスアカウントの秘密鍵を含むJSONファイルを取得した後、APIを呼び出すコードは次のようになります(ニーズに合わせて調整してください)。注:request-promise
を実行するためのより良い方法としてhttp.request
を使用しました。
const functions = require('firebase-functions');
const jwt = require('jsonwebtoken');
const keyData = require('./key.json'); // Path to your JSON key file
const request = require('request-promise');
/**
* Exchanges the private key file for a temporary access token,
* which is valid for 1 hour and can be reused for multiple requests
*/
function getAccessToken(keyData) {
// Create a JSON Web Token for the Service Account linked to Play Store
const token = jwt.sign(
{ scope: 'https://www.googleapis.com/auth/androidpublisher' },
keyData.private_key,
{
algorithm: 'RS256',
expiresIn: '1h',
issuer: keyData.client_email,
subject: keyData.client_email,
audience: 'https://www.googleapis.com/oauth2/v4/token'
}
);
// Make a request to Google APIs OAuth backend to exchange it for an access token
// Returns a promise
return request.post({
uri: 'https://www.googleapis.com/oauth2/v4/token',
form: {
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion': token
},
transform: body => JSON.parse(body).access_token
});
}
/**
* Makes a GET request to given URL with the access token
*/
function makeApiRequest(url, accessToken) {
return request.get({
url: url,
auth: {
bearer: accessToken
},
transform: body => JSON.parse(body)
});
}
// Our test function
exports.testApi = functions.https.onRequest((req, res) => {
// TODO: process the request, extract parameters, authenticate the user etc
// The API url to call - edit this
const url = `https://www.googleapis.com/androidpublisher/v2/applications/${packageName}/purchases/subscriptions/${subscriptionId}/tokens/${token}`;
getAccessToken(keyData)
.then(token => {
return makeApiRequest(url, token);
})
.then(response => {
// TODO: process the response, e.g. validate the purchase, set access claims to the user etc.
res.send(response);
return;
})
.catch(err => {
res.status(500).send(err);
});
});
これら は私がフォローしたドキュメントです。
私はこれを行うための少し速い方法を見つけたと思います...または少なくとも...もっと簡単に。
スケーリングをサポートし、index.tsが制御不能になるのを防ぐために...インデックスファイルにはすべての関数とグローバルがありますが、実際のイベントはすべてハンドラーによって処理されます。メンテナンスが簡単です。
これが私のindex.tsです(私は心臓型安全性です):
//my imports so you know
import * as functions from 'firebase-functions';
import * as admin from "firebase-admin";
import { SubscriptionEventHandler } from "./subscription/subscription-event-handler";
// honestly not 100% sure this is necessary
admin.initializeApp({
credential: admin.credential.applicationDefault(),
databaseURL: 'dburl'
});
const db = admin.database();
//reference to the class that actually does the logic things
const subscriptionEventHandler = new SubscriptionEventHandler(db);
//yay events!!!
export const onSubscriptionChange = functions.pubsub.topic('subscription_status_channel').onPublish((message, context) => {
return subscriptionEventHandler.handle(message, context);
});
//aren't you happy this is succinct??? I am!
今...ショーのために!
// importing like World Market
import * as admin from "firebase-admin";
import {SubscriptionMessageEvent} from "./model/subscription-message-event";
import {androidpublisher_v3, google, oauth2_v2} from "googleapis";
import {UrlParser} from "../utils/url-parser";
import {AxiosResponse} from "axios";
import Schema$SubscriptionPurchase = androidpublisher_v3.Schema$SubscriptionPurchase;
import Androidpublisher = androidpublisher_v3.Androidpublisher;
// you have to get this from your service account... or you could guess
const key = {
"type": "service_account",
"project_id": "not going to tell you",
"private_key_id": "really not going to tell you",
"private_key": "okay... I'll tell you",
"client_email": "doesn't matter",
"client_id": "some number",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "another url"
};
//don't guess this... this is right
const androidPublisherScope = "https://www.googleapis.com/auth/androidpublisher";
// the handler
export class SubscriptionEventHandler {
private ref: admin.database.Reference;
// so you don't need to do this... I just did to log the events in the db
constructor(db: admin.database.Database) {
this.ref = db.ref('/subscriptionEvents');
}
// where the magic happens
public handle(message, context): any {
const data = JSON.parse(Buffer.from(message.data, 'base64').toString()) as SubscriptionMessageEvent;
// if subscriptionNotification is truthy then we're solid here
if (message.json.subscriptionNotification) {
// go get the the auth client but it's async... so wait
return google.auth.getClient({
scopes: androidPublisherScope,
credentials: key
}).then(auth => {
//yay! success! Build Android publisher!
const androidPublisher = new Androidpublisher({
auth: auth
});
// get the subscription details
androidPublisher.purchases.subscriptions.get({
packageName: data.packageName,
subscriptionId: data.subscriptionNotification.subscriptionId,
token: data.subscriptionNotification.purchaseToken
}).then((response: AxiosResponse<Schema$SubscriptionPurchase>) => {
//promise fulfilled... grandma would be so happy
console.log("Successfully retrieved details: " + response.data.orderId);
}).catch(err => console.error('Error during retrieval', err));
});
} else {
console.log('Test event... logging test');
return this.ref.child('/testSubscriptionEvents').Push(data);
}
}
}
役立つモデルクラスはいくつかあります。
export class SubscriptionMessageEvent {
version: string;
packageName: string;
eventTimeMillis: number;
subscriptionNotification: SubscriptionNotification;
testNotification: TestNotification;
}
export class SubscriptionNotification {
version: string;
notificationType: number;
purchaseToken: string;
subscriptionId: string;
}
それが私たちがそのことをする方法です。