Unkey 定价目录与 Stripe 对账工具(pricing):以代码为唯一事实来源的账单配置管理

发布时间:2026/9/18 2:18:07
Unkey 定价目录与 Stripe 对账工具(pricing):以代码为唯一事实来源的账单配置管理 Unkey 定价目录与 Stripe 对账工具pricing以代码为唯一事实来源的账单配置管理【免费下载链接】unkeyThe Developer Platform for Modern APIs项目地址: https://gitcode.com/GitHub_Trending/un/unkey导读本文讲解 Unkey 仓库中tools/pricing目录下的 Stripe 计费目录对账工具它将 Unkey 的全部定价信息Deploy 套餐、用量计费 Meter、遗留 API 产品与 Webhook 端点以类型化 Go 代码的形式声明为“期望状态”并据此对真实的 Stripe 账户执行对账reconcile。读完本文你将掌握该工具的四大子命令plan/apply/verify/export的使用方法、定价目录的数据结构设计、Stripe 不可变对象的“重新定价”流程、按环境隔离的凭据注入方案以及仓库如何用无网络测试为每一分钱价格上锁。工具定位Stripe 是唯一状态代码是唯一事实来源pricing是一个面向操作者的命令行工具核心思想可以浓缩为两句话Stripe 是唯一状态state工具不维护任何本地状态文件真实 Stripe 账户中实际存在的对象就是全部事实代码是唯一事实来源source of truth期望的账单目录以类型化 Go 数据声明在catalog.go中工具负责把 Stripe 对账到这份目录上。Stripe 的计费对象是不可变、只追加immutable and append-only的你不能修改一个已存在的 Price只能发布新 Price并把它的lookup_key迁移过去。pricing严格遵循这一模型对账永远是增量收敛且永不删除——费率变更时工具会新建一个不可变 Price将lookup_key转移到新 Price 上再把旧 Price 归档archive。这一核心流程可以在internal/reconcile/reconcile.go的包注释与ensurePlan/ensureMeter实现中直接看到。它管理什么四类 Stripe 对象1. 套餐PlansDeploy 平台固定月费套餐是 Deploy 的许可制月度订阅费每个套餐对应一个固定 Pricelookup_key形如plan.key例如plan.pro。当前目录中声明了三个套餐金额单位均为美分见catalog.goKey名称月费美分月费美元starterStarter500$5/moproPro2,500$25/mobusinessBusiness5,000$50/mo每个套餐费率 Price 都携带plantier元数据见pricing.go中定义的PlanSignalKey仪表盘dashboard的 Stripe Webhook 会读取该元数据来检测用户的 Deploy 套餐即detectDeployPlan。该元数据采用失败关闭fail closed语义一个没有plan键的套餐费率 Price 会被读取为“无 Deploy 套餐”因此每次重新定价reprice时新 Price 也必须带上它绝不能丢失。2. Meter 与计量费率Meters metered prices按用量计费用量计费以usage.key作为lookup_key例如usage.cpu_seconds费率使用 Stripe 的unit_amount_decimal字段支持**亚美分sub-cent**精度因此CentsPerUnit是float64。当前目录声明了五个 Meter见catalog.goKey显示名事件名聚合方式每单位费率美分实际单价cpu_secondsCPU secondscpu_secondslast0.0006944$0.000006944 / vCPU-秒memory_gib_secondsMemory GiB-secondsmemory_gib_secondslast0.0003472$0.000003472 / GiB-秒egress_public_gibEgress GiBegress_public_giblast5$0.05 / GiBdisk_gib_secondsDisk GiB-secondsdisk_gib_secondslast0.000006$0.00000006 / GiB-秒active_keysActive keysactive_keyslast0.2$0.002 / 活跃 Key每个 Meter 对应一个 Stripe Product保证发票上每行用量都有独立标签其事件契约是固定的计费 Worker 通过stripe_customer_id将用量映射到客户并把数值放在value字段中见pricing.go的MeterCustomerMappingKey/MeterValuePayloadKey。聚合方式Aggregation是 Stripe 固定集合仅三种见pricing.goAggregationLastlast保留周期内收到的最后一个值适用于 Worker 每 tick 上报一次“周期至今累计值”的场景当前 CPU/内存/出口流量/磁盘/活跃 Key 均如此AggregationSumsum累加周期内收到的所有值适用于 Worker 按事件增量上报AggregationCountcount只统计周期内收到的事件次数忽略 value。3. API 产品APIProducts遗留许可制 API 套餐与附加项这是 Unkey 遗留的许可制 API 分层套餐与附加项采用固定月费费率 Price 不携带lookup_key而是通过产品元数据pricing_key标识并用产品的default_price指向当前费率。分层套餐带有配额元数据每月请求数、日志/审计日志保留天数附加项则没有见catalog.go与reconcile.go中的apiProductMetaKey名称月费美分每月请求配额pro_250kAPI Pro 250k2,500250,000pro_500kAPI Pro 500k5,000500,000pro_1mAPI Pro 1M7,5001,000,000pro_2mAPI Pro 2M10,0002,000,000pro_10mAPI Pro 10M25,00010,000,000pro_50mAPI Pro 50M50,00050,000,000pro_100mAPI Pro 100M100,000100,000,000enterpriseEnterprise225,000400,000,000dedicated_supportDedicated Support Channel12,5000附加项无配额sla_feeSLA fee800,0000附加项无配额配额元数据包含quota_requests_per_month、quota_logs_retention_days与quota_audit_logs_retention_days保留天数统一为APIRetentionDays 90见pricing.go。4. Webhook 端点按环境声明Webhook 端点按环境sandbox/canary/production分别声明在webhooks.go中。生产与 canary 有稳定的公网主机sandbox 没有固定主机预览部署名称随机因此不声明任何端点——需要测试时再手动指向某个预览部署。当前端点配置如下productiondashboardhttps://app.unkey.com/api/webhooks/stripe监听订阅与发票事件controlhttps://control.unkey.cloud/webhooks/stripe监听invoice.createdcanarydashboardhttps://app.unkey-canary.com/api/webhooks/stripeVercelProtected: truecontrolhttps://control.unkey-canary.com/webhooks/stripe。两个事件列表被刻意保持与下游处理器同步见webhooks.go的注释DashboardWebhookEventscustomer.subscription.created/updated/deleted、invoice.payment_failed、invoice.payment_succeeded对应仪表盘的 Next.js 计费处理器ControlWebhookEventsinvoice.created对应 Go control-plane 的 Stripe 处理器svc/ctrl/api/webhooks/stripe/stripe.go。Vercel 部署保护的处理canary dashboard 位于 Vercel 部署保护之后请求在到达处理器之前就会被拒绝而 Stripe 无法发送自定义请求头因此工具将项目的 “Protection Bypass for Automation” 密钥作为查询参数?x-vercel-protection-bypass...拼接到投递 URL 上见webhooks.go的DeliveryURL方法。密钥本身来自每环境变量VERCEL_PROTECTION_BYPASS_ENV存在本地.env中绝不会写进源码受保护端点缺少密钥时直接报错而不是回退到裸 URL从而保证apply永远不会悄悄把在线端点上已生效的投递参数剥掉、导致事件投递中断。Webhook 端点的身份是去除查询串后的基础 URL对账按基础 URL 匹配已有端点而非重复创建重复创建会产生第二个签名密钥并导致每个事件被投递两次URL 更新则在匹配到的端点上原地修改从而保留其既有签名密钥。身份模型lookup_key与元数据而不是 Stripe 生成的 id整个对账的身份体系有两个稳定标识符见pricing.goPrice 的身份是lookup_key命名空间前缀固定plan.如plan.pro与usage.如usage.cpu_secondsProduct 的身份是元数据managed_byunkey-pricing键为 snake_case、值为 kebab-case二者都被写入真实 Stripe 对象并由对账逻辑匹配是稳定标识符不能改写否则已打标的对象会变成孤儿pricing_key。工具从不以 Stripe 生成的 id 作为键因此应用层与工具层可以引用稳定的字符串。命令行用法plan / apply / verify / export通过 mise 在仓库任意位置运行任务定义会自动加载.envmise run pricing plan # 展示 diff不写任何东西沙箱模式 mise run pricing apply --env production # 让 Stripe 与目录一致 mise run pricing verify --env canary # 若 Stripe 发生漂移则以非零退出码退出 mise run pricing export --env production # 打印仪表盘所需的 env 配置块--env取值为sandbox默认、canary或production。各子命令行为如下实现见cmd/pricing/commands.goplan只读对账输出目录与 Stripe 的差异彩色 diff不产生任何写操作apply先展示计划dry-run若无变更则直接退出对production执行时要求你手动输入环境名production以确认自动化场景可加--yes跳过确认每次写操作落盘后实时流式打印进度中途失败时错误会指明是哪个对象没写成功新建 Webhook 端点时会把签名密钥打印一次verify只读对账若存在任何漂移含孤儿对象则以非零退出码失败适合作为 CI 门禁export从真实 Stripe 读取当前目录并渲染出仪表盘所需的KEYvalue环境变量块详见下文。开发循环中可直接在tools/pricing目录下跑测试无网络依赖cd tools/pricing go test ./...导出export输出什么export生成的 env 块见internal/export/export.go包括每个套餐的STRIPE_LOOKUP_DEPLOY_KEYplan.key如STRIPE_LOOKUP_DEPLOY_PROplan.pro每个 Meter 的STRIPE_LOOKUP_DEPLOY_METER_SUFFIXusage.key后缀映射为CPU/MEMORY/EGRESS/DISK/ACTIVE_KEYS未知 Meter 回退为大写 KeyAPI 产品按类别导出为STRIPE_PRODUCT_IDS_PRO与STRIPE_PRODUCT_IDS_ENTERPRISE两个逗号分隔的产品 id 列表附加项如sla_fee不导出若本次apply恰好创建了 dashboard 端点追加STRIPE_WEBHOOK_SECRETsigning secret。由于仪表盘在运行时按lookup_key解析当前激活 Price重新定价迁移lookup_key后无需重新 export同理API 产品 id 在运行时解析default_price费率变更也无需重新导出。凭据从环境变量到 AWS Secrets Manager简单路径STRIPE_SECRET_KEYsandbox 与本地开发只需导出自己的 Keyexport STRIPE_SECRET_KEYrk_test_...生产路径AWS Secrets Manager未设置STRIPE_SECRET_KEY时工具通过awsCLI 从 AWS Secrets Manager 读取密钥secret 的api_key字段secret 名默认unkey/stripe并依次按下述规则选择 profileAWS_PROFILE标准 AWS 变量否则使用每环境变量AWS_PROFILE_ENV如AWS_PROFILE_PRODUCTION结合项目生成的 AWS 配置这类 profile 通常命名为unkey-account-role若 SSO 会话过期自动执行aws sso login --sso-session unkey注意是 SSO会话而非每账户 profile可用AWS_SSO_SESSION覆盖AWS_REGION默认us-east-1与STRIPE_SECRET_ID默认unkey/stripe用于账户不同的场景。这些解析逻辑全部集中在internal/stripeenv/client.go的resolveKey/awsProfile中。本地.env文件Profile 是部署相关的因此放在本地、被 gitignore 的.env中而不是源码里。复制.env.example为.env并填写AWS_PROFILE_SANDBOX AWS_PROFILE_CANARY AWS_PROFILE_PRODUCTION # Vercel Protection Bypass for Automation 密钥每环境一份当前为 canary dashboard VERCEL_PROTECTION_BYPASS_CANARY # 可选覆盖默认值如下 # AWS_SSO_SESSIONunkey # AWS_REGIONus-east-1 # STRIPE_SECRET_IDunkey/stripemise run pricing任务会自动加载该文件因此填好后mise run pricing plan --env production即可直接工作。环境守卫与最小权限 Key工具拒绝在非生产环境使用 live Key也拒绝在生产环境使用 test Key见internal/stripeenv/client.go的guardKeyMatchesEnv从根本上杜绝“把错误的账户指向错误的目录”。为每个环境创建restricted受限Key以获得最小权限详细步骤见tools/pricing/docs/stripe-api-key.md在 Stripe DashboardDevelopers → API keys → Create restricted key中将以下四类资源设为Write其余全部保持None选择器中的分区资源权限CoreProductsWriteBillingPricesWriteBillingBilling MetersWriteWebhook EndpointsWebhook Endpoints, Event DestinationsWriteWrite已包含读权限因此同一个 Key 即可满足plan/verify/export/apply。不要误开以下“近似项”Billing Meter Events / Billing Meter Event Adjustments用于上报用量本工具只管理 Meter 定义、Usage Records、Core 下的 Events、以及 Customers / Subscriptions / Invoices / Charges and Refunds / Coupons / Credit Grants本工具绝不触碰保持 None 才能限制爆炸半径。密钥模式上sandbox / canary 用 test-mode Keyrk_test_…production 用 live-mode Keyrk_live_…。若希望 CI 漂移检查永不持有写密钥可为plan/verify/export单独创建一个四类资源均为Read的只读 Key写 Key 只留给apply。修改一个价格的标准流程改价必须走“改代码 改测试钉 apply”三步编辑catalog.go中的金额美分在同一个变更中更新pricing_test.go中对应的钉值pin——这些测试是“小数点位移守护”完全无网络运行手滑写错的金额在到达 Stripe 之前就会被 CI 拦下运行mise run pricing apply工具创建新的不可变 Price、把lookup_key迁移过去、归档旧 Price。测试的设计刻意把want映射中的每个金额都手写重复一遍因为拿Desired()和它自身比较是同义反复手写重复才是能抓住小数点移位回归的关键。相关测试包括TestPlanRates/TestMeterRates/TestAPIProductRates逐项钉住每个金额、名称、事件名、聚合方式与lookup_key格式TestMeterAggregationValid拒绝空值或未知聚合Stripe 只接受last/sum/count零值会静默发送空字符串TestWebhookDeliveryURL钉住 Vercel 绕过行为——生产端点不允许被标记为VercelProtected受保护端点缺密钥必须报错且密钥需经 URL 转义s3cret/value→s3cret%2F%2BvalueTestNoDuplicateKeys保证目录内无重复lookup_key。新结账checkout会立即使用新价格已有订阅则继续使用旧价格直到应用层把它们重新指向新价——这是应用侧的工作本工具不做。孤儿对象Orphans只报告不处理孤儿是指在我们的命名空间内、但目录已不再声明的对象plan./usage.前缀的 Price或带有managed_byunkey-pricing标签但pricing_key已从catalog.go移除的 Product。detectOrphans见internal/reconcile/reconcile.go只报告孤儿、绝不触碰plan会列出它们verify会因它们失败最终由操作者决定如何处理。手工创建与命名空间外的遗留对象会被忽略。目前各环境的命名空间边界由PlanLookupPrefix/UsageLookupPrefix常量定义目录中的 Price 与 Product 通过managed_by元数据划分归属。已知边界与注意事项CaveatsWebhook 签名密钥只在创建时返回一次Stripe 仅在端点创建时返回签名密钥。apply会在创建那一刻打印一次verify与export无法读回。现有手工创建的生产端点按 URL 被收养而无需找回密钥因此线上STRIPE_WEBHOOK_SECRET保持原样继续工作。Meter 无法删除Stripe 只能停用deactivateMeter 而不能删除因此 Meter 清理不自动化。残留的 Meter 会以孤儿的usage.keyPrice 形式暴露出来——这正是需要手工清理的信号。control 端点不要提前 applycontrol-plane 处理器只有在 Stripe 密钥配置完成后才会注册路由/webhooks/stripe注意没有/api前缀与 dashboard 不同。在该主机上的处理器真正上线前不要 apply control 端点否则 Stripe 会对 404 反复重试并最终禁用该端点。源码结构速览tools/pricing/ ├── catalog.go # 期望目录金额纯数据 ├── pricing.go # 类型、常量与 Stripe 契约仅标准库、零依赖 ├── pricing_test.go # 无网络的金额钉值测试与行为测试 ├── webhooks.go # 按环境的 Webhook 端点声明 ├── cmd/pricing/ # CLI 入口main.go与四个子命令实现commands.go ├── internal/ │ ├── reconcile/ # 对账核心diff、apply、孤儿检测 │ ├── stripeenv/ # 按环境构建 Stripe client 与密钥解析 │ └── export/ # 渲染仪表盘 env 块 └── docs/stripe-api-key.md # restricted Key 的创建与权限指引pricing.go所在包刻意保持标准库零依赖见其包注释这样pricing_test.go可以在无网络、无 SDK 的情况下钉住每一个金额真正与 Stripe 通信的对账与导出逻辑被隔离在internal/*包中职责清晰。pricing的设计模式——不可变对象 lookup_key迁移 只增不删 元数据归属 无网络测试钉值——是“用代码管理计费目录”这一场景的完整参考实现可直接迁移到任何以 Stripe 为计费后端的项目。【免费下载链接】unkeyThe Developer Platform for Modern APIs项目地址: https://gitcode.com/GitHub_Trending/un/unkey创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询