Shopify App Pricing 实战:使用 Gadget 配置订阅计费与按用量计费

文章目录

如果 Shopify 应用只收取固定月费,传统的订阅计费逻辑通常比较简单。

但当应用需要根据实际使用量收费时,事情就会复杂很多。例如:

  • 每生成一个折扣码收费一次。
  • 每处理一张图片收费一次。
  • 每次调用 AI 功能收费一次。
  • 根据客户使用量分级收费。
  • 采用固定月费加额外使用费。
  • 为不同商家提供不同套餐。

Shopify 现在提供了统一的 Shopify App Pricing,用于配置固定订阅、按用量计费和组合套餐。App Events API 则负责接收应用上报的使用事件,并将这些事件用于应用分析或按用量计费。

本文使用 Gadget 的折扣码生成器作为基础项目,完成一个带计费功能的 Shopify 嵌入式应用。

应用的示例逻辑是:

商家打开应用
→ 检查当前是否有有效套餐
→ 没有套餐则跳转到定价页
→ 有套餐则进入应用
→ 商家生成折扣码
→ 应用上报一次 generate-discount 使用事件
→ Shopify 根据套餐规则计算费用

本项目实际演示了什么

从折扣码生成器开始

字幕中的项目不是从空白应用开始,而是 Fork 了一个 Gadget 折扣码生成器模板。

这个模板已经包含:

  • Shopify 嵌入式应用。
  • React 前端。
  • Gadget Node.js 后端。
  • Discount Code 数据模型。
  • 生成折扣码的 Action。
  • Shopify 应用连接。
  • Development Store 安装流程。

应用的原始功能很简单:商家点击按钮后,应用生成一条折扣码,并将它保存到 Gadget 数据库。

为生成折扣码增加计费

在计费版本中,每当商家成功生成一条折扣码,应用就会上报一次使用事件:

generate-discount

这个事件可以被 Shopify App Pricing 中的按用量套餐用作计量单位。

例如:

每生成一条折扣码,收费 1 美分

实际价格只是教程示例,生产应用应根据目标客户、服务成本和 Shopify 应用定价策略进行设计。

当前 Shopify App Pricing 的计费模式

固定周期订阅

固定订阅适合:

  • 月费套餐。
  • 年费套餐。
  • 不限制使用量的基础服务。
  • 按功能划分的标准套餐。

例如:

Monthly Plan:每月 10 美元
Yearly Plan:每年 100 美元

Shopify App Pricing 支持月度和年度订阅,也可以为年度套餐设置折扣。

按用量计费

按用量计费适合:

  • 按生成次数收费。
  • 按处理数量收费。
  • 按 AI 调用次数收费。
  • 按存储量收费。
  • 按导入商品数量收费。

本项目中,计量单位是:

generate-discount

每上报一次这个事件,就代表应用完成了一次可计费操作。

组合套餐

组合套餐由两部分组成:

固定月费 + 按用量收费

例如:

每月 5 美元基础费用
+
每生成一条折扣码收费 1 美分

这种模式适合既需要稳定基础收入,又需要根据实际使用量收费的应用。

免费试用和零美元测试套餐

Shopify App Pricing 支持免费试用和零美元测试套餐。

开发阶段可以创建一个只允许测试店铺使用的私有套餐:

Monthly Test Plan:0 美元

这样可以测试:

  • 套餐选择页面。
  • 商家授权流程。
  • 订阅状态读取。
  • 应用重定向。
  • 应用事件上报。

不需要让开发者为自己的测试操作支付真实费用。

创建 Gadget Shopify 应用

Fork 折扣码生成器模板

打开 Gadget,使用折扣码生成器模板创建项目。

项目名称可以使用:

billing-tutorial

如果名称已经被使用,可以换成:

billing-tutorial-dev

创建完成后,检查项目结构。

查看 Discount Code 模型

折扣码模型通常包含:

Discount Code
├── Shop
└── Code

其中:

  • Shop 表示折扣码属于哪个 Shopify 店铺。
  • Code 保存生成的折扣码文本。

应用的核心 Action 会生成折扣码,并将它写入 Gadget 数据库。

连接 Shopify Development Store

打开 Gadget 项目的 Shopify 连接设置,创建或连接 Shopify 应用。

然后:

  1. 登录 Shopify 账户。
  2. 创建开发应用。
  3. 选择 Shopify Development Store。
  4. 安装应用。
  5. 在 Shopify Admin 中打开嵌入式应用。
  6. 点击生成折扣码。
  7. 确认折扣码能够正常创建。

