Đồng bộ hoá Quyền của người dùng (Tích hợp phía máy chủ)

Nhà xuất bản chủ yếu sử dụng tính năng tích hợp phía máy chủ để quản lý độc giả và quyền của họ. Chủ yếu, nhà xuất bản sử dụng UpdateReaderEntitlements để cập nhật bản ghi của Google về quyền sử dụng Mã sản phẩm cho một PPID.

Thiết lập Google Cloud

Việc định cấu hình tính năng Liên kết gói thuê bao trong Google Cloud bao gồm hai thành phần chính:

  1. Bật API cho một dự án cụ thể
  2. Tạo tài khoản dịch vụ để truy cập vào API

Bật Subscription Linking API

Để sử dụng tài khoản dịch vụ và quản lý quyền của người đọc, dự án Google Cloud phải bật cả API Liên kết gói thuê bao và tài khoản dịch vụ OAuth được định cấu hình đúng cách. Để bật Subscription Linking API cho một dự án, hãy chuyển từ trình đơn -> API và Dịch vụ -> Thư viện rồi tìm Subscription Linking hoặc truy cập trực tiếp vào trang:


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

api

Hình 1. Chuyển đến Thư viện API và bật API cho một dự án Google Cloud.

Tạo tài khoản dịch vụ

Tài khoản dịch vụ được dùng để cho phép ứng dụng của bạn truy cập vào API Liên kết gói thuê bao.

  1. Tạo tài khoản dịch vụ trong bảng điều khiển của dự án.
  2. Tạo thông tin xác thực cho tài khoản dịch vụ và lưu trữ tệp credentials.json ở một vị trí an toàn mà ứng dụng của bạn có thể truy cập.
  3. Cấp vai trò IAM "Quản trị viên liên kết gói thuê bao" cho tài khoản dịch vụ mà bạn đã tạo. Để kiểm soát chi tiết các chức năng của tài khoản dịch vụ, bạn có thể chỉ định vai trò thích hợp trong bảng sau.
Chức năng / Vai trò Quản trị viên liên kết gói thuê bao Trình xem liên kết gói thuê bao Trình xem quyền trong tính năng Liên kết gói thuê bao
Nhận quyền của người đọc
Thu hút người đọc
Cập nhật quyền của người đọc
Xoá trình đọc

Sử dụng tài khoản dịch vụ với API Liên kết gói thuê bao

Để xác thực các lệnh gọi đến Subscription Linking API bằng tài khoản dịch vụ, hãy sử dụng thư viện ứng dụng googleapis (tự động xử lý các yêu cầu access_token) hoặc ký trực tiếp các yêu cầu bằng API REST. Nếu sử dụng API REST, trước tiên, bạn phải lấy access_token (thông qua thư viện Xác thực của Google hoặc sử dụng JWT tài khoản dịch vụ) rồi đưa vào tiêu đề Authorization

Cả ví dụ về thư viện ứng dụngAPI REST sau đây đều có mã mẫu về cách gọi getReader(), getReaderEntitlements(), updateReaderEntitlements()deleteReader().

Thư viện ứng dụng

Phần này giải thích cách sử dụng thư viện ứng dụng googleapis trong Node.js.

Yêu cầu mẫu

Đối với trường keyFile trong hàm khởi tạo Auth.GoogleAuth, hãy đặt đường dẫn đến khoá tài khoản dịch vụ. Nếu không thể xuất khoá tài khoản dịch vụ do chính sách của tổ chức, bạn có thể sử dụng phương thức thông tin xác thực mặc định của tài khoản (ADC). Nếu sử dụng phương thức ADC, bạn không cần cung cấp trường keyFile vì ADC sẽ tự tìm thông tin xác thực.

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
  });
};

API REST

Nếu muốn gọi các điểm cuối API REST, bạn có thể sử dụng một trong hai phương thức để lấy accessToken nhằm đặt thành tiêu đề Authorization.

1. Sử dụng thư viện GoogleAuth

Đối với khoá credentials, bạn có thể sử dụng khoá tài khoản dịch vụ hoặc thông tin xác thực mặc định của tài khoản (ADC). Nếu sử dụng phương thức ADC, bạn không cần cung cấp trường credentials vì ADC sẽ tự tìm thông tin xác thực.

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. Tạo access_token bằng JWT Tài khoản dịch vụ

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;
}

Mã mẫu cho các lệnh gọi API REST bằng thư viện 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_LINIING_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_LINIING_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_LINIING_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_LINIING_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_LINIING_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;
}