Shopify App 用量计费:先定义单位,再接账单
用量计费最难的部分通常不是调用收费接口,而是定义什么才算一次可收费的结果。一个含糊的“操作次数”会把预览、跳过、失败重试和真正写入混在一起,最终让商家无法预测账单,开发者也无法可靠对账。
Shopify 目前有两条计费路径,设计计量模型前必须先确定用哪一条,因为它们的额度能力完全不同。
| Shopify App Pricing | Billing API(manual pricing) | |
|---|---|---|
| 定位 | 官方对公开应用的推荐方式 | 仍受支持,用于既有应用和前者未覆盖的定价模型 |
| 用量配置 | 在 Partner Dashboard 的 Pricing 中定义 usage meter,通过 App Events API 上报 billing event | appSubscriptionCreate 等 GraphQL mutation |
| 周期上限 | 当前不支持 usage cap | cappedAmount 限定 30 天周期内的计费上限 |
Shopify App Pricing 的用量计费还有几条硬约束会直接影响实现:每个套餐最多 5 个启用的 usage meter,每个 meter 最多 6 个价格档,用量计费必须按月结算(不能与只按年付的套餐组合),商家卸载应用后只有 24 小时可以补交剩余 billing event。usage charges 文档、Billing API 用量订阅
平台负责订阅与账单,应用仍需负责业务计量、费用预估和上报幂等。
先定义计费单位
一个好单位应满足四个条件:
- 商家在执行前能理解。
- 应用在执行后能精确计算。
- 重试不会产生第二份有效用量。
- 单位与主要成本大致相关。
以跨店同步为例,可以定义“每个成功写入目标店的资源或字段值”为一个单位。但必须继续明确:
| 结果 | 是否计费 | 原因 |
|---|---|---|
| 只做差异预览 | 否 | 尚未改变目标店 |
| 目标已经一致而跳过 | 否 | 没有产生新的写入结果 |
| 写入失败 | 否 | 商家没有获得承诺结果 |
| 写入成功 | 是 | 产生一个可验证的目标结果 |
| 同一项失败后重试成功 | 一次 | 结果只应被确认一次 |
| 成功后用户主动再次覆盖 | 按产品规则 | 需要在定价说明中明确 |
不能直接复用任务的 done 数量收费。很多任务引擎会把“已经一致”“找到现有映射”也记为完成,但完成不等于发生了应计费写入。
预估与结算使用同一集合
执行前的报价应来自同一份权威 diff:
候选资源
→ 过滤已一致、不可执行和缺少依赖项
→ 得到预计写入集合
→ 计算预计单位和最高费用
执行后再以真实成功写入集合结算。预估不是最终账单,但两者的分类规则必须一致,否则用户会看到“预计 20,实际 87”而不知道差异来自哪里。
费用确认界面至少显示:
- 本次预计单位。
- 套餐内剩余额度(使用 Shopify App Pricing 时由应用自己维护并执行,平台不提供 usage cap)。
- 可能产生的超额费用。
- 失败和跳过是否收费。
- 明确的取消与确认操作。
不要用一个没有可见操作按钮的提示条阻塞任务。计费确认是一个需要用户做决定的流程,应提供稳定、可返回的确认界面。
内部账本是事实来源
应用应先写内部用量账本,再把待上报事件发送到 Shopify:
UsageLedger
- shopId
- billingCycleId
- operationId
- itemKey
- units
- status: pending | reported | failed
- providerRecordId
- createdAt
推荐的唯一边界是 (operationId, itemKey, meter) 或能表达同等语义的幂等键。任务重启、网络超时或 worker 重试时,同一业务结果只能生成一条有效账本记录。
账本把“业务已经成功”和“Shopify 已经接受上报”拆开:
- 目标写入成功。
- 以幂等键写入本地账本。
- 异步上报 Shopify。
- 保存平台记录 ID 或失败原因。
- 定期补报 pending 项,并与 Shopify 账单状态对账。
补报窗口不是无限的:使用 Shopify App Pricing 时,商家卸载后只有 24 小时提交剩余 billing event。补报任务必须以卸载事件为触发点抢先清空该店的 pending 记录,不能只依赖固定间隔的定时扫描。
如果直接在业务请求末尾调用收费接口,网络超时会留下两种坏结果:写入成功但漏收费,或重试后重复收费。
周期、额度与试用
额度不是一个永久累加数字,需要绑定明确账单周期。使用 Shopify App Pricing 时平台不会替你封顶,超额保护完全由应用实现;使用 Billing API 时 cappedAmount 限定 30 天周期上限,提高上限需要商家同意(商家也可在后台自行调整);用量达到上限的 90% 时平台会发送 APP_SUBSCRIPTIONS_APPROACHING_CAPPED_AMOUNT Webhook,应用仍要在本地跟踪余量以便提前提示。跨周期运行的长任务,应在开始时锁定计费规则,或逐项按实际完成时间归入周期;两种做法都可以,但必须提前说明并保持一致。
试用也需要独立规则:
- 试用期是否有单位上限。
- 超出后暂停还是开始计费。
- 转付费后是否获得新的周期额度。
- 试用中创建、试用结束后才完成的任务如何处理。
不要用“试用免费”代替这些定义。免费只说明价格,不说明资源限制和跨周期归属。
成本保护
预览不收费,不代表预览可以无限消耗资源。对于需要大量 API 查询、计算和数据库访问的预览,应增加:
- 按能力区分的限流键,避免文件列表和费用报价误用同一冷却时间。
- 单店并发限制。
- 单次扫描上限和分页。
- 队列容量与超时。
- 预算或异常用量提醒。
这些限制应描述为服务保护,而不是偷偷计入同步单位。
对账与验收
- 同一成功项重试多次,账本和 Shopify 最终都只计一次。
- 跳过、失败和预览不会产生收费记录。
- 上报接口超时后能够安全补报。
- 任务跨账单周期时,单位归属符合公开规则。
- 商家卸载后,该店的 pending 账本能在 24 小时窗口内完成补报或明确放弃。
- 套餐升级、取消和试用结束后,权限与账本周期同步变化。
- 商家能看到已用、剩余、重置时间和本次预计费用。
- 应用能从业务记录追溯到平台收费记录,也能反向解释账单来源。
Shopify 的收费能力解决“怎样进入商家账单”,计量模型解决“为什么该收这笔钱”。后者应该先于价格表和 API 接入完成。