Polar
使用 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 - 简单扩展,用于列出客户计量器及摄取事件,支持基于用量计费
- 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" // 自定义 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 客户端与 Polar 功能交互。
import { createAuthClient } from "better-auth/react";
import { polarClient } from "@polar-sh/better-auth/client";
// 就这么简单
// 所有 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,避免了在数据库中维护 Polar 与用户的映射。
结账插件
为了支持你的应用内结账,只需将 Checkout 插件通过 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',
// referenceId 会保存为结账、订单与订阅对象的 metadata 中的 `referenceId`
referenceId: organizationId
});门户插件
允许客户管理其购买、订单及订阅的插件。
import { polar, checkout, portal } from "@polar-sh/better-auth";
const auth = betterAuth({
// ... Better Auth 配置
plugins: [
polar({
...
use: [
checkout(...),
portal()
],
})
]
});portal 插件为 BetterAuth 客户端提供了一组客户管理方法,位于 authClient.customer 下。
客户门户管理
以下方法可将用户重定向至 Polar 客户门户,查看订单、购买、订阅、权益等。
await authClient.customer.portal();客户状态
portal 插件还添加了方便的状态查询方法,可获取通用客户状态。
const { data: customerState } = await authClient.customer.state();客户状态对象包含:
- 客户的所有数据
- 其活跃的订阅列表
- 注意:不含父组织的订阅。详见下方订阅列表方法
- 授权权益列表
- 活跃计量器列表及其当前余额
因此,凭该对象即可判定用户是否应获得服务访问权限。
权益、订单与订阅
portal 插件提供三个方便方法,列出认证用户/客户相关的权益、订单与订阅。
所有这些方法均使用 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 的基于用量计费完全依赖事件摄取。你可以从应用中摄取事件,创建计量器表示该使用量,并为产品添加计量价格进行计费。
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- 当客户状态变更时触发