# Polar (/docs/plugins/polar)

使用 Polar 实现更佳的支付和结账验证插件



[Polar](https://polar.sh) 是一个以开发者为中心的支付基础设施。开箱即用，提供了许多面向开发者的支付、结账等集成。此插件帮助你将 Polar 与 Better Auth 集成，实现无缝的身份验证和支付流程。

<Callout>
  本插件由 Polar 团队维护。如遇错误、问题或功能请求，
  请访问 [Polar GitHub 仓库](https://github.com/polarsource/polar-adapters)。
</Callout>

## 功能 [#功能]

* 结账集成
* 客户门户
* 注册时自动创建客户
* 事件摄取与用于灵活使用计费的客户仪表
* 使用签名验证安全处理 Polar Webhooks
* 引用系统，用于将购买与组织关联

## 安装 [#安装]

```bash
pnpm add better-auth @polar-sh/better-auth @polar-sh/sdk
```

## 准备工作 [#准备工作]

进入你的 Polar 组织设置，创建一个组织访问令牌，并添加到你的环境变量中。

```bash
# .env
POLAR_ACCESS_TOKEN=...
```

### 配置 BetterAuth 服务端 [#配置-betterauth-服务端]

Polar 插件包含若干附加插件，为你的堆栈增加功能。

* 结账（Checkout）—— 提供无缝的结账集成
* 门户（Portal）—— 允许客户管理他们的订单、订阅和已授予的权益
* 用量（Usage）—— 简单的扩展，用于列出客户仪表并摄取事件以支持基于用量的计费
* Webhooks —— 监听相关的 Polar Webhooks

```typescript
import { betterAuth } from "better-auth";
import { polar, checkout, portal, usage, webhooks } from "@polar-sh/better-auth";
import { Polar } from "@polar-sh/sdk";

const polarClient = new Polar({
    accessToken: process.env.POLAR_ACCESS_TOKEN,
    // 如果使用 Polar 沙盒环境，则使用 'sandbox'
    // 记住，访问令牌、产品等在不同环境间完全分隔。
    // 生产环境获取的访问令牌不能在沙盒环境使用。
    server: 'sandbox'
});

const auth = betterAuth({
    // ... Better Auth 配置
    plugins: [
        polar({
            client: polarClient,
            createCustomerOnSignUp: true,
            use: [
                checkout({
                    products: [
                        {
                            productId: "123-456-789", // 来自 Polar 仪表盘的产品 ID
                            slug: "pro" // 自定义 slug 以便在结账 URL 中引用，例如 /checkout/pro
                        }
                    ],
                    successUrl: "/success?checkout_id={CHECKOUT_ID}",
                    authenticatedUsersOnly: true
                }),
                portal(),
                usage(),
                webhooks({
                    secret: process.env.POLAR_WEBHOOK_SECRET,
                    onCustomerStateChanged: (payload) => // 当客户相关信息变更时触发
                    onOrderPaid: (payload) => // 当订单付款成功时触发（购买、订阅续费等）
                    ...  // 超过 25 个细粒度 webhook 处理器
                    onPayload: (payload) => // 捕获所有事件的通用处理器
                })
            ],
        })
    ]
});
```

### 配置 BetterAuth 客户端 [#配置-betterauth-客户端]

你将使用 BetterAuth 客户端与 Polar 功能交互。

```typescript
import { createAuthClient } from "better-auth/react";
import { polarClient } from "@polar-sh/better-auth/client";

// 就这么简单
// 所有 Polar 插件应绑定到服务端的 BetterAuth 配置上
export const authClient = createAuthClient({
  plugins: [polarClient()],
});
```

## 配置选项 [#配置选项]

```typescript
import { betterAuth } from "better-auth";
import {
  polar,
  checkout,
  portal,
  usage,
  webhooks,
} from "@polar-sh/better-auth";
import { Polar } from "@polar-sh/sdk";

const polarClient = new Polar({
  accessToken: process.env.POLAR_ACCESS_TOKEN,
  // 如果使用 Polar 沙盒环境，则使用 'sandbox'
  // 记住，访问令牌、产品等在不同环境间完全分隔。
  // 生产环境获取的访问令牌不能在沙盒环境使用。
  server: "sandbox",
});

const auth = betterAuth({
  // ... Better Auth 配置
  plugins: [
    polar({
      client: polarClient,
      createCustomerOnSignUp: true,
      getCustomerCreateParams: ({ user }, request) => ({
        metadata: {
          myCustomProperty: 123,
        },
      }),
      use: [
        // 这里添加 Polar 插件
      ],
    }),
  ],
});
```

### 必需选项 [#必需选项]

* `client`：Polar SDK 客户端实例

### 可选选项 [#可选选项]

* `createCustomerOnSignUp`：用户注册时自动创建 Polar 客户
* `getCustomerCreateParams`：自定义函数，用于提供额外的客户创建元数据

### 客户管理 [#客户管理]

启用 `createCustomerOnSignUp` 后，当在 Better-Auth 数据库中新增用户时，系统会自动创建对应的 Polar 用户。

所有新客户均带有相关的 `externalId`，即数据库中用户的 ID，避免在数据库中维护 Polar 与用户的映射关系。

## 结账插件 [#结账插件]

为了支持应用内结账，只需通过 `use` 属性传入 Checkout 插件。

```typescript
import { polar, checkout } from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth 配置
    plugins: [
        polar({
            ...
            use: [
                checkout({
                    // 可选字段 - 允许通过 slug 而非产品 ID 触发结账
                    products: [ { productId: "123-456-789", slug: "pro" } ],
                    // 结账成功后跳转的相对 URL
                    successUrl: "/success?checkout_id={CHECKOUT_ID}",
                    // 是否仅允许认证用户结账
                    authenticatedUsersOnly: true
                })
            ],
        })
    ]
});
```

启用结账后，可以通过 BetterAuth 客户端的 `checkout` 方法初始化结账会话，用户将被重定向到产品结账页面。

```typescript
await authClient.checkout({
  // 这里可传任何 Polar 产品 ID
  products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
  // 或者，如果你在结账配置中设置了“products”，可以传递 slug
  slug: "pro",
});
```

结账会自动将认证用户作为客户关联，邮箱地址将被“锁定”。

如果 `authenticatedUsersOnly` 为 `false`，则可触发无关联客户的结账会话。

### 组织支持 [#组织支持]

此插件支持组织插件。如果你将组织 ID 传给结账的 `referenceId`，你可以追踪组织成员的购买记录。

```typescript
const organizationId = (await authClient.organization.list())?.data?.[0]?.id;

await authClient.checkout({
    // 这里可传任何 Polar 产品 ID
    products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
    // 或者，如果你在结账配置中设置了“products”，可以传递 slug
    slug: 'pro',
    // referenceId 会保存为结账、订单与订阅对象的 metadata 中的 `referenceId`
    referenceId: organizationId
});
```

## 门户插件 [#门户插件]

允许客户管理其购买、订单及订阅的插件。

```typescript
import { polar, checkout, portal } from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth 配置
    plugins: [
        polar({
            ...
            use: [
                checkout(...),
                portal()
            ],
        })
    ]
});
```

portal 插件为 BetterAuth 客户端提供了一组客户管理方法，位于 `authClient.customer` 下。

### 客户门户管理 [#客户门户管理]

以下方法可将用户重定向至 Polar 客户门户，查看订单、购买、订阅和权益等。

```typescript
await authClient.customer.portal();
```

### 客户状态 [#客户状态]

portal 插件还添加了方便的状态查询方法，可获取通用客户状态。

```typescript
const { data: customerState } = await authClient.customer.state();
```

客户状态对象包含：

* 有关客户的全部数据。
* 他们的活跃订阅列表
  * 注意：这不包括由父组织进行的订阅。更多订阅列表信息请参见下文。
* 他们已授予的权益列表。
* 带有当前余额的活跃计量器列表。

因此，可以根据该对象判断用户是否应获得服务访问权限。

[你可以在 Polar 文档中了解更多关于客户状态的信息](https://docs.polar.sh/integrate/customer-state)。

### 权益、订单与订阅 [#权益订单与订阅]

portal 插件提供三个便捷方法，用于列出认证用户/客户相关的权益、订单和订阅。

[这些方法均使用 Polar CustomerPortal API](https://docs.polar.sh/api-reference/customer-portal)

#### 权益 [#权益]

此方法仅列出认证用户/客户的已授予权益。

```typescript
const { data: benefits } = await authClient.customer.benefits.list({
  query: {
    page: 1,
    limit: 10,
  },
});
```

#### 订单 [#订单]

此方法列出认证用户/客户的订单，如购买及订阅续费。

```typescript
const { data: orders } = await authClient.customer.orders.list({
  query: {
    page: 1,
    limit: 10,
    productBillingType: "one_time", // 或 'recurring'
  },
});
```

#### 订阅 [#订阅]

此方法列出认证用户/客户的订阅。

```typescript
const { data: subscriptions } = await authClient.customer.subscriptions.list({
  query: {
    page: 1,
    limit: 10,
    active: true,
  },
});
```

**重要**——组织支持

此方法**不会**返回由父组织为认证用户订购的订阅。

你可以向此方法传入 `referenceId`，它将返回与该 `referenceId` 关联的所有订阅，而非用户的订阅。

因此，要判断用户是否应有权限，可以传入用户所属组织的 ID，查看该组织是否有活跃订阅。

```typescript
const organizationId = (await authClient.organization.list())?.data?.[0]?.id;

const { data: subscriptions } = await authClient.customer.orders.list({
    query: {
      page: 1,
      limit: 10,
      active: true,
      referenceId: organizationId
    },
});

const userShouldHaveAccess = subscriptions.some(
    sub => // 你的逻辑以检查订阅产品等。
)
```

## 用量插件 [#用量插件]

用于基于用量计费的简易插件。

```typescript
import { polar, checkout, portal, usage } from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth 配置
    plugins: [
        polar({
            ...
            use: [
                checkout(...),
                portal(),
                usage()
            ],
        })
    ]
});
```

### 事件摄取 [#事件摄取]

Polar 的基于用量计费完全依赖事件摄取。你可以从应用中摄取事件，创建计量器表示该使用量，并为产品添加计量价格进行计费。

[在 Polar 文档中了解更多关于基于用量计费](https://docs.polar.sh/features/usage-based-billing/introduction)。

```typescript
const { data: ingested } = await authClient.usage.ingest({
  event: "file-uploads",
  metadata: {
    uploadedFiles: 12,
  },
});
```

认证用户会自动与摄取事件关联。

### 客户计量器 [#客户计量器]

一个简单方法列出认证用户的用量计量器，即“客户计量器”。

客户计量器包含其定义计量器的所有用量信息：

* 客户信息
* 计量器信息
* 客户计量信息
  * 已消耗单位
  * 抵扣单位
  * 余额

```typescript
const { data: customerMeters } = await authClient.usage.meters.list({
  query: {
    page: 1,
    limit: 10,
  },
});
```

## Webhooks 插件 [#webhooks-插件]

Webhooks 插件可用于捕获来自你的 Polar 组织的事件。

```typescript
import { polar, webhooks } from "@polar-sh/better-auth";

const auth = betterAuth({
    // ... Better Auth 配置
    plugins: [
        polar({
            ...
            use: [
                webhooks({
                    secret: process.env.POLAR_WEBHOOK_SECRET,
                    onCustomerStateChanged: (payload) => // 当客户相关信息变更时触发
                    onOrderPaid: (payload) => // 当订单付款成功时触发（购买、订阅续费等）
                    ...  // 超过 25 个细粒度 webhook 处理器
                    onPayload: (payload) => // 捕获所有事件的通用处理器
                })
            ],
        })
    ]
});
```

在你的 Polar 组织设置页面配置 Webhook 端点，Webhook 地址为 `/polar/webhooks`。

将密钥添加到环境变量。

```bash
# .env
POLAR_WEBHOOK_SECRET=...
```

插件支持所有 Polar webhook 事件的处理：

* `onPayload` - 捕获所有传入 Webhook 事件的通用处理器
* `onCheckoutCreated` - 当创建结账时触发
* `onCheckoutUpdated` - 当更新结账时触发
* `onOrderCreated` - 当创建订单时触发
* `onOrderPaid` - 当订单付款成功时触发（购买、订阅续费等）
* `onOrderRefunded` - 当订单退款时触发
* `onRefundCreated` - 当创建退款时触发
* `onRefundUpdated` - 当更新退款时触发
* `onSubscriptionCreated` - 当创建订阅时触发
* `onSubscriptionUpdated` - 当更新订阅时触发
* `onSubscriptionActive` - 当订阅变为活跃时触发
* `onSubscriptionCanceled` - 当订阅取消时触发
* `onSubscriptionRevoked` - 当订阅被撤销时触发
* `onSubscriptionUncanceled` - 当订阅取消被撤销时触发
* `onProductCreated` - 当创建产品时触发
* `onProductUpdated` - 当更新产品时触发
* `onOrganizationUpdated` - 当更新组织时触发
* `onBenefitCreated` - 当创建权益时触发
* `onBenefitUpdated` - 当更新权益时触发
* `onBenefitGrantCreated` - 当创建权益授予时触发
* `onBenefitGrantUpdated` - 当更新权益授予时触发
* `onBenefitGrantRevoked` - 当权益授予被撤销时触发
* `onCustomerCreated` - 当创建客户时触发
* `onCustomerUpdated` - 当更新客户时触发
* `onCustomerDeleted` - 当删除客户时触发
* `onCustomerStateChanged` - 当客户状态变更时触发

