我如何构建了一个系统,自动出售对私有 GitHub Repo 的访问权限

发布: (2026年2月8日 GMT+8 01:43)
13 分钟阅读
原文: Dev.to

Source: Dev.to

TL;DR

我需要出售对私有 GitHub 仓库的访问权限。ZIP 下载无法获得更新,许可证密钥过于繁琐,而且多个支付提供商拒绝了我的业务。于是我构建了一个全自动系统来处理整个流程:

  1. Payment webhook fires → signature verified
  2. GitHub username captured via OAuth
  3. Collaborator invite sent in seconds

它运行得很好,但事实证明 Polar.sh 已经内置了此功能,而且做得更好。我最终没有使用自己的版本,但在构建过程中学到了很多。

Source:

问题

我构建了一个想要出售的脚手架。产品已经完成——难点在于如何实际交付它

你创建了一个入门套件、脚手架、模板,开发者愿意为此付费。接下来怎么办?付款后如何把它交付给他们?

我评估的选项

选项优点缺点
Zip 文件下载简单,开箱即用没有更新,没有 git 历史,无法 pull/diff
License‑key 限制可以锁定访问需要自定义 CLI + 基础设施——过度复杂
npm 私有包开发者熟悉需要注册表认证 → 摩擦导致转化率下降
GitHub Sponsors(私有仓库)内置 GitHub 访问权限只能订阅,不适合一次性购买
通过支付平台手动交付少量销售时可行规模化后几乎不可能(例如凌晨 2 点的第 3 笔订单)

我真正想要的是极其简单客户付款 → 客户立即自动获得私有仓库的访问权限

支付提供商障碍

我的注册公司是一家设计与咨询公司。我尝试在 Lemon Squeezy 以及其他几个平台上注册以销售数字产品,但它们拒绝了我

  • 我说明我在销售一次性数字产品(模板代码、入门套件),而不是咨询服务。
  • 拒绝仍然存在,而且一些平台甚至相当无礼。

那时我只知道我需要一个能够在付款时触发 webhook的提供商,这样我就可以自行自动化其余流程。

灵感

我发现了一个做类似事情的项目:

  • 付款 webhook → GitHub 合作者邀请
  • 私有仓库,只读访问(克隆 + 拉取更新,完整的 git 历史)

他们收取了 30 美元。我想自己动手做——一举两得。

为什么可行

  • GitHub API 允许以编程方式添加协作者。
  • 任何支付提供商 只要触发 webhook 就可以启动此流程。
  • 权限可以设置为 pull(只读),这样客户可以克隆但不能推送。

Source:

我的解决方案架构

一个 独立的 Next.js 应用不依赖特定支付提供商

User clicks "Buy"
       |
GitHub OAuth (capture username)
       |
Payment checkout
       |
Webhook fires → verify signature → invite to repo → send email
       |
Customer has access in seconds

主要组件

组件描述
GitHub OAuth在结账前获取买家的 GitHub 用户名
Webhook handler接收付款确认,验证 HMAC‑SHA256 签名
GitHub API client自动发送仓库邀请
PostgreSQL记录每位客户(共 33 个字段)
Resend发送欢迎邮件

详细流程

1. 在 checkout 之前捕获 GitHub 用户名

支付提供商并不知道买家的 GitHub 用户名,所以我们必须先捕获它。

// OAuth 回调后,带着 GitHub 信息重定向到 checkout
const checkoutUrl = new URL(CHECKOUT_URL);
checkoutUrl.searchParams.set('gh_username', githubUser.login);
checkoutUrl.searchParams.set('gh_user_id', String(githubUser.id));

支付提供商会在 webhook 负载中包含这些字段,从而让我们在付款完成后准确知道要邀请谁。

2. 验证 webhook 签名

任何人都可以向你的 webhook 端点发送 POST 请求。如果不进行验证,攻击者就可以免费获取访问权限。

import { createHmac, timingSafeEqual } from 'crypto';