完成这一步后,先确认原始应用工作正常,再开始添加计费逻辑。

配置开发和生产环境

Gadget 的两个环境

Gadget 项目通常包含:

  • Development Environment。
  • Production Environment。

开发环境用于:

  • 测试计费。
  • 测试应用事件。
  • 测试套餐跳转。
  • 测试 Shopify API。
  • 测试数据库和前端。

生产环境用于正式商家和正式计费。

开发和生产应用要分开

生产应用不应该直接使用开发应用的配置。

建议分别使用:

Shopify Development App
→ Gadget Development Environment

以及:

Shopify Production App
→ Gadget Production Environment

生产环境还需要单独配置:

  • Shopify Client Secret。
  • 应用 URL。
  • Webhook。
  • API 权限。
  • App Pricing 套餐。
  • App Events 配置。

配置 Shopify App Pricing

打开应用分发设置

进入 Shopify Partner 或 Dev Dashboard,打开目标应用的分发和定价设置。

当前 Shopify App Pricing 用于配置应用套餐、订阅和按用量计费。

不同版本的 Shopify 管理界面名称可能略有变化,但核心流程是:

打开应用
→ 进入 Distribution 或 App Pricing
→ 创建应用定价方案
→ 配置套餐
→ 保存并发布配置

Shopify App Pricing 已经取代 Managed Pricing,今后的应用计费应优先使用 Shopify App Pricing 流程。

创建私有测试套餐

开发阶段先创建一个 Private Plan。

例如:

Plan Name:Monthly Test
Display Name:Monthly Test
Price:0

为私有套餐添加允许访问的测试店铺域名,例如:

testing-development-store.myshopify.com

私有套餐只对指定测试店铺可见,适合:

  • 开发阶段测试。
  • 内部演示。
  • QA 验证。
  • 不希望真实收费的应用测试。

如果没有设置测试店铺域名,应用的定价页可能不会显示这个私有套餐。

创建固定订阅套餐

创建一个月度和年度固定订阅套餐。

示例:

Monthly:每月 10 美元
Yearly:每年 100 美元

年度套餐可以通过较低的年度总价提供折扣。

生产应用还可以增加:

  • 免费试用期。
  • 不同功能等级。
  • 不同商家规模的套餐。
  • 更高额度的高级套餐。

创建组合计费套餐

创建一个同时包含固定费用和用量费用的套餐。

示例:

基础费用:每月 5 美元
用量费用:每生成一条折扣码收费 1 美分

创建用量收费部分时,需要填写一个内部 Event Handle:

generate-discount

这个值非常重要。

它必须与代码发送给 App Events API 的 Event Handle 完全一致。

以下两个值不一致,就无法正确计量:

套餐中:generate-discount
代码中:generate_discount

推荐在代码和定价配置中统一使用小写短横线格式。

创建应用定价页面

使用 Shopify 提供的定价页面

Shopify App Pricing 会为应用提供定价选择页面。

页面中可以显示:

  • 套餐名称。
  • 月费或年费。
  • 免费试用。
  • 按用量收费规则。
  • 当前测试店铺可以使用的私有套餐。
  • 商家选择和批准套餐的入口。

应用不应该自己伪造一套付款流程,而应使用 Shopify 的应用定价和订阅批准流程。

测试店铺域名很重要

如果私有测试套餐没有绑定正确的店铺域名,测试店铺可能看不到这个套餐。

检查:

  • Shopify 店铺的 .myshopify.com 域名。
  • Partner Dashboard 中填写的域名。
  • 应用当前连接的店铺。
  • Development App 是否与测试店铺匹配。

不要只使用店铺显示名称。定价配置通常需要使用准确的店铺域名或 Shopify 要求的店铺标识。

在应用中检查订阅状态

为什么需要检查当前套餐

应用不能只把定价页放在那里,却允许所有用户直接使用收费功能。

应用打开时,应该先检查当前安装是否存在有效订阅。

基本流程是:

应用加载
→ 获取当前 Shopify 店铺
→ 查询当前 App Installation
→ 读取 active subscriptions
→ 找到有效订阅
→ 进入应用

如果没有有效订阅:

跳转到 Shopify 应用定价页

查询当前应用安装

可以通过 Shopify Admin GraphQL API 查询当前应用安装信息和有效订阅。

