Polar

Polar 是一个面向开发者的支付基础设施。它开箱即用地提供了许多面向开发者的支付、结账等集成功能。本插件帮助你将 Polar 与 Better Auth 集成,使你的身份验证与支付流程无缝衔接。

此插件由 Polar 团队维护。如需报告错误、问题或功能请求, 请访问 Polar GitHub 仓库

功能特性

  • 结账集成
  • 客户门户
  • 注册时自动创建客户
  • 事件摄取和客户计量器,用于灵活的基于使用量的计费
  • 通过签名验证安全处理 Polar Webhooks
  • 关联购买与组织的参考系统

安装

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

准备工作

前往你的 Polar 组织设置,创建一个组织访问令牌。将其添加到你的环境变量中。


# .env
POLAR_ACCESS_TOKEN=...

配置 BetterAuth 服务器

Polar 插件附带了一些额外的插件,可为您的技术栈添加功能。

  • 结账(Checkout)- 启用无缝结账集成
  • 门户(Portal)- 让您的客户能够管理他们的订单、订阅和授予的权益
  • 用量(Usage)- 用于列出客户计量器和为基于用量的计费(Usage Based Billing)摄取事件的简单扩展
  • 网络钩子(Webhooks)- 监听相关的 Polar webhook 事件
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" // 用于在结账 URL 中方便引用的自定义标识符,例如 /checkout/pro
                        }
                    ],
                    successUrl: "/success?checkout_id={CHECKOUT_ID}", // 成功 URL
                    authenticatedUsersOnly: true // 仅限认证用户
                }),
                portal(), // 门户插件
                usage(), // 用量插件
                webhooks({
                    secret: process.env.POLAR_WEBHOOK_SECRET, // Webhook 密钥
                    onCustomerStateChanged: (payload) => // 当与客户相关的任何内容发生变化时触发
                    onOrderPaid: (payload) => // 当订单支付完成时触发(购买、订阅续费等)
                    ...  // 超过 25 个细粒度的 webhook 处理程序
                    onPayload: (payload) => // 捕获所有事件的通用处理程序
                })
            ],
        })
    ]
});

配置 BetterAuth 客户端

您将使用 BetterAuth 客户端与 Polar 功能进行交互。

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

// 只需这些配置即可
// 所有 Polar 插件等都应附加到服务器端的 BetterAuth 配置中
export const authClient = createAuthClient({
  plugins: [polarClient()],
});

配置选项

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 对应您在数据库中的用户 ID。这样我们就能避免在您的数据库中进行任何 Polar 到用户的映射操作。

结账插件

要在您的应用中支持结账功能,只需将结账插件传递给 use 属性即可。

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 方法初始化结账会话。这将把用户重定向到产品结账页面。

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

结账过程会自动将已认证用户作为客户信息带入结账流程。邮箱地址将被"锁定"。

如果 authenticatedUsersOnly 设置为 false,则可以在没有任何关联客户的情况下触发结账会话。

组织支持

此插件支持组织插件。如果您将组织 ID 传递给结账 referenceId,您将能够跟踪组织成员进行的购买。

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

await authClient.checkout({
    // 此处可传递任何 Polar 产品 ID
    products: ["e651f46d-ac20-4f26-b769-ad088b123df2"],
    // 或者,如果您在结账配置中设置了 "products",可以传递 slug
    slug: 'pro',
    // 参考 ID 将作为 `referenceId` 保存在结账、订单和订阅对象的元数据中
    referenceId: organizationId
});

门户插件

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

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

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

门户插件为 BetterAuth 客户端提供了一组客户管理方法,这些方法位于 authClient.customer 作用域下。

客户门户管理

以下方法将重定向用户到 Polar 客户门户,在那里他们可以查看订单、购买记录、订阅、权益等。

await authClient.customer.portal();

客户状态

门户插件还提供了一个便捷的状态方法,用于检索通用的客户状态。

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

客户状态对象包含:

  • 关于该客户的所有数据
  • 他们的活跃订阅列表
    • 注意:这不包括由父组织创建的订阅。更多信息请参见下面的订阅列表方法
  • 他们被授予的权益列表
  • 他们的活跃计量器列表及其当前余额

因此,通过这一个对象,您就拥有了检查是否应该提供服务访问权限所需的所有信息。

您可以在 Polar 文档中了解更多关于 Polar 客户状态的信息

权益、订单和订阅

门户插件提供了3个便捷的方法,用于列出与认证用户/客户相关的权益、订单和订阅。

所有这些方法都使用 Polar CustomerPortal API

权益

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

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

订单

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

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

订阅

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

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

重要提示 - 组织支持

此方法不会返回父组织为认证用户创建的订阅。

但是,您可以向此方法传递一个 referenceId 参数。这将返回与该 referenceId 关联的所有订阅,而不是与用户关联的订阅。

因此,要确定用户是否应该拥有访问权限,请传递用户的组织 ID 来检查该组织是否有活跃的订阅。

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 => // 您检查订阅产品或其它逻辑的代码
)

使用量插件

一个用于基于使用量计费的简单插件。

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

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

事件摄取

Polar 的基于使用量计费完全建立在事件摄取之上。从您的应用程序中摄取事件,创建计量器来表示该使用量,并向产品添加计量价格以进行收费。

在 Polar 文档中了解更多关于基于使用量计费的信息。

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

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

客户计量单元

一种简单的方法,用于列出认证用户的使用计量单元(我们称之为客户计量单元)。

客户计量单元包含用户在您定义的计量单元上消费的所有信息。

  • 客户信息
  • 计量单元信息
  • 客户计量单元信息
    • 已消费单元数
    • 已抵扣单元数
    • 余额
const { data: customerMeters } = await authClient.usage.meters.list({
  query: {
    page: 1,
    limit: 10,
  },
});

Webhooks 插件

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

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。

将密钥添加到您的环境变量中。


# .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 - 当创建客户时触发