# How to Use AMP with Arc XP Subscriptions

Source: https://dev.arcxp.com/subscriptions/how-to-guides/how-to-use-amp-with-arc-xp-subscriptions/

Using AMP with Arc XP Subscriptions paywall is fairly straightforward. You will need to use the `<amp-subscription>` tag provided by Google and tell it to use an iframe as its service provider. The iframe will load a document which will in turn load the paywall’s rules & rules evaluator. From here on we will refer to this file as p.html and the evaluator will be referred to as p.js.

## Caveats

- You **must** host the **p.html** file on the same domain as your login page, so that **p.js** can access `localStorage` or `cookies` which store the user’s `JWT`. This is only relevant if you want to customize **p.html**
- It is also necessary to store the _JWT_ in a place where **p.js** can access it. If you are using our default implementation, you will need to store the `JWT` in `localStorage["ArcId.USER_INFO"]` which is where the Identity SDK stores it.
- If you want to use 3rd party entitlement or identity services you will need to modify **p.html** and host it yourself.
- Apple’s Intelligent Tracking Prevention’s changes to the browser storage APIs and how AMP cache serves AMP content to client browsers have now made it impossible for Arc Identity SDKs and **p.js** from accessing values in localStorage/cookies for users on all browsers on iOS v14+. See **AMP READER\_ID** section below for guidance on the workaround.

## Quick Start

The AMP document must load the following extension.

```
    <script
  async
  custom-element="amp-subscriptions"
  src="https://cdn.ampproject.org/v0/amp-subscriptions-0.1.js"
></script>
```

The extension must be passed data like this:

```json
    <script>
  {
    "services": [
      {
        "type": "iframe",
        "iframeSrc": "//yoursite.com/arc/subs/p.html",
        "iframeVars": [
          "READER_ID",
          "CANONICAL_URL",
          "AMPDOC_URL",
          "SOURCE_URL",
          "DOCUMENT_REFERRER"
        ],
        "actions":{
          "login": "//your-site.com/#/login",
          "subscribe": "//your-site.com/#/offer/default"
        },
        "data": {
          "contentType": "story",
          "section": "technology",
          "contentRestriction": "premium",
          "contentId": "123456"
          "apiOrigin": "api-demo-demo-prod.cdn.arcpublishing.com"
        }
      }
    ],
    "fallbackEntitlement": {
      "source": "fallback",
      "granted": false,
      "grantReason": "METERING"
    }
  }
</script>
```

A breakdown of the JSON above follows:

```json
    {
  "services": [
    {
      "type": "iframe", // You MUST set this to type="iframe"
      "iframeSrc": "//yoursite.com/arc/subs/p.html", // this must be the location of p.html
      "iframeVars": [
        "READER_ID",
        "CANONICAL_URL",
        "AMPDOC_URL",
        "SOURCE_URL",
        "DOCUMENT_REFERRER"
      ],
      "actions":{
        "login": "//your-site.com/#/login",
        "subscribe": "//your-site.com/#/offer/default"
      },
      "data": { // This is how you will pass information down to p.js
        "contentType": "story", // required
        "section": "technology", // required
        "apiOrigin": "api-demo-demo-prod.cdn.arcpublishing.com", // required
        "contentRestriction": "premium", // optional, unless your rules require it
        "contentId": "123456" // optional but recommended, if not passed, iframeVars.CANONICAL_URL will be used.
        // You can pass more information here if you are using a custom implementation
        // of p.html. This data will come in as `config.data` in the "connect" method
        // of the controller
      }
    }
  ],
  "fallbackEntitlement": {  // This must be set up in case p.html or p.js doesn't load
    "source": "fallback",
    "granted": false,  // you can set this to true if you want the fallback to allow reads.
    "grantReason": "METERING"
  }
}
```

The rest of the AMP document must then use AMP attributes to show or hide content depending on the verdict. As a simple example:

```html
    <div subscriptions-action="login" subscriptions-display="NOT data.loggedIn">Login</div>

<section subscriptions-section="content-not-granted">
  Login or subscribe to read more.
</section>

<div subscriptions-section="content" class="article-body" itemprop="articleBody">
  <p>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
    Curabitur ullamcorper turpis vel commodo scelerisque. Phasellus
    luctus nunc ut elit cursus, et imperdiet diam vehicula.
    Duis et nisi sed urna blandit bibendum et sit amet erat.
    Suspendisse potenti. Curabitur consequat volutpat arcu nec
    elementum. Etiam a turpis ac libero varius condimentum.
    Maecenas sollicitudin felis aliquam tortor vulputate,
    ac posuere velit semper.
  </p>
</div>

<template type="amp-mustache" subscriptions-dialog subscriptions-display="data.loggedIn">
  Dialog for Subscribers
</template>
<template type="amp-mustache" subscriptions-dialog subscriptions-display="NOT data.loggedIn">
  <div>Dialog for non logged in users<div>
  <div subscriptions-display="NOT data.loggedIn"
    subscriptions-action="subscribe"
    subscriptions-decorate>Login</div>
  <div subscriptions-display="NOT data.subscribed">You are not a subscriber!</div>
</template>
```

