Joplin Server 本地 Stripe 支付全流程测试指南:从 Webhook 到订阅开通的完整实践
Joplin Server 本地 Stripe 支付全流程测试指南从 Webhook 到订阅开通的完整实践【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin本指南基于 Joplin 开源仓库中 Stripe 测试文档 编写讲解如何在本地开发环境完整跑通 Joplin Server 的 Stripe 订阅支付流程包括启动 Stripe CLI 转发 Webhook、配置公开/私密密钥、通过测试结算页发起订阅以及排查api_key_expired登录故障。读完本文你将掌握 Joplin Cloud 计费体系在开发环境的搭建方法、价格配置结构以及checkout→webhook→ 账号开通的底层实现原理。一、Joplin Server 的 Stripe 计费架构概览Joplin Server即 Joplin Cloud 的自托管版本使用 Stripe 承载订阅计费整套体系由三部分组成Stripe CLI本地开发时用于把 Stripe 云端事件转发到本地服务的 Webhook 端点本地 Joplin Server提供/stripe/*路由创建结算会话、接收 Webhook、展示测试结算页价格配置stripeConfig.json 存放公开的价格与 publishableKey私密密钥secretKey、webhookSecret则来自服务端环境变量。三者的数据流为用户在结算页提交支付 → Stripe 生成checkout.session→ Stripe CLI 把事件转发到http://joplincloud.local:22300/stripe/webhook→ 服务端解析事件、创建订阅记录、更新用户账号类型。从源码看这一链路的核心实现在 stripe 路由 与 stripe 工具函数 中并有配套的 webhook 单元测试 与 mockStripe 模拟器 验证各事件分支。二、本地完整工作流逐步复现1. 构建开发版官网可选但推荐原文档要求先将官网环境切换到dev在website/build.ts中把 env 设为dev然后执行yarn watchWebsite该命令持续监听并构建开发版官网。官网页面如http://localhost:8077/plans/会读取 stripeConfig.json 中的dev配置展示套餐价格。需要注意的是官网构建产物位于packages/tools/website目录相关的文档处理逻辑可参考 processDocs.ts。2. 启动 Stripe CLI 并转发 Webhook仓库已在 packages/server/package.json 中预置了脚本stripeListen: stripe listen --forward-to http://joplincloud.local:22300/stripe/webhook直接执行yarn stripeListenStripe CLI 会输出一串whsec_...格式的 Webhook 签名密钥webhook signing secret本地服务端需要用这把密钥校验事件签名确保事件确实来自 Stripe。3. 配置 Webhook 密钥将上一步复制到的密钥写入本地的joplin-credentials/server.env该目录位于仓库之外属于本地开发凭据目录不在仓库中添加STRIPE_WEBHOOK_SECRETwhsec_xxxxxxxx从 config.ts 的源码可以看到服务端从环境变量加载 Stripe 私密配置function stripeConfigFromEnv(publicConfig: StripePublicConfig, env: EnvVariables): StripeConfig { return { ...publicConfig, enabled: !!env.STRIPE_SECRET_KEY, secretKey: env.STRIPE_SECRET_KEY, webhookSecret: env.STRIPE_WEBHOOK_SECRET, }; }这意味着只要设置了STRIPE_SECRET_KEYStripe 集成即被启用enabled: trueSTRIPE_WEBHOOK_SECRET则用于 stripeEvent() 中的stripe.webhooks.constructEvent()签名校验。4. 启动本地 Joplin Serveryarn start-dev服务运行在http://joplincloud.local:22300该域名即stripeConfig.json中dev.webhookBaseUrl与stripe listen --forward-to的目标地址二者必须保持一致。5. 发起一次真实的结算流程打开http://localhost:8077/plans/开发版官网的套餐页选择套餐并完成支付。支付成功后Stripe 向本地 Webhook 端点推送事件服务端创建或更新订阅与用户记录用户收到包含账号确认链接的邮件。邮件查看技巧本地官网通常未配置真实发件服务但邮件不会丢失——它们会落库。直接查看数据库中的emails表即可这也是开发调试最直接的确认手段。三、简化工作流免官网直接测试如果只想测试支付链路、不想先构建官网服务端内置了一个测试结算页。直接访问http://joplincloud.local:22300/stripe/checkoutTest该页面由 checkoutTest 处理器 动态生成特点如下页面加载 Stripe.js读取stripeConfig().publishableKey初始化客户端预置Subscribe Basic、Subscribe Pro两个按钮分别对应dev配置中accountType: 1与accountType: 2的月付价格支持在输入框中填写Promotion code后发起会话若请求携带?price_idxxx查询参数会额外显示Subscribe Custom按钮用于测试任意自定义价格包括团队阶梯价、AI 积分等点击按钮后前端fetch(/stripe/createCheckoutSession, ...)提交priceId、promotionCode、source字段拿到sessionId后调用stripe.redirectToCheckout跳转 Stripe 托管结算页。安全上该路由做了环境保护当服务端运行在prod环境时会直接抛出ErrorForbidden仅允许开发环境使用见 checkoutTest 首行判断。四、Stripe 配置体系详解Stripe 配置分为公开与私密两层二者在运行时被合并为StripeConfig层级存放位置内容敏感级别公开配置packages/server/stripeConfig.jsonpublishableKey、webhookBaseUrl、prices、archivedPrices可入库私密配置服务端.env本地为joplin-credentials/server.envSTRIPE_SECRET_KEY、STRIPE_WEBHOOK_SECRET严禁泄露公开配置的加载逻辑位于 config.tsconst stripePublicConfig loadStripeConfig(envType Env.BuildTypes ? Env.Dev : envType, ${rootDir}/stripeConfig.json);也就是说JSON 文件的顶层按环境划分dev/prod服务端按当前运行环境选取对应块并通过 loadStripeConfig 做价格预处理计算formattedAmount、formattedMonthlyAmount、userRange等展示字段。价格条目结构stripeConfig.json中的每种价格条目遵循 StripePublicConfig 类型定义支持两类产品订阅价格accountTypeperiod字段说明示例值accountType账号类型枚举见下方说明1BasicidStripe Price IDprice_1TTPQHL9ZkKzC9sXEOq6yJhmperiod计费周期monthly或yearlymonthlyamount单价字符串以currency计1.99currency币种枚举为 EUR/GBP/USDEURaccountType与 AccountType 枚举 一一对应0Default、1Basic、2Pro、3Team、4Pro100Gb、5SelfHosted。团队阶梯价accountType: 5使用quantityMinimum与amounts数组表达按用户数分档的定价{ accountType: 5, id: price_1ThCwVL9ZkKzC9sXU9AVyDB1, period: yearly, quantityMinimum: 2, amounts: [ { amount: 40.00, users: [2, 10] }, { amount: 30.00, users: [11, 50] }, { amount: 20.00, users: [51, infinity] } ], currency: EUR }users为[min, max]闭区间infinity表示无上限。这类条目在源码中对应 StripeTieredSubscriptionPrice其userRange.max会被规范化为Number.POSITIVE_INFINITY。AI 积分商品productType: ai-credits不绑定订阅按积分数量售卖{ productType: ai-credits, id: price_1Tf4FvL9ZkKzC9sXpjjunUDE, amount: 2.00, aiCredits: 500000, currency: EUR }对应源码类型 StripePublicConfigAiProductPrice不包含accountType与period。archivedPrices已下架的价格快照。运行时 findPrice 会同时在prices与archivedPrices中查找确保历史订阅续费时仍能解析出价格只是不再对新用户开放。价格查询的核心工具服务端所有“根据 Price ID 反查账号类型”“根据账号类型反查 Price ID”的操作都通过findPrice完成例如 priceIdToAccountTypeexport function priceIdToAccountType(priceId: string): AccountType { const price findPrice(stripeConfig(), { priceId }); return price.accountType; }Webhook 收到customer.subscription.created事件后正是靠这个函数从订阅的price.id反推出账号类型Basic/Pro/Team 等默认兜底为Basic见 stripe.ts 路由。五、Webhook 事件处理订阅如何真正开通本地测试绕不开 Webhook。服务端对 Stripe 事件的完整处理集中在 stripe 路由的 webhook 处理器关键事件及其作用如下Stripe 事件服务端行为checkout.session.completed记录客户来源 metadata为后续订阅落库做准备customer.subscription.created核心开通逻辑从订阅 item 提取 Price ID → 反查账号类型 → 创建/更新用户与订阅记录handleSubscriptionCreatedinvoice.paid标记支付成功续订计费期handlePayment(..., true)invoice.payment_failed标记支付失败进入past_due状态handlePayment(..., false)触发 paymentFailedTemplate.ts 等邮件提醒customer.subscription.updated升级/降级生效用新 Price ID 反查账号类型并更新本地用户account_typecustomer.subscription.deleted软删除订阅并给用户打上SubscriptionCancelled标志停用其高级功能几个值得注意的实现细节测试时会遇到重复订阅防护如果用户已有订阅却又创建了第二个服务端会主动取消后创建的重复订阅见 handleSubscriptionCreated事件幂等每个事件通过 StripeEventModel 的withTask以事件 ID 加锁执行重复推送的事件会被识别为ErrorTaskInProgress而跳过成功页兜底等待Stripe 在 Webhook 处理完成前就可能跳转success_url因此 success 处理器 会轮询等待用户创建完成最长约 10 秒然后自动生成 token 并跳转到账号确认页若等待超时则提示用户查收确认邮件Beta 用户试用期对在 Beta 时间窗口内betaUserDateRange注册的老用户试用期会被延长到 Beta 期结束避免用户损失免费试用时长betaUserTrialPeriodDays最少 7 天满足 Stripe 对试用期时长的限制。这些分支逻辑均有测试覆盖完整的 Webhook 驱动测试见 stripe.test.ts测试中通过 mockStripe 模拟 Stripe 服务端返回可以直接驱动postHandlers.webhook(...)验证订阅开通、续费、取消等场景。六、Stripe CLI 登录失败api_key_expired排查本地长期不使用时Stripe CLI 的登录令牌会过期运行yarn stripeListen时可能报错FATAL Error while authenticating with Stripe: Authorization failed并伴随错误码api_key_expired。解决办法是重新登录以刷新 CLI 令牌stripe logout stripe loginstripe login会打开浏览器完成 OAuth 授权并生成新的本地令牌存放在~/.config/stripe下随后重新执行yarn stripeListen即可恢复 Webhook 转发。七、常见问题与调试建议Webhook 收不到事件先确认stripe listen输出的whsec_密钥与STRIPE_WEBHOOK_SECRET完全一致含前缀再确认server.env中该变量已生效重启yarn start-dev域名解析问题本地服务绑定在joplincloud.local:22300若无法访问需确认本机 hosts 中已将joplincloud.local解析到127.0.0.1支付失败模拟代码注释中提供了直接命令stripe trigger invoice.payment_failed可用来模拟扣款失败观察past_due处理与邮件提醒查看落库邮件本地未配置 SMTP 时邮件内容保存在数据库emails表中可直接查询确认确认邮件、支付失败通知等内容价格改动的生效stripeConfig.json是运行时读取的公开配置修改后重启服务即可新增 Stripe 侧 Price ID 后需同步维护accountType/period映射否则findPrice会抛Not found错误。八、总结本文从 Stripe 测试文档 出发完整复现了 Joplin Server 本地支付链路的搭建步骤并结合源码梳理了公开/私密双层配置、价格条目的字段语义、Webhook 六大事件的处理流程以及api_key_expired的修复方法。掌握这套流程后你可以在不改动任何线上配置的前提下对订阅开通、升级降级、续费失败、取消订阅等场景做完整的本地回归验证——这正是 Joplin 团队用来保障 Joplin Cloud 计费稳定性的标准开发姿势。【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplin创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考