Synchronizacja uprawnień użytkownika (integracja po stronie serwera)

Wydawcy korzystają z integracji po stronie serwera głównie do zarządzania czytelnikami i ich uprawnieniami. Głównie wydawcy używają UpdateReaderEntitlements, aby zaktualizować rekord Google dotyczący uprawnień identyfikatora produktu dla identyfikatora PPID.

Konfiguracja Google Cloud

Konfigurowanie funkcji łączenia subskrypcji w Google Cloud obejmuje 2 główne komponenty:

  1. Włączanie interfejsu API w danym projekcie
  2. Tworzenie konta usługi do uzyskiwania dostępu do interfejsu API

Włączanie interfejsu Subscription Linking API

Aby używać konta usługi i zarządzać uprawnieniami czytelnika, projekt Google Cloud musi mieć włączone interfejs API do łączenia subskrypcji oraz prawidłowo skonfigurowane konto usługi OAuth. Aby włączyć interfejs Subscription Linker API w projekcie, otwórz menu -> Interfejsy API i usługi -> Biblioteka i wyszukaj Subscription Linking lub przejdź bezpośrednio na tę stronę:


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

api

Rysunek 1. Otwórz Bibliotekę interfejsów API i włącz interfejs API w projekcie Google Cloud.

Tworzenie konta usługi

Konta usługi służą do udzielania dostępu aplikacji do interfejsu Subscription Linking API.

  1. Utwórz konto usługi w konsoli projektu.
  2. Utwórz dane logowania dla konta usługi i zapisz plik credentials.json w bezpiecznym miejscu, do którego aplikacja ma dostęp.
  3. Przypisz do konta usługi, które utworzyłeś/utworzyłaś, rolę uprawnień „Administrator łączenia subskrypcji”. Aby uzyskać szczegółową kontrolę nad możliwościami konta usługi, możesz przypisać odpowiednią rolę z tabeli poniżej.
Funkcja / rola Administrator połączeń subskrypcji Wyświetlający połączenia subskrypcji Wyświetlający uprawnienia połączeń subskrypcji
Uzyskiwanie uprawnień czytelników
Zdobywanie czytelników
Aktualizowanie uprawnień czytelników
Usuwanie czytników

Korzystanie z kont usługi w ramach interfejsu API łączenia subskrypcji

Aby uwierzytelniać wywołania interfejsu Subscription Linking API za pomocą kont usługi, użyj biblioteki klienta googleapis (która automatycznie obsługuje żądania access_token) lub podpisz żądania bezpośrednio za pomocą interfejsu API REST. Jeśli używasz interfejsu REST API, musisz najpierw uzyskać access_token (za pomocą biblioteki Google Auth lub za pomocą tokena JWT konta usługi), a następnie umieścić go w nagłówku Authorization.

Oba te przykłady biblioteki klientainterfejsu API REST zawierają przykładowy kod wywoływania funkcji getReader(), getReaderEntitlements(), updateReaderEntitlements()deleteReader().

Biblioteka klienta

W tej sekcji wyjaśniamy, jak używać biblioteki klienta googleapis w Node.js.

Przykładowe żądanie

W polu keyFile w konstruktorze Auth.GoogleAuth ustaw ścieżkę do klucza konta usługi. Jeśli ze względu na zasady organizacji nie możesz wyeksportować klucza konta usługi, możesz użyć metody domyślnych danych logowania do konta (ADC). Jeśli wybierzesz metodę ADC, nie musisz podawać pola keyFile, ponieważ ADC samodzielnie wyszuka dane logowania.

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/subscribe/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 Linkikng 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
 *  entitelements 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
  });
};

Interfejs API typu REST

Jeśli chcesz wywoływać punkty końcowe interfejsu API REST, możesz użyć jednej z metod, aby uzyskać wartość accessToken, która zostanie ustawiona w nagłówku Authorization.

1. Używanie biblioteki GoogleAuth

W przypadku klucza credentials możesz użyć klucza konta usługi lub domyślnych danych logowania konta (ADC). Jeśli wybierzesz metodę ADC, nie musisz podawać pola credentials, ponieważ ADC sam poszuka danych logowania.

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. Generowanie access_token za pomocą tokena JWT konta usługi

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

function getSignedJwt() {
  /*
    Either store the service account credentials string in an environmental 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;
}

Przykładowy kod wywołań interfejsu API REST z biblioteką Google Auth

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/subscribe/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
 *  entitelements 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;
}