مزامنة أذونات المستخدمين (الدمج من جهة الخادم)

يستخدم الناشرون الدمج من جهة الخادم بشكل أساسي لإدارة القرّاء و الحقوق التي يحصلون عليها. يستخدم الناشرون UpdateReaderEntitlements بشكل أساسي لتعديل سجلّ Google الخاص بإذن استخدام معرّف المنتج (PPID).

إعداد Google Cloud

يتضمّن إعداد ميزة "ربط الاشتراكات" في Google Cloud مكونَين رئيسيَين:

  1. تفعيل واجهة برمجة التطبيقات لمشروع معيّن
  2. إنشاء حساب خدمة للوصول إلى واجهة برمجة التطبيقات

تفعيل واجهة برمجة التطبيقات Subscription Linking API

لاستخدام حساب خدمة وإدارة أذونات القارئ، يجب أن يكون مشروع Google Cloud مفعَّلاً فيه كلّ من واجهة برمجة التطبيقات Subscription Linking API وحساب خدمة OAuth تم إعداده بشكلٍ سليم. لتفعيل واجهة برمجة التطبيقات Subscription Linking API لأحد المشاريع، انتقِل من القائمة إلى واجهة برمجة التطبيقات والخدمات (APIs & Services) ثم إلى المكتبة (Library) وابحث عن Subscription Linking، أو انتقِل إلى الصفحة مباشرةً:


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

api

الشكل 1: الانتقال إلى "مكتبة واجهات برمجة التطبيقات" وتفعيل واجهة برمجة التطبيقات لمشروع على Google Cloud

إنشاء حساب خدمة

تُستخدَم حسابات الخدمة للسماح بالوصول من تطبيقك إلى واجهة برمجة التطبيقات Subscription Linking API.

  1. أنشئ حساب خدمة ضمن وحدة تحكّم مشروعك.
  2. أنشئ بيانات اعتماد لحساب الخدمة، واحفظملفcredentials.json في مكان آمن يمكن لتطبيقك الوصول إليه.
  3. منح دور إدارة الهوية وإمكانية الوصول "مشرف ربط الاشتراكات" لحساب الخدمة الذي أنشأته للتحكّم بشكل دقيق في إمكانات حساب الخدمة، يمكنك منح الدور المناسب من الجدول التالي.
الإمكانية / الدور مشرف ميزة "ربط الاشتراكات" عارض ربط الاشتراكات أداة عرض أذونات استخدام ميزة "ربط الاشتراكات"
الحصول على أذونات القرّاء
جذب القرّاء
تعديل أذونات القرّاء
حذف القرّاء

استخدام حسابات الخدمة مع واجهة برمجة التطبيقات Subscription Linking API

لمصادقة طلبات البيانات إلى واجهة برمجة التطبيقات Subscription Linking API باستخدام حسابات الخدمة، استخدِم إما مكتبة العميل googleapis client library (التي تعالج طلبات access_token تلقائيًا) أو وقِّع الطلبات مباشرةً باستخدام واجهة برمجة التطبيقات REST API. في حال استخدام واجهة برمجة التطبيقات REST API، يجب أولاً الحصول على access_token (من خلال مكتبة Google Auth أو باستخدام معرّف JWT لحساب الخدمة) ثم تضمينه في عنوان Authorization.

تتضمّن أمثلة مكتبة العميل وواجهة برمجة التطبيقات REST التالية عيّنات من الرموز البرمجية حول كيفية استدعاء getReader() و getReaderEntitlements() وupdateReaderEntitlements() و deleteReader().

مكتبة العميل

يوضّح هذا القسم كيفية استخدام مكتبة عملاء googleapis في Node.js.

مثال على طلب

بالنسبة إلى الحقل keyFile في دالة الإنشاء Auth.GoogleAuth، اضبط المسار إلى مفتاح حساب الخدمة. إذا لم تتمكّن من تصدير مفتاح حساب الخدمة بسبب سياسة مؤسستك، يمكنك استخدام طريقة بيانات الاعتماد التلقائية للحساب (ADC). إذا اخترت طريقة ADC، لن تحتاج إلى تقديم الحقل keyFile لأنّ ADC سيبحث عن بيانات الاعتماد بنفسه.

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

واجهة برمجة تطبيقات REST

إذا كنت تريد طلب نقاط نهاية واجهة برمجة التطبيقات REST، يمكنك استخدام أيّ من الطريقتَين للحصول على accessToken لضبط العنوان Authorization.

1. استخدام مكتبة GoogleAuth

بالنسبة إلى مفتاح credentials، يمكنك استخدام مفتاح حساب الخدمة أو بيانات الاعتماد التلقائية للحساب (ADC). إذا اخترت طريقة ADC، لن تحتاج إلى تقديم الحقل credentials لأنّ ADC سيبحث عن بيانات الاعتماد بنفسه.

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. إنشاء access_token باستخدام ملف JWT لحساب الخدمة

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

نموذج رمز برمجي لطلبات 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
 *  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;
}