需要关注的信息包括:

  • 当前应用安装。
  • 当前有效订阅。
  • 订阅状态。
  • 套餐名称。

应用可能同时存在:

  • 已取消的旧订阅。
  • 当前有效订阅。
  • 切换套餐中的临时记录。

因此,不能只检查订阅列表是否为空,而要检查订阅状态是否为有效状态。

判断是否有有效套餐

后端加载逻辑可以按照以下思路处理:

读取 active subscriptions
→ 遍历订阅列表
→ 找到状态为 active 的订阅
→ 获取当前套餐名称
→ 返回套餐信息

如果找到了有效订阅:

允许进入应用

如果没有找到有效订阅:

返回定价页面地址

不要只根据套餐名称判断权限。套餐名称可能被商家或管理员调整,真正重要的是订阅状态和应用定价配置。

将未订阅用户重定向到定价页

嵌入式应用的重定向问题

Shopify 嵌入式应用运行在 iframe 中。

如果直接在 iframe 内打开定价页面,可能会导致定价页面仍然嵌在应用内部,用户体验不正确。

因此,需要让重定向跳出 iframe,在 Shopify Admin 的完整页面中打开定价流程。

处理三种页面状态

应用加载时可以处理:

正在加载

显示全页面 Loading 状态。

正在检查订阅状态

没有订阅

显示:

正在跳转到定价页面

然后跳转到 Shopify App Pricing 页面。

已有订阅

显示正常的应用界面。

显示当前套餐

当用户有有效订阅后,可以在应用顶部显示当前套餐:

Current plan: Monthly Test

同时添加一个按钮:

Pricing Plans

点击后可以回到 Shopify 应用定价页面,方便商家升级或切换套餐。

使用 App Events API 记录用量

什么是 App Events API

App Events API 用于向 Shopify 上报应用中发生的事件。

它可以记录:

  • 可计费事件。
  • 普通应用活动。
  • 功能使用次数。
  • 应用指标。
  • 商家行为数据。

如果使用 Shopify App Pricing 的按用量套餐,App Events API 就可以作为自动计量入口。

创建计量事件

本项目将“生成折扣码”定义为一个事件:

generate-discount

每当商家成功生成一条折扣码,应用就上报一次事件。

如果一次操作生成多条折扣码,也可以通过事件属性中的数量值记录实际数量。

App Event 与套餐 Event Handle 必须一致

定价套餐中的计量句柄:

generate-discount

代码上报的事件句柄也必须是:

generate-discount

它们必须完全一致,包括:

  • 字母大小写。
  • 连字符。
  • 空格。
  • 下划线。
  • 拼写。

建议将事件句柄定义为共享常量,避免在不同文件中手动重复输入。

在 Gadget 中配置 Shopify Client Secret

创建环境变量

在 Gadget 项目的 Environment Variables 中添加:

SHOPIFY_CLIENT_SECRET

将它设置为 Secret。

不要把 Shopify Client Secret 写在:

  • React 前端。
  • Git 仓库。
  • 浏览器代码。
  • 日志输出。
  • 公开的配置文件。

获取 Client Secret

在 Shopify Dev Dashboard 中打开对应应用的设置,复制 Client Secret。

将它保存到 Gadget 的服务器端环境变量中。

Development 和 Production 应分别配置各自的 Secret。

为什么需要 Client Secret

App Events API 的服务端请求需要使用 Shopify 应用凭据获取访问令牌。

这个流程必须在 Gadget 后端完成,因为 Client Secret 不能暴露给浏览器。

编写上报计量事件的后端工具

创建工具文件

在 Gadget API 目录下创建工具文件,例如:

api/
  utils/
    reportBillingEvent.ts

这个工具负责:

  • 获取 Shopify 应用访问令牌。
  • 读取当前 Shopify 店铺 ID。
  • 构造 App Event。
  • 发送事件到 Shopify。
  • 记录响应状态。
  • 处理错误。
  • 设置幂等键。

获取 Shopify 访问令牌

后端首先使用 Shopify 应用凭据获取用于发送事件的访问令牌。

请求需要在服务器端完成,并使用环境变量中的 Client Secret。

示意流程如下:

Gadget 后端
→ Shopify Token Endpoint
→ 获取访问令牌
→ 使用 Bearer Token 调用 App Events API

不要在 React 前端直接请求 Token Endpoint。

构造 App Event

