让你的 iOS App 支持教育优惠(上):获取配置变量

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(提供优惠代码)
  • 飞书机器人(可选,在程序出错时通知开发者)

最后的通知效果如下:

effect-overview-1
正常情况下用户接收到的通知邮件

effect-overview-2
服务出错时,用户和开发者分别接收到的通知

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)」标签下点击这个加号。首次进入此标签可能需要一些额外的确认,按照页面提示操作即可。

CleanShot 2023-08-02 at 16.07.43@2x

在新建 API 密钥对话框下,你可以任意为它命名,但请给「访问(Access)」项选择「App 管理(App Manager)」。

CleanShot 2023-08-02 at 16.11.05@2x
CleanShot 2023-08-02 at 16.13.25@2x

生成之后,请使用文本编辑器打开代码文件中的 wrangler.toml,然后分别将 JWT_ISSJWT_KIDAS_SECRET_KEY 字段替换为上图中 1、2、3 处获得的信息。格式参考如下:

CleanShot 2023-08-02 at 16.21.53@2x

获取App Store 的优惠代码 ID

作为示例,我为我的 Numpkin 设置了一个年度会员的 25% 优惠。如果你还没有设置过优惠,可以跟随这篇 App Store Connect 的帮助文档 来完成设置。

设置完成后,请在浏览器打开此优惠的页面,点击浏览器的导航栏,查看完整的 URL。URL 最后的一串 UUID 即为优惠代码 ID(形如xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)。

CleanShot 2023-08-02 at 16.39.54@2x

同样的,请使用文本编辑器打开 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)。

CleanShot 2023-08-02 at 17.39.21@2x

点击「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 的途径:

  1. 在飞书桌面端新建一个群组,不必邀请任何人
  2. 在群组「设置」页中,依次进入「群机器人」和「自定义机器人」(请使用桌面端操作,网页和手机端都无法添加自定义机器人)
    CleanShot 2023-08-03 at 15.07.34@2xCleanShot 2023-08-03 at 15.10.42@2x
  3. 设置机器人的名称和介绍(可以参考下图) CleanShot 2023-08-03 at 15.12.05@2x 4.点击「添加」后,飞书会提供一个 webhook 地址,复制该地址 CleanShot 2023-08-03 at 15.12.56@2x

使用刚才复制的 webhook 地址,替换 wrangler.toml 文件中的 FEISHU_ROBOT_URL 字段。

至此,我们基本获取了各项服务的密钥,完成了环境变量的配置。接下来,我们要开始部署服务。

继续阅读(下)篇

留下评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注