This sets up [RedeemID for EmDash](/docs/emdash/) on your own site. The package's [README](https://github.com/webmultipliers/redeemid-emdash#readme) has every option.

## Requirements

- EmDash 1.1 or newer, on Astro 6 or newer, running on Node or Cloudflare Workers (the free plan is enough)
- A Shopify store on the new customer accounts (**Settings → Customer accounts**). Classic accounts have no Customer Account API.
- An `https` address for the site. Shopify only accepts `https` callback addresses, so a local site needs a tunnel to sign in for real.

## Install the package

```sh
pnpm add @redeemid/emdash
```

Add it to `astro.config.mjs`: the auth provider for sign-in, webhooks and cart routes, the plugin for the admin pages and editor blocks, and the guard that keeps members out of EmDash's own content APIs.

```js
import { redeemid, redeemidGuard, redeemidPlugin } from "@redeemid/emdash";
import emdash from "emdash/astro";

const config = {
	shop: "my-store.myshopify.com",
	levels: [
		{ slug: "member", name: "Member", tags: ["member"] },
		{ slug: "pro", name: "Pro", tags: ["pro"] },
	],
	rules: [
		{ collection: "posts", taxonomy: "category", term: "members", levels: ["member", "pro"] },
		{ collection: "posts", taxonomy: "category", term: "pro", levels: ["pro"] },
	],
	join: { href: "/join/", label: "Become a member" },
};

export default defineConfig({
	integrations: [
		emdash({
			authProviders: [redeemid(config)],
			plugins: [redeemidPlugin(config)],
		}),
		redeemidGuard(),
	],
});
```

Then render entry bodies with `<Content>` instead of `<PortableText>`, so locked parts stay off the page. Pass `id` on the entry's own page so drip and the meter apply:

```astro
---
import Content from "@redeemid/emdash/components/Content.astro";
---
<Content value={post.data.content} collection="posts" terms={post.data.terms} id={post.id} />
```

## Connect Shopify

### Sign-in: a Headless storefront

1. In Shopify admin, open **Sales channels → Headless** and **Create storefront**. Name it after the site.
2. In its **Customer Account API** settings, pick a client type. Confidential gives a client secret; public has none and checks the site's origin instead.
3. Add the callback address `https://your-site/_emdash/api/auth/redeemid/callback`, the JavaScript origin `https://your-site` (public clients), and the logout address `https://your-site/`.
4. Copy the client ID, and the secret if there is one. Copy the storefront's **Storefront API** private token too, for plan cards, products and the cart.

One store can serve several sites. Give each site its own Headless storefront.

### Webhooks and staff tools: a Shopify app

RedeemID keeps tags and status current from webhooks sent by an app on the store, such as a Dev Dashboard app. Point these topics at `https://your-site/_emdash/api/auth/redeemid/webhook`:

`customers/create`, `customers/update`, `customers/delete`, `customers/disable`, `customers/enable`, `customers/merge`, `customer.tags_added`, `customer.tags_removed`, `customers/redact`

`customers/merge` needs the `read_customer_merge` scope. Shopify signs each webhook with the app's client secret. For the staff tools, give the app the `read_customers` and `write_customers` scopes and set its client ID too. With those, the **Register webhooks** button on the Status page subscribes every topic but `customers/redact`, which stays in the app's own settings.

## Set the secrets

On Cloudflare Workers, use `npx wrangler secret put`. On Node, use the environment or `.env`.

| Secret | What it is |
| --- | --- |
| `REDEEMID_SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_ID` | The Headless storefront's Customer Account API client ID |
| `REDEEMID_SHOPIFY_CUSTOMER_ACCOUNT_CLIENT_SECRET` | Its client secret. Confidential clients only. |
| `REDEEMID_SHOPIFY_STOREFRONT_PRIVATE_TOKEN` | The storefront's Storefront API private token |
| `REDEEMID_SHOPIFY_ADMIN_APP_CLIENT_SECRET` | The webhook app's client secret |
| `REDEEMID_SHOPIFY_ADMIN_APP_CLIENT_ID` | Optional: the webhook app's client ID, for the staff tools |
| `REDEEMID_EVENTS_SECRET` | Optional: signs event webhooks sent to `events.url` |
| `REDEEMID_SHOPIFY_STORE_DOMAIN` | Only when `shop` isn't in the config |

## Try it

1. Finish EmDash's own setup as staff first. Store customers can't create the first account.
2. Tag a test customer in Shopify, for example `member`, and visit a locked post.
3. Sign in with that customer's email. You come back to the post, unlocked.
4. Remove the tag in Shopify. The post locks again once the webhook arrives.

**Plugins → RedeemID → Status** in the EmDash admin shows which secrets are set, whether the store was found, the addresses to give Shopify, and the webhooks the site has received.
