
Tamagui Takeout 订阅权限同步体系实战Stripe、GitHub、Discord 三方数据一致性运维指南【免费下载链接】tamaguiStyle React fast with 100% parity on React Native, an optional UI kit, and optimizing compiler.项目地址: https://gitcode.com/GitHub_Trending/ta/tamagui本指南以 Tamagui 开源仓库中的 scripts/takeout/README.md 为核心系统讲解 Takeout 商业化产品Tamagui Pro、Takeout Stack、Team Seats在 Stripe 订阅、Supabase 数据库、GitHub 团队授权与 Discord 角色授权四者之间的同步架构、巡检脚本、清理流程与历史故障复盘。读完本文你将掌握如何用仓库内提供的 6 个 TypeScript 运维脚本完成三方数据一致性审计、Webhook 故障排查、月度过期 Claim 清理与 Discord 角色回收并理解底层权限同步链路在code/tamagui.dev中的真实实现。一、体系架构订阅 → Claim → GitHub/Discord 的同步链路Takeout 是 Tamagui 的商业化产品其权限发放不再依赖人工操作而是由一套「Stripe 订阅 Supabase 数据库 GitHub 团队 Discord 角色」的自动化链路驱动。文档给出了四个核心步骤用户订阅客户在 Stripe 中订阅 Tamagui Pro 或 Takeout Stack。Webhook 创建 ClaimStripe 事件触发 Webhook在数据库claims表中创建一条领取记录并写入team_slug: early-access。用户领取权限用户通过官网tamagui.dev点击 Claim Access系统将其加入 GitHub 的early-access团队。订阅取消Stripe Webhook 将对应 Claim 标记为未领取unclaimed_at同时从 GitHub 团队移除用户并回收 Discord Takeout 角色。这一流程对应的真实实现位于 code/tamagui.dev/app/api/stripe/webhookapi.ts它通过stripe.webhooks.constructEvent()校验签名后分发product.*、price.*、invoice.*、customer.subscription.*等事件类型取消/续费类事件最终会调用 code/tamagui.dev/features/api/unclaimProduct.ts 中的unclaimSubscription()完成 GitHub 与 Discord 的双向回收。授予 early-access 的三个产品文档明确列出以下三个 Stripe 产品会授予 early-access 权限产品 ID 与 scripts/takeout/check-discord-sync.ts 中的EARLY_ACCESS_PRODUCT_IDS常量一一对应产品Stripe 产品 ID权限说明Tamagui Proprod_RlRd2DVrG0frHe包含 takeout 访问权限Takeout Stackprod_NzLEazaqBgoKnC独立的 takeout 产品Tamagui Pro Team Seatsprod_Rxu0x7jR0nWJSv团队订阅席位值得注意的差异check-subscription-sync.ts判断 takeout 订阅时采用的是「名称匹配 ID 兜底 metadata 关键字」的复合策略name含takeout/tamagui pro或metadata.type repo、metadata.includes_takeout true、metadata.repository_name为takeout/unistack等而check-discord-sync.ts仅依据三个硬编码产品 ID 精确过滤。前者覆盖面更广适合做总量审计后者更严格适合做 Discord 角色对账。涉及的数据库表整套体系依赖 Supabase 中的三张核心表README「Related Files」一节列出claims权限领取记录核心字段包括subscription_id、product_id、dataJSON内含user_github、team_slug、repository_name等、created_at、unclaimed_atdiscord_invitesDiscord 用户与订阅的映射discord_user_id↔subscription_idsubscriptions本地订阅镜像表用于通过user_id反查订阅归属用户。二、运行前置条件与环境变量所有脚本均为 TypeScript 源码scripts/takeout/ 目录下共 6 个以#!/usr/bin/env tsx作为 shebang运行时依赖tsx、supabase/supabase-js、stripe与discordjs/*core、rest、ws等包。执行方式统一为npx tsx scripts/takeout/脚本名.ts [参数]按 README 说明脚本所需的环境变量会自动从code/tamagui.dev/.env加载从源码看脚本本身直接读取process.env.*并在缺失时打印错误、process.exit(1)退出例如 check-subscription-sync.ts。因此运行前需确认对应环境已注入下列变量变量用途需要该变量的脚本NEXT_PUBLIC_SUPABASE_URLSupabase 项目 URL全部除 check-webhook-eventsSUPABASE_SERVICE_ROLE_KEYSupabase 服务端密钥service key全部除 check-webhook-eventsSTRIPE_SECRET_KEYStripe 密钥全部GITHUB_ADMIN_TOKEN具备 org/team 管理权限的 GitHub PATcheck-subscription-syncDISCORD_BOT_TOKENDiscord Bot Tokencheck-discord-sync、remove-discord-users三、健康巡检三个「只读审计」脚本1. check-subscription-sync.tsStripe 与 GitHub 团队的对账这是最核心的同步健康检查脚本运行方式npx tsx scripts/takeout/check-subscription-sync.ts它并行拉取三类数据后交叉比对源码见 check-subscription-sync.ts数据库活跃 Claim查询claims表中unclaimed_at IS NULL的记录最多 1 万条再按data.repository_name takeout过滤出 takeout 相关 ClaimStripe 活跃订阅分页拉取所有status: active的订阅按 takeout 产品集合过滤GitHub 团队成员通过 GitHub REST API 分页拉取https://api.github.com/orgs/tamagui/teams/early-access/members每页 100并统计待处理邀请.../invitations。最终输出摘要与差异分析对比「数据库 Claims vs Stripe 订阅」「GitHub 团队 vs 数据库 Claims」两个差值当任一差值绝对值超过5时输出 ⚠️ 警告提示可能存在的订阅无 Claim、已取消订阅残留 Claim、迁移不完整、手动增删成员等情况并列出「数据库有而 Stripe 无」「Stripe 有而数据库无」的订阅 ID 前 10 条。脚本最后返回结构化摘要对象便于程序化消费。典型应用场景怀疑同步异常、迁移前后验证、或例行巡检。2. check-discord-sync.tsDiscord Takeout 角色对账npx tsx scripts/takeout/check-discord-sync.ts该脚本将「Discord 中持有 Takeout 角色角色 ID1131082605052301403所属 Tamagui 服务器 ID909986013848412191」的用户与「Stripe 活跃订阅」进行比对源码见 check-discord-sync.ts通过discordjs/ws网关连接 Discord拉取服务器内最多 1000 名成员并筛选出持有 Takeout 角色的用户通过 Supabasediscord_invites表查询这些用户的subscription_id映射逐一判断无映射记录的用户归入「⚠️ 无 Supabase 映射可能是旧账号/测试账号」有映射但订阅全部不活跃的用户归入「❌ 应移除」至少有一个活跃订阅的用户为「✅ 有效」。脚本会将应移除用户写入tmp/discord-users-to-remove-时间戳.json源码中使用process.cwd()下的tmp/目录mkdirSync(tmp, { recursive: true })自动创建文件包含generated_at、count与users数组含discord_id、username、global_name、inactive_subscription_ids供后续移除脚本消费。典型应用场景审计 Discord 访问、找出已取消订阅却仍保留角色的过期用户。3. check-webhook-events.tsWebhook 事件排障npx tsx scripts/takeout/check-webhook-events.ts [--limit 100]该脚本是纯 Stripe 侧诊断工具源码见 check-webhook-events.ts输出三部分信息事件类型分布调用stripe.events.list({ limit })拉取最近 N 条事件--limit默认 100按event.type分组计数排序展示订阅相关事件明细过滤customer.subscription.*前缀的事件最多展示最近 20 条包含事件时间ISO 格式、订阅 ID、状态并对customer.subscription.deleted标注「该订阅已取消」Webhook 端点状态调用stripe.webhookEndpoints.list()列出已配置端点展示 URL、enabled状态与启用的事件列表若未配置任何端点则直接警告。典型应用场景排查 Webhook 是否收到事件、订阅创建/取消事件是否按时到达、端点配置是否正确。四、月度清理分析—复核—执行三段式脚本清理类操作统一遵循「先分析出文件 → 人工复核 → dry-run 试跑 → 正式执行」的安全流程避免误删有效权限。1. analyze-stale-claims.ts过期 Claim 安全分析只读npx tsx scripts/takeout/analyze-stale-claims.ts该脚本不删除任何数据只做只读分析源码注释明确标注 Does NOT delete anything。它并行拉取「Stripe 全部活跃订阅」与「Supabase 全部活跃 Claimunclaimed_at IS NULL最多 1 万条」然后逐条判定订阅 ID 不是sub_/in_开头 → 归入「无效订阅 ID」订阅 ID 存在于活跃订阅集合 → 归入「有效 Claim保留」否则 → 归入「过期 Claim清理候选」。分析结果写入tmp/README 记为/tmp/源码实际为仓库工作目录下的tmp/目录共生成 34 个文件文件内容active-subscriptions-ts.json去重后的全部活跃 Stripe 订阅 ID 列表valid-claims-ts.json应保留的 Claim含 id、subscription_id、product_id、GitHub 用户名、repository_name、created_atstale-claims-ts.json待清理 Claim 候选含team_slug字段并带 REVIEW CAREFULLY BEFORE CLEANING UP 警告invalid-claims-ts.json仅当存在无效订阅 ID 时才生成cleanup-summary-ts.txt汇总报告总数、按 product_id / repository_name 分组的过期 Claim 分布、前 20 条样本、文件清单与后续步骤汇总报告中特别强调安全原则未删除/修改任何数据、全部数据已备份为 JSON、可与 Stripe 后台交叉核验、执行清理前必须复核。典型应用场景定位已取消订阅的残留 Claim为清理做准备。2. cleanup-stale-claims.ts过期 Claim 批量标记# 先试跑 npx tsx scripts/takeout/cleanup-stale-claims.ts tmp/stale-claims-*.json --dry-run # 确认无误后正式执行 npx tsx scripts/takeout/cleanup-stale-claims.ts tmp/stale-claims-*.json脚本读取分析阶段产出的stale-claims-*.json将其中的 Claim ID 按每批 100 条分批执行supabase.from(claims).update({ unclaimed_at: new Date().toISOString() }).in(id, batch)源码见 cleanup-stale-claims.ts。关键安全机制--dry-run只打印「Would update N claims」与样本 ID不写库非 dry-run 时启动前有3 秒倒计时CtrlC可取消给操作者最后的反悔窗口分批执行并统计成功/失败数量失败批次会打印具体错误。典型应用场景对已复核的过期 Claim 执行批量回收将其标记为unclaimed。3. remove-discord-users.tsDiscord 角色批量移除# 先试跑 npx tsx scripts/takeout/remove-discord-users.ts tmp/discord-users-to-remove-*.json --dry-run # 确认后正式移除 npx tsx scripts/takeout/remove-discord-users.ts tmp/discord-users-to-remove-*.json脚本读取check-discord-sync.ts产出的discord-users-to-remove-*.json逐一调用 Discord REST APIdiscordClient.api.guilds.removeRoleFromMember移除用户的 Takeout 角色源码见 remove-discord-users.ts。两个工程化细节值得关注限速保护DELAY_MS 1000每处理一个用户后 sleep 1 秒规避 Discord 速率限制3 秒倒计时与 cleanup 脚本一致非 dry-run 正式执行前同样有确认缓冲期。典型应用场景复核discord-users-to-remove文件后批量回收已取消订阅用户的 Discord 访问权。五、标准运维流程速查README 提供了四组可直接照搬的运维流程检查同步是否健康npx tsx scripts/takeout/check-subscription-sync.ts npx tsx scripts/takeout/check-discord-sync.ts调试 Webhook 问题npx tsx scripts/takeout/check-webhook-events.ts月度过期 Claim 清理# 1. 分析 npx tsx scripts/takeout/analyze-stale-claims.ts # 2. 人工复核 tmp/ 下的分析文件 # 3. 清理 npx tsx scripts/takeout/cleanup-stale-claims.ts tmp/stale-claims-*.json月度 Discord 清理# 1. 检查 Discord 同步 npx tsx scripts/takeout/check-discord-sync.ts # 2. 人工复核 tmp/discord-users-to-remove-*.json # 3. 移除已取消订阅的用户 npx tsx scripts/takeout/remove-discord-users.ts tmp/discord-users-to-remove-*.json六、历史故障复盘unclaimProduct.ts 的「WHERE 缺失」事故README 记录了 2024 年 11 月修复的一个关键线上 Bug这是理解本套体系为何需要「巡检 清理」双保险的最佳案例。问题订阅取消时Webhook 更新 Claim 记录却遗漏了 WHERE 子句导致更新波及全表// BEFORE (BROKEN) await supabaseAdmin.from(claims).update({ unclaimed_at: Number(new Date()).toString(), }) // 这会更新数据库中 ALL 的 claims 记录修复为更新语句补充id条件并修正时间戳格式为 ISO 字符串// AFTER (FIXED) await supabaseAdmin .from(claims) .update({ unclaimed_at: new Date().toISOString(), }) .eq(id, claim.id) // ← 补上的 WHERE 子句对照当前源码 code/tamagui.dev/features/api/unclaimProduct.ts该修复已稳定落地循环遍历claimRes.data逐条update(...).eq(id, claim.id)。同批附加修复README 完整列出在unclaimRepoAccess()调用前补上await避免未等待的异步回收为没有team_slug的旧 Claim 增加回退逻辑默认按early-access团队移除源码 unclaimProduct.ts 中可见team_slug非字符串时console.warn并调用removeUserFromTeam(early-access, login)日期格式统一改为 ISO 字符串新增 Discord 角色移除订阅取消时同步回收 Takeout 角色。当前实现中unclaimDiscordAccess()会查询该订阅的discord_invitesPromise.allSettled并行移除所有关联用户的角色随后删除discord_invites记录见 unclaimProduct.ts。七、架构演进从「仓库协作者」到「团队授权」2024 年 11 月的另一项重要变更是授权模型整体迁移迁移前Before用户被逐个添加为单个仓库的协作者collaboratorClaim 记录中带有repository_name: takeout或unistack。迁移后After用户被加入统一的early-accessGitHub 团队团队统一持有所有相关仓库的访问权Claim 记录写入team_slug: early-access。这一迁移的收益显而易见新增/回收权限只需增删团队成员即可不必逐仓库维护协作者列表也避免了订阅频繁变动时的仓库级权限漂移。脚本中的TEAM_SLUG early-access、ORG_NAME tamagui常量check-subscription-sync.ts即是该模型在当前运维工具中的直接体现。迁移统计README 记录邀请 197 名活跃订阅者加入 GitHub 团队移除 40 名已取消订阅用户的 Discord Takeout 角色清理 590 条来自已取消订阅的过期 Claim修复 Webhook Bug防止后续再次产生过期 Claim 与 Discord 清理缺口。八、当前状态基线2024 年 11 月快照README 记录的时点数据可作为对账基线与容量参考指标数值活跃 Stripe 订阅~370GitHub 团队成员~350活跃 待接受邀请Discord Takeout 角色~27仅活跃订阅Webhooks正常工作Claim 创建正确写入team_slugClaim 清理取消时正确标记unclaimedDiscord 清理取消时移除角色并删除discord_invites说明以上为 README 记录的时点快照随着业务推进数值会持续变化建议以check-subscription-sync.ts/check-discord-sync.ts的实时输出为准。九、故障排查手册「有订阅但没有 Claim」可能原因用户尚未在官网点击 Claim Access订阅创建时 Webhook 调用失败用户在 Claim 体系上线之前订阅。解决方案提示用户访问 tamagui.dev 的账户页面手动领取权限。「用户不在 GitHub 团队」排查步骤确认用户是否已在官网完成领取claim运行check-subscription-sync.ts定位缺失用户用check-webhook-events.ts验证 Webhook 是否正常工作。「过期 Claim 持续堆积」排查步骤确认 unclaimProduct.ts 中的 Bug 修复已部署检查 Webhook 事件是否持续收到用check-webhook-events.ts查看 Webhook 日志中的报错。十、相关文件索引为便于继续深入阅读仓库源码以下是本体系的关键实现文件scripts/takeout/README.md本指南所依据的运维文档code/tamagui.dev/app/api/stripe/webhookapi.tsStripe Webhook 处理器事件校验、签名验证、事件分发code/tamagui.dev/features/api/unclaimProduct.ts取消授权逻辑GitHub 团队移除 Discord 角色回收code/tamagui.dev/features/user/claim-product.tsClaim 创建逻辑code/tamagui.dev/features/github/helpers.tsGitHub 团队管理含removeUserFromTeam等code/tamagui.dev/features/discord/helpers.tsDiscord 客户端与常量Takeout 角色 ID、服务器 IDcode/tamagui.dev/app/api/discord/channelapi.tsDiscord 频道管理数据库表claims、discord_invites、subscriptions。综上Tamagui 的 Takeout 权限体系是一个以「Stripe 为计费事实源、Supabase 为状态存储、GitHub 团队与 Discord 角色为交付载体」的闭环。运维人员只需掌握scripts/takeout/下的六个脚本即可独立完成从日常健康巡检到月度权限回收的全部工作而理解unclaimProduct.ts的历史 Bug 与修复则能帮助你在未来遇到同类同步问题时快速定位根因。【免费下载链接】tamaguiStyle React fast with 100% parity on React Native, an optional UI kit, and optimizing compiler.项目地址: https://gitcode.com/GitHub_Trending/ta/tamagui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考