文章目录
如果你准备开发一个面向多个商家的 Shopify Public App,应用功能只是第一步。真正上线前,还需要解决:
- 订阅套餐
- 月付和年付价格
- 免费试用
- 商家选择计划
- Shopify 计费确认
- 订阅状态同步
- 卸载和重新安装
- 过期订阅拦截
- Production 环境收费
本文使用 Gadget 配置一个 Shopify Public App,并接入 Shopify 当前的 App Pricing 计费流程,完成 Basic 和 Pro 两个套餐的订阅。
Shopify 后台中部分页面可能仍会出现 Managed Pricing 相关名称,但当前公开应用的计费方向是 Shopify App Pricing。它支持固定订阅、按使用量收费,以及固定费用和用量费用组合的计费模式。citation:Building apps
项目的整体结构
项目由 Shopify、Gadget 和 Shopify App Pricing 三部分组成:
Shopify Public App
↓
Shopify App Pricing
↓
商家选择套餐并确认收费
↓
Shopify 创建订阅记录
↓
Gadget 同步订阅状态
↓
应用根据状态开放功能
Gadget 主要负责:
- Shopify OAuth
- Embedded App
- Session Token
- PostgreSQL 数据库
- Shopify Shop 模型
- App Subscription 模型
- 订阅状态同步
- React 管理页面
- 后端权限判断
Shopify 负责:
- 应用安装
- 应用分发
- 商家收费确认
- 应用版本
- App Store 审核
- 订阅和账单基础设施
Public App 和 Custom App 的区别
只有适合公开分发的 Public App 才适合向多个商家收费。
Public App
适合:
- 面向多个独立商家
- 提交 Shopify App Store
- 提供 Basic、Pro 等套餐
- 通过 Shopify App Pricing 计费
Custom App
适合:
- 单个客户定制
- 企业内部使用
- 特定组织使用
- 不准备公开销售
Custom App 不适合使用 Shopify Billing API 向商家收费。
应用创建时就要确认分发方向,因为 Shopify 的分发方式选择后通常不能随意切换。
开始前的准备
需要准备:
- Shopify Partners 或开发者账号
- Shopify Development Store
- Gadget 账号
- Shopify Dev Dashboard
- Shopify App Pricing 配置权限
- Node.js
- VS Code 或其他代码编辑器
本项目技术栈:
Backend: Gadget Serverless Node.js
Database: Gadget PostgreSQL
Frontend: React
Admin UI: Shopify Polaris
Billing: Shopify App Pricing
开发阶段使用 Development Store 和测试收费,不要直接连接真实商店。
创建 Gadget Shopify App
进入 Gadget 应用创建页面:
https://gadget.new
选择:
Shopify App
应用名称可以设置为:
Managed Billing Demo
Gadget 会创建:
- PostgreSQL 数据库
- Serverless Node.js 后端
- React 前端
- Shopify Connection
- Development 环境
- Production 环境
- Shopify 数据模型
- Action 和 API 客户端
创建 Shopify Public App
在 Shopify Dev Dashboard 中创建应用。
应用名称可以设置为:
Managed Billing Demo
创建后获取:
Client ID
Client Secret
将它们填入 Gadget 的 Shopify Connection。
Client Secret 只能保存在服务端或 Gadget 私密配置中,不要放到:
- React 前端
- 浏览器 Local Storage
- GitHub 公开仓库
- URL 参数
- 前端日志
配置 Shopify API Scopes
本教程的重点是计费,但为了完成 Shopify OAuth 和商店安装,仍然需要选择应用需要的权限。
如果示例应用需要读取商品,可以选择:
read_products
如果应用本身不需要商品数据,就不要额外选择商品权限。
权限应根据真实功能决定:
商品读取:
read_products
订单读取:
read_orders
库存读取:
read_inventory
商品修改:
write_products
完成 Shopify Connection 后,可以选择:
Shopify App Subscription
Shopify App
这两个模型用于帮助 Gadget 获取应用订阅和应用信息。
Shopify App Subscription 模型
Shopify App Subscription 模型用于保存商店订阅记录。
其中可能包含:
status
name
price
shop
createdAt
updatedAt
应用可以通过这些数据判断:
商店有没有订阅
订阅的是什么计划
计划是否仍然有效
订阅是否已取消
Gadget 通常会为该模型配置相关 Webhook 和同步能力。
Shopify App 模型
Shopify App 模型可以用于读取应用本身的信息,例如:
handle
name
应用 Handle 在生成计费返回地址时可能会用到。
如果当前项目已经明确知道 App Handle,也可以放进环境变量。但要确保:
- Development 和 Production 使用不同配置
- App Handle 与当前 Shopify App 一致
- 不要把一个环境的 Handle 写死到另一个环境
配置 App URL 和 OAuth 回调地址
从 Gadget Shopify Connection 页面复制:
App URL
OAuth Callback URL
回到 Shopify App 配置中填写:
App URL
Allowed redirection URL
检查:
- App URL 正确
- OAuth 回调路径完整
- 使用 HTTPS
- Client ID 属于当前应用
- Development 和 Production 没有混用
- 当前应用版本已经保存和发布
完成配置后,先不要安装到商店,继续配置 Public App 和计费计划。
选择 Public Distribution
回到 Shopify App 的分发设置,选择:
Public distribution
Public App 后续可以提交到 Shopify App Store。
在提交和配置过程中,可能需要准备:
- 主语言
- 应用名称
- 应用介绍
- 隐私政策
- 服务条款
- 客服信息
- 应用截图
- 价格信息
- 数据处理说明
Shopify App Pricing 的套餐配置通常会出现在应用提交或 Pricing details 页面中。
配置 App Pricing 套餐
在应用的 Pricing details 中创建套餐。
可以先创建两个计划:
Basic
Pro
示例配置:
| 计划 | 月付 | 年付 |
|---|---|---|
| Basic | $10/月 | $100/年 |
| Pro | $25/月 | $250/年 |
也可以设置:
- 免费试用天数
- 计划描述
- 功能差异
- 使用量收费
- 计划可见范围
- 订阅确认后的返回地址
价格只是示例,正式应用需要根据产品成本、服务内容和客服成本重新制定。
配置计费返回地址
商家完成 Shopify 计费确认后,Shopify 需要将商家带回应用。
可以设置一个相对路径:
/billing/callback
也可以设置 Gadget 后端的回调路由:
/api/billing/callback
本文使用后端回调路由,原因是后端可以:
- 读取 Shopify 返回的参数
- 查询当前商店
- 检查订阅是否已经同步
- 暂时保存计费引用
- 再将商家重定向到 Embedded App
计费回调过程如下:
商家选择套餐
↓
Shopify 显示收费确认
↓
商家批准
↓
Shopify 回调 Gadget
↓
Gadget 检查商店和订阅状态
↓
重定向回应用首页
为什么需要临时保存 Charge ID?
计费确认和订阅 Webhook 不一定同时到达。
可能出现这样的时间差:
商家已经完成付款
↓
计费回调先到达
↓
订阅 Webhook 还没有到达
这时 Gadget 数据库中可能还没有 active App Subscription。
为了避免商家已经完成付款却被应用挡在外面,可以在 Shop 模型中添加一个临时字段:
chargeId
它的作用只是短暂记录计费流程中的引用。
注意:
Charge ID 不能作为长期订阅凭证。最终是否允许使用应用,仍然应该以有效的 App Subscription 状态为准。
创建计费回调路由
在 Gadget 后端创建路由,例如:
api/routes/billing-callback.ts
这个路由负责:
- 读取 Shopify 返回的商店域名
- 读取当前计费引用
- 查询 Gadget 中的 Shop 记录
- 查询当前是否已经存在 active 订阅
- 如果没有 active 订阅,临时保存计费引用
- 将商家重定向回 Embedded App
逻辑可以理解为:
收到计费回调
↓
查询当前 Shop
↓
检查 App Subscription
↓
如果已有 active 订阅
直接进入应用
否则
临时保存 chargeId
再进入应用等待同步
回调路由中需要校验商店身份,不能只相信 URL 中传入的商店域名。
清理临时 Charge ID
当 Shopify App Subscription Webhook 到达后,Gadget 会创建或更新订阅记录。
如果订阅状态是:
active
就可以清空 Shop 上临时保存的:
chargeId
数据变化如下:
完成付款
↓
Shop.chargeId 暂时保存
↓
App Subscription 创建成功
↓
确认 status = active
↓
清空 Shop.chargeId
这样可以避免旧的计费引用长期留在数据库中。
处理应用卸载和重新安装
这是计费流程中非常容易忽略的部分。
商家卸载应用后,应用可能无法继续收到这个商店的部分 Webhook。因此,Gadget 本地数据库中的订阅状态可能暂时仍然显示为:
active
如果商家之后重新安装应用,而应用只相信旧的本地数据,就可能错误地让已经取消订阅的商家继续使用。
更稳妥的做法是在:
Install
Reinstall
Action 中重新同步最新的 Shopify App Subscription 数据。
流程如下:
商家重新安装应用
↓
运行 App Subscription 同步
↓
读取最新订阅状态
↓
更新 Gadget 数据库
↓
判断是否需要重新计费
重新安装时,只同步需要的模型即可:
Shopify App Subscription
不需要每次都同步全部 Shopify 数据。
创建 Plans 页面
在 Gadget React 前端中创建:
web/routes/plans.jsx
Plans 页面负责:
- 读取可用套餐
- 显示价格
- 显示试用期
- 显示计划描述
- 读取当前订阅
- 提供订阅按钮
- 处理计费返回
可以使用:
- Shopify Polaris Card
- Layout
- Button
- Banner
- Badge
- Spinner
- App Bridge Title Bar
页面可以显示:
Basic
$10 / month
Suitable for small stores
[Choose Basic]
Pro
$25 / month
Advanced features
[Choose Pro]
启动订阅流程
商家点击套餐按钮后,前端调用 Shopify App Pricing 的订阅流程。
过程如下:
点击 Choose Plan
↓
创建订阅请求
↓
Shopify 显示收费确认
↓
商家批准收费
↓
回调 Gadget
↓
同步 App Subscription
↓
返回应用首页
计费流程中需要处理:
- 商家批准
- 商家取消
- 订阅已经存在
- 试用期仍然有效
- 计划升级
- 计划降级
- 收费失败
- 返回地址错误
- Webhook 延迟
不要在商家完成收费确认之前开放付费功能。
在应用首页添加订阅拦截
应用首页需要检查当前商店是否有有效订阅。
可以查询:
Shop
App Subscriptions
chargeId
判断逻辑:
如果存在 active App Subscription:
允许进入应用
如果没有 active App Subscription:
跳转到 Plans 页面
如果应用处于嵌入式 iframe 中,跳转时要使用正确的父窗口或 Shopify App Bridge 跳转方式,避免只在 iframe 内部加载计费页面。
推荐将这个判断做成:
- React Provider
- 路由守卫
- 统一的订阅 Hook
- 后端权限中间件
不要在每个页面中重复复制一套订阅检查代码。
前端不能代替后端检查
即使前端隐藏了按钮,也不能说明功能真正被保护。
商家可能直接调用:
API Action
因此,涉及付费权限的操作必须由后端再次检查:
当前 Shop
↓
当前 App Subscription
↓
订阅状态
↓
计划名称
↓
是否允许执行当前操作
前端负责显示体验,后端负责最终授权。
测试计费流程
在 Development Store 中完成以下测试。
安装测试
- 应用可以安装
- OAuth 正常
- Shop 记录创建
- App Subscription 模型可用
- 计划页面可以打开
付款测试
- Basic 计划可以选择
- Pro 计划可以选择
- Shopify 显示测试收费
- 测试收费可以批准
- 回调可以返回 Gadget
- App Subscription 最终变为 active
延迟测试
模拟:
付款回调先到
订阅 Webhook 后到
确认:
chargeId能暂时保存- active 订阅创建后能清理
- 应用最终不会错误阻止已付款商家
卸载测试
- 安装应用
- 订阅 Basic
- 确认状态为 active
- 卸载应用
- 重新安装应用
- 执行订阅同步
- 确认取消的订阅显示为 canceled
- 重新选择 Pro
- 确认新订阅变为 active
计划状态测试
测试以下状态:
active
canceled
expired
past_due
pending
不同状态应该对应不同的应用行为。
生产环境配置
正式上线前,准备独立的:
Development Shopify App
Production Shopify App
生产环境需要重新确认:
- Production Client ID
- Production Client Secret
- Production App URL
- Production OAuth 回调地址
- Production 数据库
- Production 环境变量
- Production App Pricing
- Production 订阅回调地址
测试环境中的:
Test charges
不能用于正式商家。
生产环境还需要完成:
- Shopify App Store 应用资料
- 隐私政策
- 服务条款
- 退款政策
- 取消订阅政策
- 客服页面
- 计费说明
- 数据删除流程
常见问题排查
商家安装后直接进入应用,没有看到计费页面
检查:
- 应用是否设置为 Public Distribution
- Plans 是否已经创建
- 当前商店是否有 active 订阅
- 前端是否执行了订阅检查
- App Handle 是否正确
- 计费回调地址是否正确
付款后仍然显示没有订阅
检查:
- App Subscription Webhook 是否到达
- Shopify App Subscription 模型是否同步
- 回调是否提前返回
- Shop 和 App Subscription 的关系是否正确
- 是否需要手动运行一次订阅同步
- Gadget 日志是否有异常
已经卸载的商家仍然可以使用应用
检查:
- 是否只依赖本地 active 记录
- Reinstall Action 是否执行订阅同步
- 取消订阅记录是否被更新
- 前端是否缓存了旧订阅状态
- 后端是否重新校验当前订阅
Charge ID 一直没有清理
检查:
- App Subscription Create Action 是否执行
- 是否判断
status = active - Shop ID 是否正确
- 更新 Shop 记录时是否使用了正确的 ID
- Webhook 是否存在延迟或失败
用户绕过 Plans 页面继续使用高级功能
检查:
- 后端 Action 是否验证订阅
- 是否只在前端做跳转
- 当前 Shop 是否正确识别
- 计划权限是否在服务端执行
- 是否允许没有 active 订阅的商店调用付费功能
项目验收清单
完成以下检查后,Public App 的 Managed Billing 才算基本跑通:
- Shopify Public App 创建成功
- Gadget Shopify Connection 正常
- OAuth 安装正常
- App URL 配置正确
- App Pricing 套餐创建成功
- Basic 计划可以显示
- Pro 计划可以显示
- 计费回调路由正常
- Shop 模型可以保存临时 chargeId
- App Subscription 模型可以同步
- 订阅成功后 chargeId 可以清理
- active 订阅可以进入应用
- 无订阅商店会跳转到 Plans 页面
- 商家取消订阅后不能继续使用受限功能
- 卸载后重新安装会同步最新订阅状态
- 计划升级和降级可以处理
- 后端会重新验证订阅权限
- Development 和 Production 配置彼此隔离
- Production 不使用测试收费
结语
Shopify Public App 的计费不是简单显示一个价格,而是一条完整的状态链:
商家安装应用
↓
查看计划
↓
选择套餐
↓
Shopify 收费确认
↓
计费回调
↓
App Subscription 同步
↓
应用开放功能
↓
取消或卸载后重新校验
Gadget 主要负责:
- Shopify App 连接
- Shop 数据
- App Subscription 数据
- 后端 Action
- 订阅同步
- React 管理页面
- 生产环境
Shopify App Pricing 负责:
- 计划展示
- 收费确认
- 订阅记录
- 计费基础流程
- 应用分发和审核相关能力
真正需要特别注意的是三个细节:
不要把 chargeId 当成永久授权
不要只依赖前端判断订阅
卸载后重新安装必须同步最新订阅状态
只要把安装、付款、回调、Webhook、卸载和重新安装这些环节都测试一遍,应用的计费流程才算真正具备生产可用性。




