Apple 在前几周开启了今年的返校季优惠,作为一名 iOS 开发者,我心里冒出一个念头:能否给自己的 app 订阅启用教育优惠?也就是先验证用户的教育资格,然后向其分发 App Store 的优惠代码(Offer Code)。
要单纯地实现这个目的,其实很简单。我可以直接让用户使用教育邮箱/学生证件给我发送一封邮件,然后挨个回复优惠代码;甚至我可以不设任何验证,让学生用户「自觉」选择付费的价格。
但我就是有点心血来潮,想要动手实现一个全自动的优惠代码分发系统。没有考虑特别多的利弊,没有特别多的比较,就想搞一个这样的东西出来——就像想玩过家家的小朋友一样。
在一些思考之后,我决定让这个系统拥有以下的功能和特性:
- 通过教育邮箱验证资格:接收邮件,如果来信地址是教育邮箱,则向其发送优惠代码。要保证发送的代码还剩至少 7 天的有效期。
- 一次部署,尽量零维护:接入 App Store Connect API,优惠代码用完了之后,自动获取新的并保存到数据库。App Store 一次最低允许生成 500 个优惠代码,因此需要一次生成后,把所有代码保存起来。
- 频率限制:例如,同一个邮件地址每年只允许获取一次教育优惠。
- 错误通知与处理:如果用户因为资格不符或超过频率限制,发邮件通知用户;如果程序抛出异常,则告知用户程序出错,让其耐心等待开发者修复,同时通知开发者这个异常,方便及时修复并手动重发优惠代码。
- 简单低成本:使用 Cloudflare Worker,不配置服务器,免费额度足够涵盖绝大多数需求。
在探索和折腾之后,我成功使用了以下服务来完成这件事情:
- Cloudflare Worker(处理资格验证、代码分发、错误通知的逻辑)
- Cloudflare Email Routing(接收电子邮件)
- Cloudflare D1(保存优惠代码和兑换记录)
- Resend(发送电子邮件)
- App Store Connect(提供优惠代码)
- 飞书机器人(可选,在程序出错时通知开发者)
最后的通知效果如下:
Cloudflare 的以上三个服务均有比较足够的免费额度,Resend 每天可以免费发送 100 封邮件,App Store Connect API 、飞书机器人都不需要付费。因此对于中小体量的开发者来说,基本上可以无费用地实现这个功能。
需要说明的是,实现这个效果的途径很多,不同开发者可以开发和探索最适合自己的方案。
对于和我有类似背景和需求(比如域名托管在 Cloudflare,有一个在 App Store 上架的 app)的开发者,我把我的探索和部署过程分为上、下两篇博文来分享。
对于并没有类似背景朋友,后续配置起来可能有一些繁琐,需要一定的耐心。我会介绍步骤和代码的含义,你可以根据自己的情况,对其中的组件、步骤、代码进行增删或修改,以适应你自己的需求。
概览
我使用了两个 Cloudflare Worker,一个用来分发代码,它是这个系统的关键,进行所有的数据处理和响应。另一个是 Email Worker,用来响应邮件事件,它会在收到邮件时被邮件路由自动触发,并将收到的邮件地址发送到分发优惠代码的 Worker。
如果你不想实现接收邮件来触发代码分发,可以通过前端直接调用代码分发 Worker,而不必部署 Email Worker。
如果你也想实现接收邮件来触发,你需要有一个闲置或者已经绑定 Cloudflare Email Routing 的域名。由于该服务只允许绑定一级域名(如 numpkin.app)来接收邮件,不允许绑定 mail.numpkin.app 这样的二级域名,所以如果你已经使用一级域名绑定了自定域名邮箱服务,你可能需要将自定域名邮件服务切换到 Cloudflare Email Routing,或使用一个新域名。
2023 年 10 月更新:Cloudflare Email Routing 已于 10 月 26 日支持子域名绑定,详见这篇博客。
在这篇文章中,我还是会完整介绍通过接收邮件触发的方式。
免责声明
这篇分享的所有步骤和代码都是公开的,尽管我已尽量保证安全性,但仍然可能存在没有预见的隐患。请阅读代码并理解各个步骤,自行判断得失、合规性、安全性后再部署。作者不对因此可能带来的任何风险和事故负任何责任。
配置环境变量
我们需要先进行一些准备工作:获取程序运行必需的配置变量,例如 API 密钥等。
获取代码仓库
首先,请先从项目仓库clone 或下载并解压源代码到本地。
git clone https://github.com/Hzao/edu-offer-dispatcher
接下来我们会获取需要的变量,作为环境变量修改到 wrangler.toml
文件中。
获取 App Store Connect API 密钥
我在代码中为你写好了 JWT 的签名、优惠代码的获取逻辑,但需要你生成你自己的 App Store Connect API Key,从而获得向 App Store Connect 申请代码的权限。
首先,请你登入 App Store Connect,进入「用户和访问(Users and Access)」。在「密钥(Keys)」标签下点击这个加号。首次进入此标签可能需要一些额外的确认,按照页面提示操作即可。
在新建 API 密钥对话框下,你可以任意为它命名,但请给「访问(Access)」项选择「App 管理(App Manager)」。
生成之后,请使用文本编辑器打开代码文件中的 wrangler.toml
,然后分别将 JWT_ISS
、JWT_KID
、AS_SECRET_KEY
字段替换为上图中 1、2、3 处获得的信息。格式参考如下:
获取App Store 的优惠代码 ID
作为示例,我为我的 Numpkin 设置了一个年度会员的 25% 优惠。如果你还没有设置过优惠,可以跟随这篇 App Store Connect 的帮助文档 来完成设置。
设置完成后,请在浏览器打开此优惠的页面,点击浏览器的导航栏,查看完整的 URL。URL 最后的一串 UUID 即为优惠代码 ID(形如xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
)。
同样的,请使用文本编辑器打开 wrangler.toml
,然后用此优惠代码 ID 替换文件中的 OFFERCODE_ID
字段。
获取 Resend API 密钥
Resend 是一个邮件发送服务,它允许开发者通过一个 http 请求来发送电子邮件。
为了向用户发出包含兑换代码的邮件,你需要注册一个 Resend 账号。然后,添加你的域名,这个域名将会是给用户发送邮件时使用的域名。我建议在这里使用二级域名(如 mail.numpkin.app),因为待会我们需要把一级域名留给 Cloudflare 。注册完成后按照首页指引,添加几条 DNS 记录即可绑定。如果需要帮助,请参考 Resend 文档。
除了 numpkin.app 外,我还为我的 Numpkin app 注册了 numpk.in。由于 numpkin.app 已经用于接收用户邮件的邮箱,我暂时不想对此有改动,所以我想使用 noreply@mail.numpk.in 向用户发送兑换代码,因此绑定了二级域名 mail.numpk.in。
接下来,在 API Keys 标签下,点击「Create API Key」来创建新密钥。为了安全,请仅为这个 API Key 选择 「Sending access」的权限,并选择你刚刚绑定的二级域名(如 mail.numpk.in)。
点击「Add」后,你会获得一个”re_”开头的密钥,请复制此密钥,并替换 wrangler.toml
文件中的 RESEND_APIKEY
字段。
设置 Worker 认证密钥(可选)
如前面介绍的,我们要使用 Cloudflare Email Worker 来调用我们的优惠分发 Worker。
为了保证安全性,不让优惠代码分发 Worker 被滥用,防止没有请求过优惠代码的用户收到我们发送的邮件,我们需要一个用于认证的密钥,保证优惠分发 Worker 只在收到用户邮件时,才被 Email Worker 调用。
请在 wrangler.toml
文件中的 EMAILWORKER_AUTHKEY
字段中填入足够长的随机字符串,例如 AS@#&!@NXS31JDdXJSiqpsdaxjxjs#a2Hjsj
(仅举例,请勿使用此字符串作为认证密钥)。
如果不需要这额外的安全性,或者需要直接从前端调用 Worker,可以不进行这一步。
设置飞书机器人链接(可选)
通常,我们的程序可以自动运行、获取新代码、处理错误。但在一些情况下,例如 App Store Connect API 故障、数据库故障等,仍然有可能抛出错误。
在这个时候,我们希望能够得到通知并马上修复。为了让消息更及时地传达,我选择使用飞书机器人来通知我。
如果你想使用其他通知推送服务(如 Bark、Email、Telegram Bot),可以根据需求修改 worker.ts
文件中的 notifyDeveloperOfError
函数。
以下是获取飞书自定义机器人 Webhook 的途径:
- 在飞书桌面端新建一个群组,不必邀请任何人
- 在群组「设置」页中,依次进入「群机器人」和「自定义机器人」(请使用桌面端操作,网页和手机端都无法添加自定义机器人)
- 设置机器人的名称和介绍(可以参考下图) 4.点击「添加」后,飞书会提供一个 webhook 地址,复制该地址
使用刚才复制的 webhook 地址,替换 wrangler.toml
文件中的 FEISHU_ROBOT_URL
字段。
至此,我们基本获取了各项服务的密钥,完成了环境变量的配置。接下来,我们要开始部署服务。
继续阅读(下)篇。