事件通常需要包括:

  • Shopify 店铺 ID。
  • Event Handle。
  • 事件发生时间。
  • 幂等键。
  • 用量值或其他属性。

示意结构如下:

{
  shopId: currentShopId,
  eventHandle: "generate-discount",
  timestamp: new Date().toISOString(),
  idempotencyKey: uniqueEventId,
  attributes: {
    value: 1
  }
}

具体请求格式应以当前 Shopify App Events API 文档为准。

使用幂等键

幂等键用于避免同一事件因为网络重试而被重复计费。

例如:

同一条折扣码生成操作
→ 请求发送一次
→ 网络超时
→ 应用再次发送
→ Shopify 根据相同幂等键只计量一次

不要简单使用:

shopId + 当前日期

作为所有事件的幂等键,因为同一个店铺一天可能生成很多条折扣码。

更安全的做法是为每一次成功的业务操作生成唯一事件 ID,例如:

discountCodeRecordId

或者使用:

shopId + discountCodeId + eventType

确保同一项业务操作始终使用同一个幂等键,而不同操作不会意外共用一个键。

在折扣码 Action 中上报用量

在成功回调中上报

折扣码只有在成功创建后,才应该上报用量事件。

推荐流程:

创建折扣码
→ Shopify 或 Gadget 操作成功
→ 保存 Discount Code 记录
→ 上报 generate-discount 事件

不要在折扣码创建失败时上报计费事件。

使用当前 Shopify 连接

折扣码创建 Action 需要获得当前 Shopify 店铺的连接信息。

上报工具至少需要知道:

  • 当前 Shopify 店铺 ID。
  • 当前环境。
  • 计量事件句柄。
  • 本次操作的唯一 ID。

示意流程如下:

await createDiscountCode();

await reportBillingEvent({
  shopId: connections.shopify.currentShopId,
  eventHandle: "generate-discount",
  value: 1,
  idempotencyKey: discountCodeId
});

实际代码应根据 Gadget 当前生成的 Shopify Connection 对象和 Action 参数进行调整。

是否应该等待计量请求完成

如果计量事件是收费依据,应用需要设计清楚:

  • 计量请求失败时是否重试。
  • 折扣码已经创建但计量失败时怎么办。
  • 是否将事件放入后台队列。
  • 是否保存待上报状态。
  • 是否允许商家继续使用功能。

对于重要的计费事件,不建议简单忽略错误。

可以在 Gadget 数据库中保存:

billingEventPending
billingEventSent
billingEventFailed

这样后续可以重试未成功上报的事件。

在 Dev Dashboard 查看 App Events

查看应用日志

打开 Shopify Dev Dashboard 中的应用日志。

在日志筛选中查看:

  • App Event。
  • App Billing Event。

刚发送的事件可能需要一点时间才会显示。

检查事件内容

确认以下内容正确:

  • Event Handle 是 generate-discount。
  • Shop ID 正确。
  • 用量值正确。
  • 时间戳正确。
  • 幂等键存在。
  • Shopify 返回成功状态。

如果事件没有出现,检查:

  • Client Secret 是否正确。
  • Token 是否成功获取。
  • Event Handle 是否与套餐配置一致。
  • 请求是否从后端发送。
  • 当前店铺是否已经安装应用。
  • Gadget 环境是否使用了正确的 Shopify 应用配置。

App Events 不只用于计费

即使应用没有使用按用量计费,也可以使用 App Events API 记录功能使用情况。

例如:

discount-created
product-imported
ai-search-used
image-processed
report-exported

这些事件可以帮助开发者了解:

  • 商家最常使用什么功能。
  • 哪些功能很少被使用。
  • 不同套餐的使用差异。
  • 应用的真实使用量。
  • 计费方案是否合理。

订阅检查与用量计费的关系

没有套餐时不要直接使用收费功能

应用可以根据当前订阅状态决定是否允许生成折扣码。

例如:

没有有效套餐
→ 跳转定价页

或者采用免费额度:

免费用户每月允许生成 10 条折扣码
超过后才需要订阅

具体策略取决于应用业务,不一定所有功能都必须完全锁定。

只对有效套餐上报计费事件

如果事件是实际收费依据,后端应确认:

  • 当前应用安装有效。
  • 当前套餐处于 active 状态。
  • 当前套餐包含这个计量事件。
  • 当前操作确实完成。
  • 当前事件尚未上报。

不要仅根据前端显示的套餐名称决定是否计费。

处理取消和套餐切换