### Implementation

#### Custom **p.html**

As mentioned above, for custom builds, we have generated a `p.html` file that you can modify to suit your needs. You will be responsible for hosting this file on a domain that matches your login page. This is a good option if you are not using our `Identity SDK`, have third-party authentication or entitlement services, or want to have complete control over the data passed between the iframe and the amp document.

You can use these file as a starting point:

- [Development Version](https://arc-subs-sdk.s3.amazonaws.com/paywall/p.html)
- [Sandbox Version](https://arc-subs-sdk.s3.amazonaws.com/paywall/sandbox/p.html)
- [Production Version](https://arc-subs-sdk.s3.amazonaws.com/paywall/pp.html)

#### Arc XP Commerce Supported Solution

Alongside the **p.js** file which you already should have access to you will also find a **p.html** which is customized to work with the generated **p.js** file. The goal of the officially supported **p.html** is that you won’t need to change anything. You will simply point the `<amp-subscription>` extension to it and set up the rest of the AMP documents to use its attributes. This solution is good if you are using our Identity SDK, Identity API and Sales API.

## How it works

From a high level, this is how the AMP version of the Arc XP Paywall works:

1. AMP Document loads.
2. `<amp-subscription>` tag is declared with `“type”: “iframe”` and the location of **p.html** specified among other details (for example, content type, location of offers page, etc.).
3. **p.html** loads the Arc Paywall script (for example, **p.js**).
4. `<amp-subscription>` sends an `authorize` event to the iframe.
5. **p.html** runs the paywall rules evaluator and returns a Promise with a verdict.
6. AMP document reacts appropriately.

![AMP with Subscriptions](https://dev.arcxp.com/_astro/Ampwithsubscriptions.DpxLHVPs_Si76n.webp)

## AMP READER\_ID

[AMP](https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/amp-managing-user-state/#background) will often load publisher content from either an AMP CDN on an AMP domain or from Google AMP Viewer. On these domains, our paywall scripts are loaded in an iframe from the publisher’s public origin. In these contexts, p.js is treated as a 3rd party and Apple’s ITP now blocks 3rd party resources from accessing script writable storage (localStorage, javascript cookies, etc.). This impacts our AMP Paywall because we make use of either cookies or localStorage for managing user tokens and logged in state.

In order to get around this latest change, on Google’s recommendation, we’re leveraging the [AMP Reader ID](https://amp.dev/documentation/components/amp-subscriptions/#amp-reader-id), a persistent and unique ID for the user. Google will pass the **READER\_ID** to any publisher resources loaded in an AMP context as a URL query param.

A new Sdk-Amp handles accepting that **READER\_ID** and saving it to cookies or localStorage from the publisher domain. When the user is in an authenticated state in that domain, the **READER\_ID** also gets associated to the Arc user’s identity record. To leverage this functionality, you’ll want to call `AMP.checkAMPReaderID(options, readerId)` on login, heartbeat and options calls. After sign up and login, leverage AMP’s **RETURN\_URL** (placeholder for the return URL specified by the AMP runtime for a Login Dialog to return to) to navigate back to the AMP page. The **RETURN\_URL** query substitution can be used to specify the query parameter for the return URL, for example, `?returnURL=RETURN_URL_`. If the **RETURN\_URL** substitution is not specified, it will be injected automatically with the default query parameter name of “return”.

**p.html** now handles passing the **READER\_ID** to **p.js.** When **p.js** finds a **READER\_ID**, it will be used to fetch entitlements for the current user based on that **READER\_ID** that was associated to the user’s Arc Identity.

## Implementation steps

1. Enable AMP **READER\_ID** from paywall overview screen. ![AMP Reader](https://dev.arcxp.com/_astro/Ampreader.Cl9MvF58_ZyIqxj.webp)
2. Install **AMP** (Arc’s AMP SDK package) from arc-publishing/sdk-amp.  
```js
import AMP from 'arc-publishing/sdk-amp'
```
3. Make call to **AMP.options()**, passing **apiOrigin** and injectable **Identity**. _Ensure an_ _**AMP.options**_ _call is made prior to calling_ _**AMP.checkAMPReaderID.**_  
```js
    AMP.options({
  Identity: Identity,
  apiOrigin: 'https://api-example-example-sandbox.cdn.arcpublishing.com',
});
```
4. After _login, heartbeat_ and _options_ calls, call **checkAMPReaderID()**. This method takes one argument, **readerId.** Google passes the **READER\_ID** to any publisher resources loaded in an AMP context as a URL query param that defaults to “readerId”.  
```js
    const url_string = window.location.href;
const url = new URL(url_string);
const readerId = url.searchParams.get(“readerId”);
const ampReaderId = await AMP.checkAMPReaderID(readerId);
```
5. Return to the AMP page using AMP’s **RETURN\_URL**, which defaults to query param ‘return’. See [AMP Documentation](https://amp-dev.cdn.ampproject.org/c/s/amp.dev/documentation/components/amp-access/) for more info and configuration options.

```js
    // Identity.login...
.then(() => {
  if (return) {
    window.location.replace(`${decodeURI(return)}#success=true`);
    return;
  }
})
.catch(error => {
  if (returnUrl) {
    window.location.replace(`${decodeURI(return)}#success=false`);
    return;
  }
});
```

Below are code examples encompassing steps outlined above.

```js
     <!--AMP page markup-->
 {
    "services": [
      {
        "type": "iframe",
        "iframeSrc": "/amp/p.html",
        "iframeVars": [
          "READER_ID",
          "CANONICAL_URL",
          "AMPDOC_URL",
          "SOURCE_URL",
          "DOCUMENT_REFERRER",
          "RETURN_URL"
        ],
        "actions":{
          "login": "/#/loginL",
          "subscribe": "/#/offer/live"
        },
        "data": {
          "contentType": "story",
          "section": "technology",
          "contentRestriction": "premium",
          "apiOrigin": 'https://api-example-example-sandbox.cdn.arcpublishing.com',
          "identityApiOrigin": 'https://api-example-example-sandbox.cdn.arcpublishing.com'
        }
      },
    ],
    "fallbackEntitlement": "example"
  }

    //Login component example
import { useEffect } from 'react'
import Identity from 'arc-publishing/sdk-identity';
import AMP from 'arc-publishing/sdk-amp'

const search = new URLSearchParams(window.location.search);
const { return: returnUrl, readerId } = Object.fromEntries(urlSearchParams.entries());

 useEffect(() => {
    AMP.options({
      Identity,
      apiOrigin: 'https://api-example-example-sandbox.cdn.arc-publishing.com'
    });
 }, []);

const onLogin = () => {
    return Identity.login(username, password, {
      rememberMe,
      recaptchaToken
    })
      .then(() => {
          AMP.checkAMPReaderID(
           readerId
         ).then(() => {
          if (returnUrl) {
          window.location.replace(`${decodeURI(returnUrl)}#success=true`);
          return;
        }
        history.push('/profile');
        })
      })
      .catch(error => {
        if (returnUrl) {
          window.location.replace(`${decodeURI(returnUrl)}#success=false`);
          return;
        }
      });
}
```

## Differences between Arc Paywall without AMP and with AMP

The main difference between the two implementations is that the AMP page cannot run arbitrary javascript (for the most part). This means that `ArcredP.Run` will not run the `paywallFunction` when a rule evaluates as true. You must use the AMP constructs which will let you display a paywall, hide content, or direct a user somewhere else (see: [AMP Subscriptions](https://amp.dev/documentation/components/amp-subscriptions/) or check below for a simple example.)

Additionally, because of the AMP limitation of not being able to run arbitrary javascript in the document and also because Google hosts AMP pages in their own servers, the Arc Paywall cannot use typical constructs like localStorage, cookies, and the DOM to find the information it needs to render a verdict. This is why the script runs in an iframe and you must pass page-data like the _content type_ and _section_ down to it.

## Resources

- [AMP Access Client-Side Via iFrame GitHhub Repository](https://github.com/ampproject/amphtml/issues/13287) (intent to implement: amp-access iframe)
- [amp-access-iframe-api](https://github.com/ampproject/amphtml/blob/main/extensions/amp-access/0.1/iframe-api/README.md) (amp-access-iframe API)
- [ampproject/amp-iframe NPM Package (Deprecated)](https://www.npmjs.com/package/@ampproject/amp-iframe) (necessary api to communicate w/ AMP document — this is already in p.html)
- [Access iframe messaging and handshake implementation GitHub File](https://github.com/dvoytenko/amphtml/blob/58d9c72bc0324606f43689b432d3183b7361396d/examples/article-access-iframe.provider.html)
- [Extensions: amp-access/0.1/iframe-api/iframe-api.js GitHub File](https://github.com/dvoytenko/amphtml/blob/58d9c72bc0324606f43689b432d3183b7361396d/extensions/amp-access/0.1/iframe-api/iframe-api.js)
