ユーザーの利用資格の同期(サーバーサイドの統合)

パブリッシャーは主に、読者とその利用資格を管理するためにサーバーサイドの統合を使用します。パブリッシャーは UpdateReaderEntitlements を使用して、PPID のプロダクト ID 利用資格に関する Google の記録を更新します。

Google Cloud の設定

Google Cloud で定期購読のリンクを設定する作業は、主に次の 2 つに分けられます。

  1. 特定のプロジェクトに対して API を有効にする
  2. API にアクセスするためのサービス アカウントを作成する

Subscription Linking API を有効にする

サービス アカウントを使用して読者の利用資格を管理するには、Google Cloud プロジェクトで Subscription Linking API を有効にして、OAuth サービス アカウントを適切に構成する必要があります。プロジェクトの Subscription Linking API を有効にするには、メニュー -> [API とサービス] -> [ライブラリ] と移動して Subscription Linking を検索するか、ページに直接アクセスしてください。


https://console.cloud.google.com/apis/library?project=gcp_project_id

API

図 1.API ライブラリに移動し、Google Cloud プロジェクトに対して API を有効にする。

サービス アカウントを作成する

サービス アカウントは、アプリケーションから Subscription Linking API へのアクセスを許可するために使用されます。

  1. サービス アカウントを作成するには、プロジェクトの コンソールを使用します。
  2. サービス アカウントの認証情報を作成し、 アプリケーションにアクセスできる安全な場所に credentials.json ファイルを保存します。
  3. IAM ロール「定期購読リンク管理者」を、作成した サービス アカウントに付与します。サービス アカウントの機能をさらに細かく管理する場合は、以下の表から適切なロールを割り当てることができます。
機能 / ロール サブスクリプション リンク管理者 サブスクリプション リンク閲覧者 サブスクリプション リンク権限の閲覧者
読者の利用資格の取得
読者の取得
読者の利用資格の更新
読者の削除

Subscription Linking API でサービス アカウントを使用する

サービス アカウントを使用して Subscription Linking API の呼び出しを認証するには、 googleapis クライアント ライブラリaccess_token リクエストを自動的に処理します) を使用するか、REST API でリクエストに直接署名します。REST API を使用する場合は、まず access_tokenGoogle Auth ライブラリまたは サービス アカウント JWT を使用)を取得し、Authorization ヘッダーに含める必要があります。

次のクライアント ライブラリREST API の例には、getReader()getReaderEntitlements()updateReaderEntitlements()deleteReader() を呼び出す方法のサンプルコードが含まれています。

サービス アカウント キーとアプリケーションのデフォルト認証情報(ADC)

Subscription Linking API を呼び出すには、 サービス アカウント キーが必要です。 組織のポリシーによりサービス アカウント キーをエクスポートできない場合は、 アプリケーションのデフォルト認証情報(ADC) を使用できます。

gcloud auth application-default login コマンドを使用して ADC を設定するコマンドの例を次に示します。

gcloud config set project [YOUR_PROJECT_ID]
gcloud auth application-default login --impersonate-service-account [YOUR_SERVICE_ACCOUNT_NAME@xxx.iam.gserviceaccount.com]

このコマンドは、サービス アカウントの認証情報を含む JSON ファイルを作成し、ファイル システム上の既知の場所に配置します。場所はオペレーティング システムによって異なります。

  • Linux / macOS: $HOME/.config/gcloud/application_default_credentials.json
  • Windows: %APPDATA%\gcloud\application_default_credentials.json

ADC は認証情報を自動的に 検索するため、コードでキーファイルのパスを指定する必要はありません。

this.auth = new Auth.GoogleAuth({
   // keyFile: process.env.KEY_FILE, - You don't need to provide this field
    'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
  ],
  ...
});

クライアント ライブラリ

このセクションでは、Node.js で googleapis クライアント ライブラリを使用する方法について説明します。

リクエストの例

import {readerrevenuesubscriptionlinking_v1, Auth} from 'googleapis';
const subscriptionLinking = readerrevenuesubscriptionlinking_v1.Readerrevenuesubscriptionlinking;

class SubscriptionLinking {
  constructor() {
    this.auth = new Auth.GoogleAuth({
      keyFile: process.env.KEY_FILE,
      scopes: [
        'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
      ],
    })
  }

  init() {
    return new subscriptionLinking(
        {version: 'v1', auth: this.auth})
  }
}

const subscriptionLinkingApi = new SubscriptionLinking();
const client = subscriptionLinkingApi.init();

/**
 * Retrieves details for a specific reader associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {Promise<object>} A promise that resolves with the reader's details
 *  from the API.
 */
async function getReader(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.get({
    name: `publications/${publicationId}/readers/${ppid}`,
  });
}

/**
 * Updates the entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader whose
 *  entitlements are being updated.
 * @return {Promise<object>} A promise that resolves with the result of the
 *  updated entitlements object.
 */
async function updateReaderEntitlements(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  const requestBody = {
    /*
    Refer to
    https://developers.google.com/news/subscription-linking/appendix/glossary#entitlements_object
    */
    entitlements : [{
      product_id: `${publicationId}:basic`,
      subscription_token: 'abc1234',
      detail: 'This is our basic plan',
      expire_time: '2025-10-21T03:05:08.200564Z'
    }]
  };
  return await client.publications.readers.updateEntitlements({
    name: `publications/${publicationId}/readers/${ppid}/entitlements`,
    requestBody
  });
}

/**
 * Retrieves the current entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {Promise<object>} A promise that resolves with the reader's entitlements object.
 */