商家可能会:

  • 取消订阅。
  • 从月费切换到年费。
  • 从基础套餐升级到用量套餐。
  • 从用量套餐降级到免费套餐。
  • 在旧订阅结束前批准新订阅。

因此,应用需要根据 Shopify 返回的订阅状态动态判断,而不是永久缓存一次套餐名称。

端到端测试流程

测试免费私有套餐

  1. 创建只允许开发店铺使用的零美元私有套餐。
  2. 打开 Shopify 应用定价页。
  3. 选择测试套餐。
  4. 批准安装或订阅。
  5. 返回嵌入式应用。
  6. 确认可以进入应用主页。

测试无订阅重定向

  1. 取消或移除测试订阅。
  2. 重新打开应用。
  3. 检查应用是否查询到没有有效订阅。
  4. 确认页面跳转到 Shopify App Pricing。
  5. 确认跳转发生在完整 Shopify Admin 页面,而不是被困在 iframe 中。

测试固定套餐

  1. 创建月费套餐。
  2. 创建年费套餐。
  3. 检查定价页面显示。
  4. 测试套餐批准流程。
  5. 确认应用显示当前套餐名称。
  6. 点击 Pricing Plans,确认可以返回套餐选择页面。

测试按用量套餐

  1. 创建基础月费加用量收费套餐。
  2. 配置 Event Handle 为 generate-discount。
  3. 订阅这个测试套餐。
  4. 返回应用并生成一条折扣码。
  5. 确认折扣码成功创建。
  6. 确认 App Events 日志出现一条事件。
  7. 检查事件用量值是否为 1。
  8. 重复发送相同幂等键,确认不会重复计量。

发布前检查

不要把测试套餐直接当作生产套餐

私有套餐适合测试,不应代替正式的 Public Plan。

发布前检查:

  • Public Plan 是否已经配置。
  • 月费和年费价格是否正确。
  • 用量价格是否正确。
  • 免费试用是否符合预期。
  • Event Handle 是否稳定。
  • 计费描述是否清楚。
  • 套餐名称是否适合商家理解。

检查用量事件

确认每次收费操作都满足:

  • 操作确实成功。
  • 事件只发送一次。
  • 幂等键稳定且唯一。
  • 用量数值正确。
  • 失败后可以重试。
  • 不会因页面刷新重复收费。
  • 不会因后台任务重复执行而重复收费。

检查安全配置

确认以下敏感数据只存在后端:

  • Shopify Client Secret。
  • Shopify Token。
  • App Events API 访问令牌。
  • Gadget 生产环境变量。
  • 计费事件内部配置。

React 前端只负责显示状态和发起业务请求,不能直接持有 Shopify 应用密钥。

总结

这份字幕实际演示的是一个基于 Gadget 的 Shopify 应用计费项目。

项目从折扣码生成器开始,逐步加入:

  • Shopify App Pricing。
  • 月费订阅。
  • 年费订阅。
  • 零美元私有测试套餐。
  • 固定费用加按用量收费。
  • App Events API。
  • 应用订阅状态检查。
  • 未订阅用户重定向。
  • 当前套餐显示。
  • 生成折扣码用量计量。
  • 幂等键防止重复计费。

完整流程可以概括为:

创建 Gadget Shopify 应用
→ 安装到 Development Store
→ 创建 Shopify App Pricing 套餐
→ 配置私有测试套餐
→ 查询当前有效订阅
→ 没有订阅时跳转定价页
→ 有订阅时进入应用
→ 完成可计费操作
→ 通过 App Events API 上报用量
→ Shopify 根据套餐进行计量和结算

Shopify App Pricing 现在支持固定周期订阅、按用量计费和组合套餐。App Events API 则把应用中的功能使用记录统一发送到 Shopify,既可以用于应用分析,也可以作为用量计费的计量来源。

对于 Gadget 应用来说,最重要的实现原则是:

  • 在后端检查订阅状态。
  • 只在业务操作成功后上报事件。
  • 使用稳定的 Event Handle。
  • 为每个业务操作设置唯一幂等键。
  • 将 Client Secret 和 Token 保存在服务器端。
  • 为失败的计费事件设计重试机制。
  • 在 Development Store 中完成完整测试后,再配置 Public Plan。

参考资料

citation:构建 Shopify 应用与 Shopify App Pricing

citation:使用 Dev Dashboard 创建和发布应用

citation:管理 Shopify 应用和应用版本