همگام سازی حقوق کاربر (ادغام سمت سرور)

ناشران عمدتاً از یکپارچه‌سازی سمت سرور برای مدیریت خوانندگان و حقوق آنها استفاده می‌کنند. ناشران UpdateReaderEntitlements برای به‌روزرسانی رکورد گوگل از حق شناسه محصول برای PPID استفاده می‌کنند.

تنظیمات فضای ابری گوگل

پیکربندی لینک اشتراک در گوگل کلود شامل دو جزء اصلی است:

  1. فعال کردن API برای یک پروژه خاص
  2. ایجاد یک حساب کاربری سرویس برای دسترسی به API

فعال کردن API لینک اشتراک

برای استفاده از یک حساب کاربری سرویس و مدیریت حقوق خواننده، یک پروژه Google Cloud نیاز به فعال کردن API لینک اشتراک و پیکربندی صحیح یک حساب کاربری سرویس OAuth دارد. برای فعال کردن API لینک اشتراک برای یک پروژه، از منو -> APIها و خدمات -> کتابخانه بروید و Subscription Linking را جستجو کنید، یا مستقیماً از صفحه زیر بازدید کنید:


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

api

شکل ۱. پیمایش به کتابخانه API و فعال کردن API برای یک پروژه Google Cloud.

ایجاد حساب کاربری سرویس

حساب‌های سرویس برای دسترسی از برنامه شما به API لینک اشتراک استفاده می‌شوند.

  1. یک حساب کاربری سرویس در کنسول پروژه خود ایجاد کنید .
  2. برای حساب کاربری سرویس، اعتبارنامه ایجاد کنید و فایل credentials.json را در مکانی امن که برای برنامه شما قابل دسترسی باشد، ذخیره کنید.
  3. نقش IAM "مدیر پیوند اشتراک" را به حساب سرویسی که ایجاد کرده‌اید، اعطا کنید . برای کنترل دقیق‌تر قابلیت‌های حساب سرویس، می‌توانید نقش مناسب را از جدول زیر تعیین کنید.
قابلیت / نقش مدیریت لینک‌های اشتراک نمایشگر لینک اشتراک نمایشگر حق اشتراک لینک‌ها
دریافت حقوق خواننده
خوانندگان را جذب کنید
به‌روزرسانی حقوق خوانندگان
حذف خوانندگان

استفاده از یک حساب کاربری سرویس با API لینک اشتراک

برای احراز هویت فراخوانی‌های Subscription Linking API با حساب‌های سرویس، یا از کتابخانه کلاینت googleapis (که به طور خودکار درخواست‌های access_token را مدیریت می‌کند) استفاده کنید یا درخواست‌ها را مستقیماً با REST API امضا کنید. در صورت استفاده از REST API، ابتدا باید access_token را (از طریق کتابخانه Google Auth یا با استفاده از JWT حساب سرویس ) دریافت کنید و سپس آن را در هدر Authorization قرار دهید.

هر دو مثال کتابخانه کلاینت و REST API زیر، کد نمونه‌ای برای نحوه فراخوانی getReader() ، getReaderEntitlements() ، updateReaderEntitlements() و deleteReader() دارند.

کلید حساب سرویس و اعتبارنامه‌های پیش‌فرض برنامه (ADC)

برای فراخوانی API مربوط به لینک اشتراک، باید یک کلید حساب سرویس داشته باشید. اگر به دلیل سیاست‌های سازمانی خود نمی‌توانید کلید حساب سرویس را صادر کنید، می‌توانید از روش اعتبارنامه‌های پیش‌فرض برنامه (ADC) استفاده کنید.

در اینجا یک دستور نمونه برای تنظیم ADC با استفاده از دستور gcloud auth application-default login آورده شده است:

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

این دستور یک فایل JSON حاوی اطلاعات حساب کاربری سرویس ایجاد می‌کند و آن را در یک مکان شناخته‌شده در سیستم فایل شما قرار می‌دهد. این مکان به سیستم عامل شما بستگی دارد:

  • لینوکس، macOS: $HOME/.config/gcloud/application_default_credentials.json
  • ویندوز: %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'
  ],
  ...
});

کتابخانه مشتری

این بخش نحوه استفاده از کتابخانه کلاینت googleapis در Node.js را توضیح می‌دهد.

درخواست نمونه

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

API رست

اگر می‌خواهید نقاط پایانی REST API را فراخوانی کنید، می‌توانید از هر یک از روش‌های زیر برای دریافت accessToken جهت تنظیم آن در هدر Authorization استفاده کنید.

۱. از کتابخانه 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;
}

۲. با استفاده از یک حساب کاربری سرویس 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;
}

نمونه کد برای فراخوانی‌های REST API با کتابخانه 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
 *  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;
}