AI 参与说明(Agent:Codex):本文由 Codex 根据 Polar 官方文档、官方 SDK 发布包与 CLI 源码调研、撰写和校验,资料整理于 2026-09-07。运行记录:模型
gpt-6-astra,reasoning effortultra,执行入口 Codex Desktop,CLI 版本0.153.4,提供方openai。示例固定 SDK 与 API 版本;商家账户可操作范围以 Polar、Stripe 当前界面及审核要求为准。
术语修订(2026-09-07,Agent:Codex):通过 Context7 与 Polar 官方资料核对 Customer State,以英文陈述核心事实、中文解释授权关系。此次运行记录:模型
gpt-6-astra,reasoning effortultra,执行入口 Codex Desktop,提供方openai,CLI 版本0.153.4(不代表桌面 App 版本)。
关联阅读补充(2026-09-08,Agent:Grok):加入 Stripe 直接接入指南的交叉链接。模型
grok-4.6,提供方xAI,执行入口 Grok Build TUI。原文主体未在本次重新核验。
Polar 的基础接入,可以拆成三个相互关联的流程:创建 Checkout 让客户付款,用 Webhook 同步账单变化,在应用后端检查权益。首次实现先选择一个固定金额的订阅产品,跑通这条链路,再增加年付、试用、折扣和 Usage Based Billing。
对于已经以个人身份开始的商家,要分开处理 Organization 的注册法人记录与 Payout Account。Polar 明确支持在原 org 中新增、切换企业结算账户;但不能据此认为注册时的 Individual 已自动改成 Business,公开资料也没有说明该注册字段的自助变更流程。
一、Polar 负责什么,应用负责什么
Polar 是面向数字产品的 billing 平台,同时提供 Merchant of Record(MoR)服务:由 Polar 作为转售方处理消费者付款及相关销售税。应用仍然负责自己的用户身份、业务数据、功能访问控制和使用额度规则。MoR 处理销售税,也不等于代替商家处理其自身的所得税。Merchant of Record
一条普通订阅链路如下;图中表示业务依赖,Webhook 的网络到达顺序不应据此假定:
flowchart TD
A[用户登录应用并选择套餐] --> B[后端确定用户身份与 Product ID]
B --> C[创建 Checkout Session]
C --> D[用户在 Polar Checkout 完成结账]
D --> E[Polar 更新 Order、Subscription 与 Benefits]
E --> F[Webhook 验签并可靠保存事件]
F --> G[同步 Customer State 与应用权限]
D --> H[浏览器回到 success_url]
H --> I[向应用后端查询权限状态]
G --> I
success_url 是浏览器导航入口。用户手动访问该地址,或者浏览器先返回、Webhook 稍后到达,都很正常,因此成功页本身不能授予权限。成功页可以显示“正在确认”,再向后端查询当前状态。
二、先认清基础对象
| 官方概念 | 表示什么 | 接入时如何使用 |
|---|---|---|
| Organization | 商家在 Polar 中的业务空间 | 管理自己的产品、客户和订单;不要与应用中的客户团队混淆。 |
| Payout Account | 向商家结算收益的外部账户 | Polar 通过 Stripe Connect Express 向商家付款;与客户刷卡的付款流程分开。 |
| Product | 卖什么,以及关联的计费安排和权益 | 例如 Pro Monthly、Pro Yearly;后端保存允许出售的 Product ID。 |
| Price | 金额、币种与计价方式 | 固定金额、按用量等;金额字段要核对单位,美元分单位的 1000 表示 10 美元。 |
| Checkout Link | 可重复使用的结账入口 | 适合官网、邮件中的购买链接,每次访问创建具体 Session。 |
| Checkout Session | 一次具体结账流程 | 后端动态创建,绑定客户并取得 url,交给浏览器打开。 |
| Customer / External ID | Polar 客户记录 / 你的稳定客户标识 | 用自己的 user ID 或计费主体 ID 对应 external_id,避免靠邮箱猜归属。 |
| Order | 一次账单或订单记录 | 一次性购买、首次订阅及续费都可能产生 Order。 |
| Subscription | 客户与 recurring Product 的持续订阅关系 | 包含周期、状态和是否在期末取消;一个 Subscription 可产生多笔 Order。 |
| Benefit / Benefit Grant | 权益定义 / 授予某客户的权益实例 | 例如文件、License Key、Feature Flag;定义存在不代表客户已经得到它。 |
| Customer State | 客户当前订阅、权益和 Meter 状态的聚合视图 | 查询 active_subscriptions、granted_benefits 等,决定当前能否访问功能。 |
| Customer Portal / Customer Session | 客户自助账单页面 / 短期访问会话 | 让客户管理订阅、支付方式、订单;由后端为当前用户创建会话。 |
| Webhook Endpoint / Delivery | 事件接收地址 / 一次投递尝试 | 事件可能被重投;端点返回成功意味着你已接收或完成约定的处理。 |
Customer State combines customer data, active subscriptions, granted benefits, and active meters with their current balances in one object. Customer State
应用要据此检查目标功能对应的 Benefit,而不是把“有一条订阅”直接等同于“拥有所有功能”。产品和计费模型参考 Products、Orders、Subscriptions;交付和客户视图参考 Automated Benefits、Customer State。
有三个特别容易套用其他支付平台经验而误解的地方:
- 月付、年付可以分别建 Product,共享同一组 Benefits。Checkout 的
products数组让客户选择产品,不是一次购买整个数组的购物车。Checkout API - 改 Product 的固定价格不会自动重定价已有订阅;归档产品也不会终止已有订阅。既有订阅的变更要单独处理。Products
- External ID 应是稳定标识。官方规定它在 Organization 内唯一,设定后不可修改。个人 SaaS 可以用 user ID;团队付费则应先确定“团队是计费主体”,不要随登录成员变动。Customer Management
metadata 可以保存非敏感的业务关联信息,但不会代替身份验证。浏览器传来的 userId、customerId 或 external_customer_id,都不能未经校验就用于 Checkout 或 Portal。
三、已注册的 org 能从 Individual 转为 Business 吗
注册类型与结算账户是两层设置
如果指注册页面 Using Polar as → Individual / Business 的选项,目前不能确认已有 org 支持直接自助切换。核对官方源码后可以确认:
- 该选项存入 Organization 的
legal_entity:Individual 对应type: individual,Business 对应type: company,企业还记录registered_name。 - 创建 Organization 的 schema 接收该字段,而普通
OrganizationUpdate没有提供它。 - 切换 Payout Account 的实现更新
payout_account_id,并不连带修改 Organization 的legal_entity。
这些结论来自 2026-09-07 核对的官方源码快照:版本 ed164fa39d5467c6a1960a3750257ee5c6845531 的 注册界面、Organization schemas 与 切换结算账户实现。源码能解释当前数据模型,但不能代替托管服务对特定账户变更的审批政策。
结算方面则已有明确的自助路径:同一个 Organization 可以新增企业 Payout Account 并设为 Active。2026-04-14 官方更新记录将其称为 Self-Service Payout Account Management。Payout Accounts、发布记录
因此,已有 org 不必因为结算账户变化就立即重建。应先向 Polar 确认原组织的 legal_entity 如何由个人变更为企业、需要哪些验证,再按确认的要求更新结算账户。可直接描述:“保留原 Organization,从 Individual 变为新企业主体,是否需要更新 Organization legal_entity,以及如何处理已有余额和后续结算。”
Checkout 中的 is_business_customer 是买方的开票与账单身份;将它设为 true 不会改变卖方组织的注册类型。
结算路径 A:保留 org,新增企业账户
- 进入原 Organization 的 Finance → Account → Manage payout accounts。
- 选择 Add Payout Account,按企业税务注册所在地选择国家,完成新的 Stripe onboarding,提供企业、代表人与银行账户等要求的信息。
- 新账户通过所需验证后,使用 Make Active 切换本 Organization 的结算账户。
- 核对 active account 与后续 payout;只有满足删除条件时,才考虑清理旧账户。
官方说明每个 Organization 同时只有一个 active payout account,下一次 payout 会发往该账户。由此可知,这条流程更换的是组织关联的结算账户,通常不需要为了它重建 Product、Customer 或 Subscription;但不要将其推断为历史个人收入、已发出的 payout 或历史票据会自动改归企业。Payout Accounts
旧账户只有在没有关联任何 Organization、没有 pending payouts、Stripe balance 为零时才能删除。企业账户需要企业银行账户;国家、币种及账户是否可验证,按 Polar 的当前 payout 要求办理。Payout Accounts、Payouts FAQ
结算路径 B:在原 Stripe Express 账户中修改资料
Open in Stripe 可进入 Stripe 托管页面修改 business details 和银行资料。Stripe 的 Express 官方帮助还给出了 Settings → Business details → Business type 的编辑路径;但资料有适用范围与验证状态限制,不能承诺每个 Polar 账户都能原地修改。巴西、日本已经完成 legal entity 验证的账户属于官方列出的例外,需要新账户。Stripe:更新 Business type
国家变更是另一种情况:Stripe Express 账户创建后不能直接修改国家,需要新建账户。Polar 的 Add Payout Account → Make Active 提供了结算账户层面的切换路径;Organization 的法人记录仍需另外确认。Stripe Express:国家限制
在变更前,尤其要核对新账户的合法所有人、旧账户未完成的结算与已生成票据。公开的切换功能并不是历史税务记录重写工具。
四、第一次接入:先搭好 Sandbox
1. 独立创建测试环境
进入 Sandbox,单独创建测试账户和 Organization。它与 Production 数据完全隔离,Product ID、Customer ID、Access Token、Webhook 配置都应分别管理。
| 项目 | Sandbox | Production |
|---|---|---|
| Dashboard | sandbox.polar.sh | polar.sh |
| API | https://sandbox-api.polar.sh/v1 | https://api.polar.sh/v1 |
| 本文新版 SDK 配置 | environment: 'sandbox' | environment: 'production' |
| 付款 | 测试卡 | 真实付款 |
SDK 默认使用 Production。只替换 token、忘记切换 environment,或者复制另一环境的 Product ID,都是常见错误。Sandbox Environment
2. 建一个最简单的产品
先创建固定金额、按月计费的 Product,记录 Product ID。若准备按具体功能授权,可以创建 Feature Flag Benefit 并挂到该产品上。第一轮先不加入试用和复杂折扣,便于观察“付款—订阅—权益”的直接关系。Products、Feature Flag Benefit
3. 创建后端凭据
在组织 Settings 中创建 Organization Access Token(OAT),按实际调用的 API scopes 授权。它只能在后端保存,不进入 React bundle 或公开仓库。
另外准备 POLAR_WEBHOOK_SECRET:这是入站 Webhook 的验签密钥,不是 OAT。使用 CLI 本地转发时填 CLI 输出的 secret;部署后的正式 Endpoint 则使用对应 Endpoint 的 secret。OAT、Local Development
五、可运行的本地最小例子
版本要固定,不能混用新旧 SDK
截至 2026-09-07,npm 的 latest 为 0.49.0,next 为 1.0.0-alpha.20;官方 SDK 导览已介绍新版 Public Preview。本文按后者及 2026-04 API 编写,安装时固定版本。
| 项目 | 旧版示例中常见写法 | 本文固定新版写法 |
|---|---|---|
| 初始化 | new Polar(...) | createPolar(...) |
| 导入 | @polar-sh/sdk | @polar-sh/sdk/2026-04 |
| Sandbox 选项 | server: 'sandbox' | environment: 'sandbox' |
| 请求体字段 | externalCustomerId、successUrl | external_customer_id、success_url |
| Webhook helper | 从 @polar-sh/sdk/webhooks 导入 | 从版本入口导入 webhooks,并 await webhooks.validateEvent(...) |
这是 SDK 版本差异,不能全局替换任意框架 adapter 的参数。部分官方功能页仍展示旧版代码,复现时以所安装版本的 README、类型声明和 lockfile 为准。TypeScript SDK、官方 SDK 源码、固定 npm 版本元数据
安装与配置
运行环境为 Node.js 22 及以上;本文在 Node.js 24.16.0 检查语法,在 26.0.0 执行模拟 API 与本地 HTTP 验证。在一个用于实验的项目目录中运行:
npm install --save-exact @polar-sh/sdk@1.0.0-alpha.20
将 .env 加入 .gitignore,在本地填写以下配置;不要把真实密钥贴到博客或提交到 Git:
POLAR_ACCESS_TOKEN=替换为Sandbox的OAT
POLAR_PRODUCT_ID=替换为Sandbox的Product-ID
POLAR_EXTERNAL_CUSTOMER_ID=demo-user-001
POLAR_CUSTOMER_EMAIL=替换为自己的测试邮箱
POLAR_WEBHOOK_SECRET=替换为CLI本次输出的Secret
以下保存为 polar-demo.mjs。它提供 Checkout、Customer State、Portal 三个命令,以及一个 Webhook 本地观察器;观察器只验签、打印事件标识,没有实现持久化授权或订单交付。
import { createServer } from 'node:http';
import { createPolar, webhooks } from '@polar-sh/sdk/2026-04';
function required(name) {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name}`);
return value;
}
const mode = process.argv[2];
if (mode === 'listen') {
const secret = required('POLAR_WEBHOOK_SECRET');
createServer(async (req, res) => {
if (req.method === 'GET' && req.url === '/success') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
return res.end('已返回商店;请查询 Customer State 确认权限。');
}
if (req.method !== 'POST' || req.url !== '/webhooks/polar') {
res.writeHead(404);
return res.end();
}
try {
const chunks = [];
let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > 1024 * 1024) {
res.writeHead(413);
res.end();
return;
}
chunks.push(chunk);
}
const headers = Object.fromEntries(
['webhook-id', 'webhook-timestamp', 'webhook-signature'].map(
(name) => [name, String(req.headers[name] ?? '')],
),
);
const event = await webhooks.validateEvent(
Buffer.concat(chunks), headers, secret,
);
// 本地观察器:仅打印标识,不保存权限,也不执行交付。
console.log(JSON.stringify({
webhook_id: headers['webhook-id'],
type: event.type,
resource_id: event.data.id,
}));
res.writeHead(202);
res.end();
} catch (error) {
console.error(error.name);
const status = error instanceof webhooks.PolarWebhookVerificationError
? 403 : error instanceof webhooks.PolarWebhookError ? 400 : 500;
res.writeHead(status);
res.end();
}
}).listen(3000, '127.0.0.1', () => {
console.log('Listening: http://127.0.0.1:3000/webhooks/polar');
});
} else {
const polar = createPolar({
accessToken: required('POLAR_ACCESS_TOKEN'),
environment: 'sandbox',
});
// 本地实验固定一个用户;线上必须从已验证的登录会话取得。
const externalId = required('POLAR_EXTERNAL_CUSTOMER_ID');
if (mode === 'checkout') {
const checkout = await polar.checkouts.create({
products: [required('POLAR_PRODUCT_ID')],
external_customer_id: externalId,
customer_email: required('POLAR_CUSTOMER_EMAIL'),
success_url: 'http://localhost:3000/success',
});
console.log(checkout.url);
} else if (mode === 'state') {
const state = await polar.customers.getStateExternal(externalId);
console.log(JSON.stringify({
subscriptions: state.active_subscriptions.map((s) => ({
product_id: s.product_id,
status: s.status,
cancel_at_period_end: s.cancel_at_period_end,
current_period_end: s.current_period_end,
})),
benefit_ids: state.granted_benefits.map((b) => b.benefit_id),
}, null, 2));
} else if (mode === 'portal') {
const session = await polar.customerSessions.create({
external_customer_id: externalId,
});
// URL 含客户会话能力;仅供自己打开,不要贴到日志平台或公开分享。
console.log(session.customer_portal_url);
} else {
throw new Error('Usage: node polar-demo.mjs checkout|state|portal|listen');
}
}
代码中的用户来自实验配置。接进实际应用时,Checkout 和 Portal 必须由后端根据已验证的登录会话确定客户,Product ID 由后端套餐白名单映射;不能把这几个 CLI 操作直接包装成允许任意客户 ID 的公开接口。
六、本地调试:按一条完整链路验收
1. 把 Webhook 转发到本机
按 Polar Local Development 安装官方 CLI。随后运行:
polar login
polar listen http://127.0.0.1:3000/webhooks/polar
在交互提示中选择 Sandbox 和正确的 Organization。将 listen 打印的 secret 写入 .env,保持 CLI 运行,再开一个终端启动观察器:
node --env-file=.env polar-demo.mjs listen
若更换了 secret,需要重启 Node 进程重新读取 .env。这里的 CLI 交互行为已对照 v1.3.9 的 login 和 listen 源码核对。
不使用 CLI 时,也可以通过公网 HTTPS 隧道把本地路由暴露出来,再在 Dashboard 配置 Endpoint。正式部署采用这种固定 Endpoint 方式,URL 要直接指向最终路由;Payload format 选择 Raw,勾选业务需要的事件,并保存该 Endpoint 的 secret。Setup an endpoint
2. 创建 Checkout 并支付
在另一个终端运行:
node --env-file=.env polar-demo.mjs checkout
浏览器打开输出的 URL,用测试卡 4242 4242 4242 4242、未来到期日和任意合适的测试 CVC 完成 Sandbox 付款。观察 CLI 投递状态、本地输出和 Dashboard 中的 Order、Subscription。
Sandbox 客户通知邮件只发给 Organization 成员及其邮箱子地址别名,例如成员邮箱的 +test 别名;因此收不到外部测试邮箱的邮件,不足以证明 Checkout 失败。Sandbox Environment
3. 查询状态,再测试取消
node --env-file=.env polar-demo.mjs state
node --env-file=.env polar-demo.mjs portal
state 输出应包含当前订阅及已授予的 Benefit ID。购买前若尚未创建该 Customer,按 External ID 查询可能返回 404;首次 Checkout 后再核对映射。
打开 portal 输出的短期链接,在 Customer Portal 中取消续费,再查询状态:通常会看到 cancel_at_period_end: true,但当前周期仍有效。此时应用应按实际权益与周期处理,不能只因收到 subscription.canceled 就立即封禁访问。Managing subscriptions
4. 故意测试失败和重复
| 测试 | 应核对的结果 |
|---|---|
| 正常购买 | Order 状态、Subscription 与 Benefit Grant 对应同一个应用客户。 |
| 支付失败 | 不因回跳地址、Checkout 创建或 Order 创建就授予付费权益。 |
| 100% 折扣 / 零金额订单 | paid 可能没有实际收款;业务是否允许该产品交付要明确。 |
| 到期取消 / 立即 Revoke | 分别验证保留本期权益和立即撤销的行为。 |
| 同一事件重投 | Dashboard 查看 payload 并 Redeliver,生产处理器不重复交付。 |
| 改错 Webhook secret | 验签失败,返回 403;恢复 secret 后可重投。 |
| 暂停后端或制造 500 | Delivery 记录应反映失败;修复后验证重试、补偿和权限恢复。 |
| 数据库写入失败 | 不能提前返回成功后永久丢失事件。 |
本文已使用固定 SDK 验证三个命令的请求 URL、请求体及 Polar-Version: 2026-04,并通过本地 HTTP 验证两种签名方案、篡改 body、过期时间戳、缺少签名头与错误路由。API 请求使用 mock,未执行真实 Sandbox 交易;真实 Checkout、账户权限与端到端交付需使用自己的测试账户按上面流程验收。该 Alpha SDK 的 helper 不做完整 payload schema 校验,不应把验签成功解释为所有业务字段都已验证。
七、从观察器变成可靠的生产处理器
事件应该怎样分工
| 事件或状态 | 合适的用途 | 不应该推断什么 |
|---|---|---|
customer.state_changed | 更新客户的权限视图,必要时重新读取最新 Customer State | 不能假定收到的历史事件永远是最新状态。 |
order.created | 记录账单已创建 | 不代表订单已付清。 |
order.paid | 按订单做交付、记账或一次性额度发放 | 不代表实收一定大于零,也不代表从未交付过。 |
subscription.cycled | 观察订阅进入新周期 | 触发时订单可能尚不存在,更不代表续费成功。 |
subscription.canceled | 记录取消请求和到期安排 | 不一定立即停权。 |
subscription.revoked | 处理订阅终止带来的权限变化 | 不应抹掉客户从其他产品仍持有的权益。 |
| Benefit Grant 变化 | 处理某项实际权益的授予或撤销 | Benefit 定义本身不是授权实例。 |
以上事件语义以 Webhook Events 与 Customer State 为准。为了按功能授权,检查目标 benefit_id 或目标产品对应的权益,而不是判断“只要有任何 active subscription 就是 Pro”。
工程上建议采用的处理方式
- 使用原始 body 与签名 headers 验签,不能先
JSON.parse再JSON.stringify后验签。 - 在数据库可靠保存事件或持久化入队后再返回
2xx。后台进程尚未可靠接手时,不要仅开启一个 Promise 就声明成功。 - 使用
webhook-id对投递做幂等处理;同一 Order 的业务交付还要有order_id + action唯一约束。data.id是资源标识,一个 Subscription 的多种更新可以共享它,不能将它用于所有事件的统一去重。 - 对状态同步按客户串行处理,重新拉取最新 Customer State,并以事务更新本地权限快照。拉取最新状态也要避免多个并发任务倒序写回。
- 为失败任务保存可重试状态,定期对账。一次性积分发放等历史动作应核对 Order 交付账本,不能只靠当前 Customer State 重建。
这些是接收重复、延迟或并发事件时的应用设计建议,而不是 Polar 提供 exactly-once 的承诺。Standard Webhooks 规范
Webhook secret 在 2026 年 9 月的变化
官方文档预告:2026-09-08 00:00 UTC,即北京时间 08:00,及之后生成的 secret 使用 Standard Webhooks 密钥方案,旧 secret 保持 Polar HMAC 方案。差异在于密钥如何解释,不能只凭外观都带 whsec_ 就自行套用同一解码方式。
本文固定的 SDK 1.0.0-alpha.20 同时尝试两种方案,直接把 Dashboard 或 CLI 提供的 secret 交给对应版本的验签 helper。升级旧项目时,连同导入路径、异步调用和错误类型一起核对。Webhook delivery 与签名说明
八、常见故障如何定位
| 现象 | 按顺序检查 |
|---|---|
| API 401 / 403 | Token 是否有效、是否属于正确环境和组织、API scopes 是否足够。 |
| Product / Customer 404 | ID 与环境是否一致;External ID 对应的客户是否已经创建。 |
| API 422 | 是否混用新旧 SDK 字段;请求体、产品和计费参数是否符合当前 schema。 |
| Webhook 完全没到 | CLI 是否运行;组织与事件订阅是否正确;正式 Endpoint 是否已被禁用。 |
| Webhook 404 / 3xx | URL、尾斜线、域名跳转和实际 POST 路由;Polar 不跟随 redirect。 |
| Webhook 403 | Secret 是否对应当前通道;body 是否被改写;登录 middleware、WAF 是否拦截。 |
| Webhook 2xx 但没开权限 | 返回 2xx 前是否可靠保存;客户映射、目标 Benefit ID、后台任务和本地缓存是否正确。 |
| 付款已完成,成功页仍无权限 | Webhook 延迟、权限同步失败或前端仍使用旧查询缓存;从后端查最新状态。 |
| API 429 | 按 Retry-After 退避,避免成功页无界轮询和每个组件各自请求 Polar。 |
当前官方文档说明:失败投递指数退避重试最多 10 次;请求超时为 10 秒,建议 2 秒内响应;连续 10 次失败投递可能使 Endpoint 自动禁用,需要修复后手动重新启用。HTTP 3xx 也算失败。部署在 Cloudflare 前面时,结合防火墙日志定位拦截,不能假定签名代码一定是 403 的唯一来源。Webhook Delivery、API Overview
九、订阅业务还会遇到哪些概念
- Trial:试用期与正式收费周期不同。是否授予试用权限、试用结束是否成功付费,分别验证。Trials
- Proration:升级、降级等变化的周期内差额计算。要决定立即处理还是下一周期生效,并向用户解释账单变化。Proration
- Past due / Dunning / Grace period:续费失败、重试收款与权益宽限期。
past_due不能一律映射成有权限或无权限;官方支持通过宽限期保留 Benefits,应结合配置与实际 grants 判断。Recovering failed payments - Refund / Cancel / Revoke:退款、停止未来续费、立即终止权益是不同动作。普通订阅订单退款本身不会终止订阅;立即 Revoke 也不会自动退款。Refunds、Managing subscriptions
- Usage Based Billing / Event / Meter / Credits:应用上报用量事件,Meter 按规则聚合,计费使用汇总结果;Credits 有明确的计费余额语义,不等于应用随意维护的“剩余次数”。第一版固定订阅可以暂不接入这些功能。Usage Based Billing、Meters、Credits
十、放到 React、TanStack、Cloudflare、Convex 中
可以采用以下职责划分:React + HeroUI 展示套餐与账单入口;TanStack Start 服务端创建 Checkout 和 Customer Session;TanStack Query 读取应用自己的权限接口;Webhook 接收端在后端完成验签,再可靠存储并更新权限。
Polar 提供 TanStack Start adapter,但 adapter 有自己的版本与选项,不应把本文新版 SDK 的参数逐字替换进去。付款后应主动刷新对应权限查询;受保护的后端操作也必须检查权限,不能只隐藏按钮。
若已有 Cloudflare 后端,可以让 Workers 接收 Webhook,再由可靠队列驱动后续同步;若已有 Convex 后端,可以结合 HTTP Action 与事务写入维护权限数据。已有 Convex Polar Component 可以评估,但 Component 是单独的集成依赖,不能因为使用了它就省略客户映射、事件语义和版本核验。
对应的站内基础资料:
- Stripe 接入指南:角色、对象、Checkout 与 Webhook:直接接入 Payment Processor 时的对象模型、Checkout Session 与履约边界。
- WebHook URL 概念:理解接收 URL、签名和重试的通用约定。
- TanStack 文档导航:查询缓存、服务端路由与应用集成。
- Cloudflare Workers Runtime APIs 与 Bindings:区分请求生命周期、后台任务与可靠消息处理。
- Convex Components:概念、隔离模型与使用方式:评估第三方 Component 的接口与边界。
十一、从 Sandbox 切换到 Production
上线时需要重新核对完整的一组配置:Production Organization、有效 Payout Account、Product / Benefit ID、OAT scopes、正式 Webhook URL 与 secret、客户标识策略,以及固定的 SDK/API 版本。正式收款前完成结算账户 onboarding,并满足账户审核要求。Payout Accounts、Account reviews
验收重点是“购买、查询权限、管理订阅、取消与失败恢复”整个流程。若使用 Embedded Checkout,还应配置组织允许的 embed hosts;当前官方已要求所有组织遵守该限制。Embedded Checkout
费率、支持国家与银行条件随服务政策变化;计算成本和确定结算主体时,使用当前 Fees、Supported countries 与 Payout Accounts,不要沿用历史教程中的数字或注册条件。