function verifySignature(payload: string, signature: string): boolean {
  const expected = createHmac('sha256', WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');

  return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}

注意事项

  • 使用 原始请求体,而不是解析后的 JSON —— 重新序列化会改变空白字符并导致签名失效。
  • 使用 timingSafeEqual 而不是 === —— 直接的字符串比较会泄露时间信息。

3. UPSERT 客户记录

客户可能会购买升级或第二件商品。如果没有 UPSERT,第二个 webhook 会因邮箱唯一约束冲突而失败。

INSERT INTO customers (email, name, amount_paid, ...)
VALUES (...)
ON CONFLICT (email) DO UPDATE SET
  amount_paid = EXCLUDED.amount_paid,
  order_id    = EXCLUDED.order_id,
  updated_at  = NOW();

4. 处理待处理的邀请

如果客户没有接受邀请,邀请会保持 pending 状态。超过 50 个待处理邀请 后,GitHub 会完全阻止新的邀请。

  • 我会跟踪邀请状态。
  • 我构建了一个后台面板,用于监控待处理邀请并手动重新发送或撤销它们。

5. 对 GitHub API 失败进行重试

GitHub API 可能会宕机、触发速率限制或出现网络波动。为你的模板付费的客户不应该只看到错误并被要求联系支持。

指数退避策略

尝试次数延迟
11 秒
22 秒
34 秒
48 秒
101 小时

错误分类

  • 可重试 – 网络超时、速率限制响应、5xx 错误 → 进入退避循环。
  • 不可重试 – 4xx 客户端错误(例如无效负载) → 记录并报警。

教训

  1. 支付提供商 对您出售的数字商品类型可能挑剔
  2. 签名验证 必须使用原始负载并进行时间安全比较。
  3. UPSERT(或等价操作)可防止买家多次购买时出现键重复错误。
  4. 未处理的 GitHub 邀请 可能会悄悄阻塞您——请监控并清理它们。
  5. 健壮的重试逻辑 在依赖第三方 API 时至关重要。

结论

我构建了一个全自动、与供应商无关的系统,能够:

  • 通过 OAuth 捕获买家的 GitHub 用户名。
  • 监听支付 webhook,并安全地进行验证。
  • 将买家以 只读协作者 的身份邀请到私有仓库。
  • 发送包含仓库链接的欢迎邮件。

它能够正常工作,但 Polar.sh 已经开箱即用地提供了此功能,且实现得更好,因此我最终转而使用了他们的服务。尽管如此,这次经历让我深入了解了 OAuth、Webhook 安全、GitHub API 以及可靠的自动化模式。

带退避的队列

  • 永久失败(例如 用户未找到仓库未找到401/403)→ 发送到死信队列进行人工审查。
  • 10 次失败尝试 后,项目会移至死信队列,并且我会收到通知。

GitHub 令牌交换端点

正确的端点是 https://github.com而不是 https://api.github.com

# WRONG – returns an HTML login page
POST https://api.github.com/login/oauth/access_token
# CORRECT – returns the token
POST https://github.com/login/oauth/access_token

我在使用 GitHub API 时形成的肌肉记忆让我总是指向 API 子域名,但 OAuth 流程实际上位于主站点。

技术栈概览

组件技术
框架Next.js 16 (App Router)
语言TypeScript(严格模式)
数据库PostgreSQL on Neon
GitHub APIOctokit
邮件Resend
验证Zod
测试Vitest(全部通过)

构建后可在任何托管服务商上运行。目前在 Vercel 免费层上运行,适用于低流量。

为什么这种方法有真正的优势

  • 即时更新 – 将修复推送到 main;每位客户只需 git pull – 无需重新下载 zip 文件。
  • 完整的 git 历史 – 客户可以查看为何以这种方式构建。
  • 默认只读pull 权限让他们可以克隆但不能向仓库推送。
  • 无需额外工具 – 只需要 git;不需要许可证密钥、自定义 CLI 或注册表认证。
  • 可撤销访问 – 需要收回费用?只需一次 API 调用即可移除协作者。

缺点

  • 50 个待处理邀请的上限 – GitHub 限制未完成邀请的数量。
  • 以 GitHub 为中心 – 绑定在 GitHub 上;不是通用解决方案。

对于向开发者销售开发者工具而言,这些缺点通常是可以接受的。

我最终改用的方案

在构建工具的过程中,我改用了 Polar.sh 来处理支付,因为他们接受了我的业务(而 Lemon Squeezy 不接受)。他们的平台已经涵盖了我原本要重新实现的所有功能:

  • 捕获 GitHub 用户名
  • 仓库邀请
  • 访问管理

简而言之,Polar 自动化了我想要构建的完整流程:

  1. 客户付款。
  2. Polar 捕获他们的 GitHub 用户名。
  3. Polar 发送仓库邀请。
  4. 客户获得访问权限。

我只需要获得批准,然后就可以打开此功能。

反思

我的工具可以正常工作:所有测试通过,流水线运行正常,但我从未需要部署它。不过,我学到了很多:

  • Webhook 签名验证
  • OAuth 流程
  • 带指数退避的重试系统
  • GitHub 合作者 API 的实际工作方式

这些细节是教程中学不到的。

开放式问题

我之所以构建它,是因为看到有人为类似的解决方案收取 $30,于是觉得自己也可以做一个。

  • 真的会有人觉得这有用吗?
  • 如果你在出售脚手架或入门套件,并且不想被锁定在 Polar 的内置解决方案中,这样的独立工具是否值得?
  • 你希望它开源吗?

请在评论中告诉我。我正在决定是发布它(开源/“请给我买杯咖啡”)还是仅作为学习项目保留。出售它似乎有点没有意义,但构建过程很有趣。

TL;DR

使用 Next.jsTypeScript 构建,并花了大量时间阅读 webhook 文档。 🚀

0 浏览
Back to Blog

相关文章

阅读更多 »