
Joplin Cloud 免费 Beta 账户无缝升级订阅指南剩余试用天数自动转移机制解析【免费下载链接】joplinJoplin - the privacy-focused note taking app with sync capabilities for Windows, macOS, Linux, Android and iOS.项目地址: https://gitcode.com/GitHub_Trending/jo/joplinJoplin 是一款以隐私为核心的笔记应用支持在 Windows、macOS、Linux、Android 与 iOS 之间进行端到端同步而 Joplin Cloud 是官方托管的同步服务服务端实现在本仓库 packages/server 中。本指南以仓库新闻文档 20210804-085003.md 为主体完整讲解持有免费 Beta 账户的用户如何在试用期结束后平滑升级为付费订阅并深入解析剩余 Beta 试用天数自动转移到订阅试用期这一核心机制的底层实现。读完本文你将掌握完整的升级操作路径、剩余天数计算算法、Beta 用户判定逻辑以及订阅创建背后的 Stripe 事件流转。一、背景Joplin Cloud 的免费 Beta 账户Joplin Cloud 在 2021 年推出时为早期用户提供了免费 Beta 账户允许用户在正式付费之前先体验云同步、笔记发布、笔记本协作等托管能力。Beta 账户本质上是带试用期的账户——从服务端代码看Beta 账户的免费期并非固定日历天数而是以用户创建时间往后推算约 3 个月为截止点这一点可以从betaUserTrialPeriodDays()的实现中得到印证const oneDayMs 86400 * 1000; const oneMonthMs oneDayMs * 30; const endOfBetaPeriodMs userCreatedTime oneMonthMs * 3;也就是说每个 Beta 用户的到期日 注册时间 3 个月每月按 30 天计。Beta 账户到期后若未转为付费订阅账户将无法继续使用。二、从 Beta 免费账户升级为订阅的完整操作流程根据原始公告readme/news/20210804-085003.md升级流程共分三步全程无需人工干预剩余试用天数会被自动折算进入 Joplin Cloud 首页持有 Beta 账户的用户登录后首页会显示一条醒目的 Beta 提示横幅横幅中明确展示该免费 Beta 账户将在 X 天后到期并提供一个Start Subscription开始订阅按钮。该横幅的模板位于 home.mustacheThis is a free beta account that will expire in {{betaExpiredDays}} day(s). To continue using it after this date, please start the subscription by clicking on the button below. From the next screen, select either monthly or yearly payments and click Buy now. Note that remaining days on the beta trial period will be transferred to the new subscription, so you will not lose any trial day.点击按钮跳转到 Plans 页面按钮携带特殊链接链接中已内嵌用户的邮箱与账户类型email、account_type两个查询参数生成逻辑见 stripe.ts 中的betaStartSubUrl()。这样用户无需重新填写信息直接进入套餐选择页按月或按年付费均可。点击 Buy now 完成支付进入 Stripe 结账页Checkout后选择支付方式并确认订阅即刻创建。Stripe 会通过 webhook 回调 Joplin Cloud 服务端服务端完成建号、绑定订阅、清理 Beta 状态等收尾工作。整个过程用户可以随时进行——不需要等到 Beta 试用的最后一天。这是因为系统在创建 Stripe 订阅时会自动把剩余 Beta 天数设置为订阅试用期早订阅不会损失任何免费天数。三、核心机制剩余试用天数如何无缝转移这是整个升级流程的关键设计Beta 剩余天数 新订阅的免费试用期。举原公告中的例子若 Beta 账户还有 60 天到期那么订阅创建后同样会获得 60 天的免费试用期付费从 60 天后才开始。该逻辑在服务端结账会话Checkout Session创建时落地位于 routes/index/stripe.ts 的createCheckoutSession处理器中subscription_data: { trial_period_days: 14, }, ... // If its a Beta user, we set the trial end period to the end of // the beta period. So for example if theres 7 weeks left on the // Beta period, the trial will be 49 days. This is so Beta users can // setup the subscription at any time without losing the free beta // period. const existingUser await ctx.joplin.models.user().loadByEmail(checkoutSession.customer_email); if (existingUser await isBetaUser(ctx.joplin.models, existingUser.id)) { checkoutSession.subscription_data.trial_period_days betaUserTrialPeriodDays(existingUser.created_time); }即普通新用户默认获得 14 天试用而 Beta 用户的试用期被覆盖为betaUserTrialPeriodDays()的计算结果。3.1 剩余天数计算算法计算函数同样位于 utils/stripe.tsexport function betaUserTrialPeriodDays(userCreatedTime: number, fromDateTime 0, minDays 7): number { fromDateTime fromDateTime ? fromDateTime : Date.now(); const oneDayMs 86400 * 1000; const oneMonthMs oneDayMs * 30; const endOfBetaPeriodMs userCreatedTime oneMonthMs * 3; const remainingTimeMs endOfBetaPeriodMs - fromDateTime; const remainingTimeDays Math.ceil(remainingTimeMs / oneDayMs); // Stripe requires a minimum of 48 hours, but lets put 7 days to be sure return remainingTimeDays minDays ? minDays : remainingTimeDays; }要点拆解参数含义userCreatedTime用户在 Joplin Cloud 的注册时间毫秒时间戳fromDateTime计算基准时刻默认取当前时间Date.now()minDays最小试用天数下限默认7 天算法流程为以注册时间 3 个月得到 Beta 截止点减去当前时间得到剩余毫秒数再向上取整Math.ceil换算成天数。最后与最小值 7 天取较大值——这是为了满足 Stripe 对试用期至少 48 小时的要求并留出缓冲。即使只剩最后一天甚至已过期用户也能获得最少 7 天的订阅试用期作为过渡。测试用例对该算法有明确验证见 routes/index/stripe.test.tsexpect(betaUserTrialPeriodDays(1624441295775, fromDateTime)).toBe(50); // Wed Jun 23 2021 09:41:35 GMT0000 expect(betaUserTrialPeriodDays(1614682158000, fromDateTime)).toBe(7); // Tue Mar 02 2021 10:49:18 GMT0000第一个用例2021 年 6 月注册剩余约 50 天验证了剩余天数原样转移第二个用例2021 年 3 月注册剩余不足 7 天验证了最小值保护逻辑。3.2 Beta 用户的判定条件isBetaUser()并非简单判断有没有订阅而是同时满足三个条件见 utils/stripe.tsexport function betaUserDateRange(): number[] { return [1623785440603, 1626690298054]; } export async function isBetaUser(models: Models, userId: Uuid): Promiseboolean { if (!stripeConfig().enabled) return false; const user await models.user().load(userId, { fields: [created_time] }); const range betaUserDateRange(); if (user.created_time range[1]) return false; // approx 19/07/2021 11:24 if (user.created_time range[0]) return false; const sub await models.subscription().byUserId(userId); return !sub; }Stripe 支付功能已启用stripeConfig().enabled为 true否则直接返回 false账户创建时间落在 Beta 开放窗口内时间戳区间[1623785440603, 1626690298054]约对应 2021 年 6 月 15 日至 2021 年 7 月 19 日早于或晚于该窗口的账户均不视为 Beta 用户当前没有订阅记录一旦转为付费订阅isBetaUser即返回 false首页横幅随之消失。四、订阅创建背后的服务端流程从点击 Buy now 到订阅生效服务端经历了一次完整的 Stripe 事件驱动流程入口与处理逻辑均在 routes/index/stripe.ts。4.1 结账会话的创建createCheckoutSession处理器会组装结账会话并返回sessionId前端再用 Stripe.js 的redirectToCheckout({ sessionId })跳转到 Stripe 托管支付页。会话参数值得关注的有mode: subscription订阅模式非一次性支付payment_method_types支持card、sepa_debit、ideal、alipay等支付方式注释说明仅card被 Stripe 支持用于循环订阅当前实现中sofort被注释掉automatic_tax与tax_id_collection自动计税与税号收集allow_promotion_codes允许使用优惠码若同时传入了coupon或promotionCode则会删除该字段Stripe 规定两者只能二选一success_url/cancel_url分别指向/stripe/success与/stripe/cancel。此外服务端还会把sessionId → priceId的映射写入键值存储用于后续确定用户应归属 Basic 还是 Pro 账户类型。4.2 Webhook 事件处理Stripe 支付完成后会推送一系列事件服务端注册了如下处理钩子Stripe 事件服务端动作checkout.session.completed获取用户邮箱更新客户元数据记录来源customer.subscription.created从订阅条目中解析价格 ID映射出账户类型Basic/Pro创建或更新本地用户与订阅记录并自动设置客户偏好语言invoice.paid记录last_payment_time清理支付失败相关标记恢复/保持用户可用状态invoice.payment_failed记录首次失败时间并发送支付失败提醒邮件customer.subscription.updated同步订阅变更如升级/降级到本地账户类型customer.subscription.deleted软删除订阅并标记SubscriptionCancelled禁用账户Webhook 处理对重复事件做了幂等保护同一事件 ID 若正在处理或已处理会通过StripeEventModel的任务机制跳过详见 StripeEventModel.ts。4.3 订阅记录的本地落库SubscriptionModelmodels/SubscriptionModel.ts负责把 Stripe 侧的试用期与计费周期同步到本地updateFromStripe(subscription, stripeSubscription) { const periodEndSeconds stripeSubscription.current_period_end ?? stripeSubscription.items?.data?.[0]?.current_period_end ?? null; const trialEndSeconds stripeSubscription.trial_end ?? null; ... await this.save({ id: subscription.id, current_period_end: stripePeriodEnd, trial_end: stripeTrialEnd, }); }trial_end试用结束时间与current_period_end当前计费周期结束时间因此成为本地判断是否仍在试用期、是否到期的关键字段。值得一提的是代码注释明确说明由于 Stripe API 版本差异current_period_end可能位于订阅对象顶层或items.data内实现中两种位置都做了兼容。五、配套的提醒机制首页横幅与邮件通知为了不让用户错过转换时机服务端提供了双通道提醒首页横幅如前所述登录 Joplin Cloud 首页即展示剩余天数与订阅入口。数据注入逻辑在 routes/index/home.ts通过isBetaUser()决定是否显示横幅betaUserTrialPeriodDays(user.created_time, 0, 0)计算剩余天数此处minDays传 0避免横幅显示被 7 天下限抬高的天数betaStartSubUrl()生成带参数的计划页链接。到期提醒邮件模板见 endOfBetaTemplate.ts。邮件分两种文案剩余X天时的即将到期提醒以及已过期时的账户已过期通知。两种文案均包含订阅链接与剩余试用天数将转移到新订阅的说明并附上支持邮箱供用户咨询。六、实操要点与常见疑问何时订阅最合适任意时间都可以。由于剩余天数会自动转移早订阅不损失免费期系统还保证了最少 7 天的订阅试用期因此即便临近到期也不必担心仓促操作。按月还是按年Plans 页面两种周期都可选upgrade.tsroutes/index/upgrade.ts中的升级路径会基于当前价格周期PricePeriod.Monthly/Yearly匹配对应套餐。订阅后 Beta 状态如何处理成功订阅后isBetaUser()因已有订阅而返回 false首页 Beta 横幅自动消失账户转为对应付费类型Basic/Pro取决于所选价格 ID映射逻辑见 utils/stripe.ts 的priceIdToAccountType()。支付失败会怎样首次失败会记录时间并发送提醒邮件连续失败达到 7 天failedPaymentWarningInterval触发警告标记达到 14 天failedPaymentFinalAccount最终禁用上传/账户相关常量定义在 models/SubscriptionModel.ts。七、相关源码索引围绕本文主题可进一步研读的仓库文件原始公告readme/news/20210804-085003.md结账会话与 Webhook 处理routes/index/stripe.ts试用天数算法与 Beta 判定utils/stripe.ts订阅数据模型models/SubscriptionModel.ts首页 Beta 横幅模板views/index/home.mustache到期提醒邮件模板views/emails/endOfBetaTemplate.ts套餐与价格定义packages/lib/utils/joplinCloud/index.ts算法与判定逻辑的单元测试routes/index/stripe.test.ts通过本指南读者既可以按部就班完成 Beta 账户到付费订阅的转换也能从服务端源码层面理解试用天数无缝转移的完整实现——这正是 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),仅供参考