async function getReaderEntitlements(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.getEntitlements({
    name: `publications/${publicationId}/readers/${ppid}/entitlements`
  });
}

/**
 * Deletes a specific Subscription Linking reader record associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader to be deleted.
 * @param {boolean=} forceDelete - If true, delete the user even if their
 *  entitlements are not empty
 * @return {Promise<object>} A promise that resolves upon successful deletion
 *  with an empty object ({})
 */
async function deleteReader(ppid, forceDelete = false) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.delete({
    name: `publications/${publicationId}/readers/${ppid}`
    force: forceDelete
  });
}

REST API

REST API エンドポイントを呼び出す場合は、いずれかの方法で accessToken を取得して Authorization ヘッダーに設定できます。

1. GoogleAuth ライブラリを使用する

import { GoogleAuth } from 'google-auth-library';
import credentialJson from 'path_to_your_json_file' with { type: 'json' };

const auth = new GoogleAuth({
    credentials: credential_json,
    scopes: [
      'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
    ]
});

async function getAccessToken() {
    const accessToken = await auth.getAccessToken();
    return accessToken;
}

2. サービス アカウント JWT を使用して access_token を生成する

import fetch from 'node-fetch';
import jwt from 'jsonwebtoken';

function getSignedJwt() {
  /*
    Either store the service account credentials string in an environment variable
    Or implement logic to fetch it.
  */
  const key_file = process.env.CREDENTIALS_STRING

  const issueDate = new Date();
  const expireMinutes = 60;
  const offsetInSeconds = issueDate.getTimezoneOffset() * 60000;
  const expireDate = new Date(issueDate.getTime() + (expireMinutes * 60000));
  const iat = Math.floor((issueDate.getTime() + offsetInSeconds) / 1000);
  const exp = Math.floor((expireDate.getTime() + offsetInSeconds) / 1000);

  const token = {
    iss: key_file.client_email,
    iat,
    exp,
    aud: 'https://oauth2.googleapis.com/token',
    scope:'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage',
  }
  return jwt.sign(token, key_file.private_key, {
    algorithm: 'RS256',
    keyid: key_file.private_key_id,
  })
}

async function getAccessToken(signedJwt) {
  let body = new URLSearchParams();
  body.set('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer');
  body.set('assertion', signedJwt);
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body
  })
  const accessResponse = await response.json();
  return accessResponse.access_token;
}

Google Auth ライブラリを使用した REST API 呼び出しのサンプルコード

import { GoogleAuth } from 'google-auth-library';
import fetch from 'node-fetch'
import credentialJson from 'path_to_your_json_file' with { type: 'json' };

const BASE_SUBSCRIPTION_LINKING_API_URL='https://readerrevenuesubscriptionlinking.googleapis.com/v1';
const publicationId = process.env.PUBLICATION_ID

const auth = new GoogleAuth({
    credentials: credentialJson,
    scopes: [
      'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
    ]
});

async function getAccessToken() {
    const accessToken = await auth.getAccessToken();
    return accessToken;
}

/**
 * Retrieves details for a specific reader associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {object} reader json for the given ppid
 */
async function getReader(ppid) {
  const endpoint = `${BASE_SUBSCRIPTION_LINKING_API_URL}/publications/${publicationId}/readers/${ppid}`;
  const accessToken = await getAccessToken();
  const response = await fetch(endpoint, {
     method: 'GET',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   });
  const reader = await response.json();
  return reader;
}

/**
 * Updates the entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {object} the updated entitlements object in json.
 */
async function updateReaderEntitlements(ppid) {
  const endpoint = `${BASE_SUBSCRIPTION_LINKING_API_URL}/publications/${publicationId}/readers/${ppid}/entitlements`;
  const requestBody = {
    /*
    Refer to
    https://developers.google.com/news/subscription-linking/appendix/glossary#entitlements_object
    */
    entitlements : [{
      product_id: `${publicationId}:basic`,
      subscription_token: 'abc1234',
      detail: 'This is our basic plan',
      expire_time: '2025-10-21T03:05:08.200564Z'
    }]
  };
  const response = await fetch(endpoint, {
     method: 'PATCH',
     headers: {
       Authorization: `Bearer ${accessToken}`,
       'Content-Type': 'application/json',
     },
     body: JSON.stringify(requestBody)
   })
  const updatedEntitlements = await response.json();
  return updatedEntitlements;
}

/**
 * Retrieves the current entitlements for a specific reader.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @return {object} the reader's entitlements object in json.
 */
async function getReaderEntitlements(ppid) {
  const endpoint = `${BASE_SUBSCRIPTION_LINKING_API_URL}/publications/${publicationId}/readers/${ppid}/entitlements`;
  const accessToken = await getAccessToken();
  const response = await fetch(endpoint, {
     method: 'GET',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   });
  const entitlements = await response.json();
  return entitlements;
}

/**
 * Deletes a specific Subscription Linkikng reader record associated with the publication.
 * @async
 * @param {string} ppid - The Publisher Provided ID (ppid) for the reader.
 * @param {boolean=} forceDelete - If true, delete the user even if their
 *  entitlements are not empty
 * @return {object} returns an empty object ({}) if the delete operation is successful
 */
async function deleteReader(ppid, forceDelete = false) {
  const endpoint = `${BASE_SUBSCRIPTION_LINKING_API_URL}/publications/${publicationId}/readers/${ppid}?force=${forceDelete}`;
  const response = await fetch(endpoint, {
     method: 'DELETE',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     }
   });
  const result = await response.json();
  return result;
}