사용자 사용 권한 동기화 (서버 측 통합)

게시자는 주로 서버 측 통합을 사용하여 독자와 독자의 사용 권한을 관리합니다. 기본적으로 게시자는 UpdateReaderEntitlements를 사용하여 PPID의 제품 ID 사용 권한에 관한 Google의 레코드를 업데이트합니다.

Google Cloud 설정

Google Cloud에서 정기 결제 연결을 구성하는 작업에는 다음 두 가지 주요 구성요소가 포함됩니다.

  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에서 서비스 계정 사용

서비스 계정을 사용하여 googleapis 클라이언트 라이브러리 또는 REST API로 요청에 서명하여 Subscription Linking API 호출을 인증합니다. 클라이언트 라이브러리는 적절한 access_token 요청을 자동으로 처리하지만 REST API는 id_token를 가져온 다음 access_token로 교환해야 합니다.

다음 클라이언트 라이브러리 및 REST API 예시 모두 getReader() 엔드포인트를 사용합니다. 모든 API 메서드의 실시간 데모는 구독 연결 데모 사이트 또는 코드를 참고하세요.

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 api = new SubscriptionLinking();
const client = api.init();

async function getReader(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.get({
    name: `publications/${publicationId}/readers/${ppid}`,
  });
};

async function updateEntitlements(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
  });
};

REST API 요청 수동 서명

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

function getSignedJwt() {
  /*
    Either store the 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 request = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body
  })

  const accessResponse = await accessFetch.json()
  return accessResponse.access_token
}

async function getReader(ppid) {
  const publicationId = process.env.PUBLICATION_ID
  const base_url = 'https://readerrevenuesubscriptionlinking.googleapis.com/v1'
  const endpoint = `${base_url}/publications/${publicationId}/readers/${ppid}`
  const signedJwt = await getSignedJwt()
  const accessToken = await getAccessToken(signedJwt)

  const reader = await fetch(endpoint, {
     method: 'GET',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   }).then((response) => {
    return response.json()
  })

  return reader
}