mcpField
S
salla-app-billing

salla-app-billing

github.com/sallaapp开发281 次安装5 天前更新
代码仓库

关于 salla-app-billing

Salla App Billing Flow 是面向 Salla 应用开发者的技能,用于实现商户套餐、附加项、试用和按量计费。它指导通过 Salla API 和 Partners MCP 配置价格、处理 Webhook、跟踪权益及核对订阅。

主要功能

  1. 01在定价配置中设置套餐和附加项
  2. 02连接计费事件 Webhook
  3. 03跟踪套餐与附加项权益
  4. 04处理续订、到期和试用
  5. 05通过 API 核对订阅

适用场景

  1. 01为 Salla 应用设置订阅价格
  2. 02按套餐或附加项状态控制功能
  3. 03从遗漏的 Webhook 中恢复计费状态
  4. 04管理外部周期续订

MCP 相关

使用 Salla Partners MCP 配置并验证发布定价、设置应用 Webhook 地址及读取订阅详情。技能还介绍通过 Salla Admin API 核对订阅和写入用量余额。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sallaapp/salla-partners-agent-kit --skill salla-app-billing

安装后在 Claude Code 中输入/salla-app-billing确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
pricing-shapes.md
subscription-api.md
subscription-events.md

详细介绍

Salla 应用计费流程

设置商家如何为你的应用付费,并准确了解每位商家的套餐状态。 计费由 Salla 管理——你需要响应事件,并(可选)读取订阅端点; 你无需自行收取银行卡费用。请按顺序完成以下步骤;完成每个阶段后再继续。 步骤 1、2 和 5 会使用 Salla Partners MCP 执行操作;步骤 3–4 和 6 是你需要编写的运行时逻辑。

唯一可信来源

套餐状态由事件驱动——依据经过验证的 app.subscription.* / app.trial.* Webhook 更新,然后通过以下地址的 Admin(Merchant)API 进行核对:https://api.salla.dev/admin/v2(OAuth,offline_access)。此技能使用两个实际端点:

  • GET /apps/{app_id}/subscriptions — 获取应用的订阅状态和详细信息,涵盖套餐和附加项(通过 item_type 筛选):套餐状态、权益、日期、subscription_balance(用于核对;步骤 5)。完整的 OpenAPI Schema(唯一可信来源): https://docs.salla.dev/5401098e0.md
  • POST /apps/balance — 写回按量付费(Pay-As-You-Go)的使用余额(步骤 5b)。
  • POST /apps/subscriptions/{subscription_id}/renew — 对于 external_recurring 套餐/附加项,每次续订都由合作伙伴发起(Salla 不会自动续订)。从订阅 Webhook 获取 subscription_id;需要 offline_access。返回续订后的订阅(item_type/item_slug/plan_*/start_date/end_date/subscription_balance/features)。 处理以下错误:not_renewable、subscription_not_active、auto_renew_disabled、payment_failed(403)、rate_limit_exceeded(429 — 每天一次)。适用于 support_renew: true 的附加项。(由 Salla 管理的周期性订阅会自动续订——你只会收到 app.subscription.renewed。)请从在线 OpenAPI 文档中读取准确的请求/响应 Schema 和完整的错误响应约定——这是唯一可信来源: https://docs.salla.dev/37396517e0.md(不要手动编写数据结构;请以文档为准)。

开始编码前,请通过 App Events 参考文档 (https://docs.salla.dev/421413m0.md)或 salla_events action=list 确认负载和字段结构。Salla Partners MCP 执行以下操作:

工具操作作用
app_publishset validate设置 pricing 部分(套餐/附加项),然后验证发布
salla_appsconnect设置 webhook_url,用于自动接收应用事件(app.subscription.* / app.trial.*)
salla_appssubscriptions只读:读取应用的订阅详情
套餐与附加项
概念说明item_typeitem_slug
套餐商家在应用中的主订阅"plan"null
附加项在套餐基础上额外购买的项目"addon"附加项标识符

两者都会触发相同的 app.subscription.* / app.trial.* 事件。**此技能涵盖套餐、附加项、权益限制和用量计费。**在应用内购买附加项 → salla-addon-purchase。plan_type 的取值:free · once(一次性)· recurring(按月/按年)· on_demand(按需付费)。


步骤 0 — 了解需求

开始前请询问:

  1. 你会提供哪些套餐类型?(免费、按月、按年、试用、一次性、按需付费)
  2. 每种套餐会解锁哪些功能?(features[] → 权益映射)
  3. 付费套餐前是否需要免费试用?

步骤 1 — 定义定价计划(publication 的 pricing 部分)

计划和附加项都在 publication 的 pricing 部分中定义 — app_publish action=set section=pricing data={…},然后执行 app_publish action=validate。没有 独立的定价端点;Portal 的 Pricing 向导步骤是基于同一部分的 UI 辅助工具。发布机制由 salla-publication-consistency 负责;pricing 数据结构如下:

  • plan_type — "free" | "recurring" | "once" | "on_demand"(必填;精确的 API 值 — 不使用 one_time 或 pay_as_you_go)。它决定适用哪些字段。free 受资格限制 — 仅当 can_have_free_plan 为 true 时才可用(shipping/communication app 或 show_app_free_plan 功能);提供免费计划前,请从 app details 中读取该值(→ references/pricing-shapes.md)。
  • Recurring — plans[](≤8;0–4 个 monthly,0–4 个 yearly)。每个计划:name{ar,en}、subtitle{ar,en}、price、recurring(free | monthly | yearly | one-time)、recommended、is_compare_included、hidden、initialization_cost、discount、additional_features[]、promotions[](最多 1 个)、balance(适用于 one-time/on_demand),以及用于原位更新计划的 id。另有顶层的 plan_features[] 对比矩阵。
  • Once — one_time_price、one_time_old_price(> price)、plan_additional_features[] {key,name,price,adjustable,min,max}。不使用 plans[]。
  • On-demand — plans[],其中必须包含 balance + on_demand_type(emails|messages|per-transaction)。
  • 附加项(所有类型,最多 5 个)— {name,description,price,slug,support_renew}。每个条目都必须包含 name、price 和 slug — 缺少任一项时,服务器都会拒绝该附加项。所有价格均以 SAR 计价;没有货币字段。始终为一次性;support_renew 是唯一的周期性控制项:true ⇒ external_recurring(通过 renew API 管理续订;请在附加项标题/描述中说明周期),否则为 once 事件。
  • 试用字段位于顶层:plan_trial(天数,最小值为 1,受 max-trial-days 限制 — 默认值为 7)+ trial_description(30–1000)。流失挽回:unsubscribe_reward、unsubscribe_email_reward。

命名陷阱: plan_type:"recurring"(模型)≠ 每个计划的 recurring:"monthly"(周期); plan_additional_features(顶层,once)≠ 每个计划的 additional_features(recurring)。

完整字段表、类型和服务器规则:请加载 references/pricing-shapes.md。

使用 app_publish action=set section=pricing 设置此部分,然后执行 app_publish action=validate(保存草稿)。完整发布流程 → salla-publication-consistency。

计划类型用途
Free不收费
Monthly / Yearly周期性收费(plan_type: recurring)
Trial付费计划前的限时免费使用
One-Time单次收费(plan_type: once)
Pay As You Go按使用量收费 / 按需(plan_type: on_demand)
Addon计划之外的额外项目(在 addons 中定义)

应用创建后,App details → Custom Plans 可配置按商户定制的计划。

检查项:“计划是否已在 publication 的 pricing 部分中配置 (app_publish action=set section=pricing),且 publication 验证通过?”


第 2 步 — 设置 Webhook URL(应用事件会自动投递)

事件是你的事实来源——这些是应用事件(app.subscription.* / app.trial.*),因此应用默认已订阅这些事件:Salla 会将所有事件自动投递到你的 webhook_url。对于任何 app.* 事件,都不要调用 salla_events action=subscribe。此处唯一要做的是设置接收端:

  • salla_apps action=connect、app_id、webhook_url、 webhook_security_strategy: "signature" — 将应用事件指向你的处理程序(→ 关于完整的连接和密钥同步流程,请参阅 salla-app-lifecycle 第 1 步)。

salla_events action=subscribe 仅适用于非应用(商店)事件——应用希望响应的 order.*、 product.*、customer.*、cart.* 和商店端的 shipment.*。计费完全由应用事件处理,因此此技能无需调用 subscribe。

应用事件会自动投递——设置 webhook_url,然后处理这些事件。 每个计费事件—— app.subscription.started、app.subscription.renewed、app.subscription.expired、 app.subscription.canceled 以及 app.trial.* 事件——触发时都会立即到达你的 webhook_url, 无论它是否出现在 salla_events action=list 目录中(renewed/expired/canceled 甚至不在其中)。你的任务是在 webhook 处理程序(第 4 步)中处理所有这些事件,而不是订阅其中任何事件。应用事件参考 (https://docs.salla.dev/421413m0.md) 将每个事件都列为由平台触发。

检查项:“是否已通过 salla_apps action=connect 设置 webhook_url(应用事件会自动投递到此地址),并且处理程序覆盖了完整的事件系列——app.subscription.started/renewed/expired/ canceled + app.trial.*(第 4 步)?是否没有对任何 app.* 事件调用 salla_events action=subscribe——subscribe 仅用于商店事件。”


第 3 步 — 根据事件跟踪方案状态

安全 — 事件会授予付费访问权限。 伪造或重放订阅/试用 webhook 可能会授予(或撤销)付费功能。更改方案/权益状态前,先验证 webhook 签名并强制执行幂等性——此传输层(签名验证、重放保护、快速返回 2xx)由 salla-webhooks 负责;令牌/OAuth 由 salla-app-auth 负责。只有来自已验证服务器事件或经过协调的 Partners API(第 5 步)的信息,才能作为权威的权益变更依据;绝不能采用客户端上报的方案。通过你自己的经过身份验证的管理员路径进行权益读取/写入,并且不要将签名密钥和令牌写入日志。

通过 salla-app-lifecycle 接入这些事件。此处需要关注的变化:

  • 根据 item_type 分支处理 — "plan" 更新方案状态;"addon" 更新附加组件权益 (同一类 payload;item_slug 用于标识具体的附加组件)。
  • 试用事件的 payload 结构相同 — 也要处理 app.trial.started/.expired/.canceled, 否则试用开始/到期事件会被遗漏,导致商家的访问控制出现错误。
  • 在 started/renewed 事件中保存 end_date ?? renew_date;在 expired/canceled 事件中将订阅标记为非活动状态。
  • 当 store_type !== "live" 时跳过计费逻辑(开发/演示商店)。

payload 字段和完整示例 → references/subscription-events.md。实时文档 — 应用订阅 Webhook 事件:https://docs.salla.dev/2213496m0.md;应用事件参考(包含 payload 示例的生命周期 webhook):https://docs.salla.dev/421413m0.md。

检查项:“演示商店的订阅事件是否会以正确的状态更新已存储方案?”

第 4 步 — 处理续订、到期和试用

这三种都是应用事件——会自动发送到你的 webhook_url(你无需订阅任何 app.* 事件——参见第 2 步);你需要处理这些事件:

  • app.subscription.renewed — 确认订阅仍处于有效状态;保存新的 end_date / renew_date。不要假定结束日期没有变化。
  • app.subscription.expired — 方案已失效(未续订)。限制访问;可考虑显示宽限期横幅。
  • app.subscription.canceled — 商家已取消。根据你的策略限制访问。
  • 试用会以 app.trial.started / .expired / .canceled 事件发送(零价格的 plan_type: once)。收到 trial.started 时启用试用功能并记录试用结束时间;到期或取消时降级。商家转为付费订阅后,会触发 app.subscription.started。

根据保存的 status + end_date 控制功能访问,而不要根据事件到达的时刻来判断(事件可能延迟或重复——处理时应确保幂等)。第 5 步可作为漏收事件时的补救措施。

检查点:“续订会更新 end_date;到期或取消会限制访问;试用门控是否正确?”


第 5 步 — 通过应用订阅详情 API 对账

服务中断后(错过 Webhook),使用 salla_apps action=subscriptions 对账,该命令会调用真实的 应用订阅详情端点:

GET https://api.salla.dev/admin/v2/apps/{app_id}/subscriptions
Authorization: Bearer <access_token>   # OAuth, offline_access

{app_id} 是你的 Salla 应用 ID(Salla Partners → My Apps → Your App)。此调用使用商家的访问令牌,因此 data[] 中是该商家为此应用订阅的内容——每个有效方案或附加项对应一条记录。文档: https://docs.salla.dev/5401098e0.md。

响应(200) — { status, success, data: [...] },每一项都是订阅详情(可从中读取方案状态、权益和用量余额)。读取 item_type(筛选 "plan" 以获取方案状态)、plan_type、start_date / end_date、features[] ({ key, quantity })以及 subscription_balance(用量余额,通过第 5b 步写入)。403 表示调用方没有此应用的权限。

完整响应示例及每个字段的含义(以及 POST /apps/balance 的数据结构): 请查看 references/subscription-api.md。

检查点:“对账是否返回商家当前的方案和余额,并与保存的状态一致?”


第 5b 步 — 更新用量余额(按量付费)

对于 on_demand(按量付费)方案,使用真实的更新订阅余额端点,将商家的用量余额写回 Salla:

POST https://api.salla.dev/admin/v2/apps/balance
Authorization: Bearer <access_token>   # OAuth, offline_access
Content-Type: application/json

{ "balance": 2399 }

balance(整数)为必填项。文档:https://docs.salla.dev/5401099e0.md。

响应(201) — { status, success, data: { message, code } }。 校验错误(422) — { status: 422, success: false, error: { code, message, fields: { balance: ["..."] } } }。

安全性。 在服务器端根据已验证的用量计算余额,并且只能通过你自己的身份验证管理路径执行 POST——绝不要使用客户端上报的值。通过 GET /apps/{app_id}/subscriptions(第 5 步)重新读取余额以确认。(信任模型与第 3 步相同。)

检查点:“余额是否通过 POST /apps/balance 写入,并通过重新读取订阅确认?”


附加项

附加项是在方案之外可额外购买的内容,可以是定期或一次性项目,定义在发布请求的 addons 数组中(第 1 步)。它们通过相同的事件系列发送,且 item_type: "addon"(item_slug 用于标识附加项),因此你应像记录方案一样记录它们,并启用其 features[]。如需应用内购买界面(嵌入式 SDK Checkout),请使用 salla-addon-purchase。

权益门控

将有效方案的 features[] 和每个有效附加项的 features[] 合并为每个商家的一套权益集合。每次收到 app.subscription.* / app.trial.* 事件时都重新计算该集合,并在使用功能时进行门控(查询已保存的权益集合,绝不要直接使用原始事件)。

用量余额(按使用量付费)

对于 on_demand 方案,将每项可计费操作计入商家的 subscription_balance(商家欠下的应用服务费用):操作前检查 余额,随着使用量产生而扣减,并在余额耗尽时阻止操作(或发出警告)。使用 GET /apps/{app_id}/subscriptions(步骤 5)读取余额;使用 POST /apps/balance(步骤 5b)写入余额。


步骤 6 — 架构与门控(参考)

subscriptions
  merchant_id      (PK / FK)
  subscription_id
  item_type        'plan'
  plan_name
  plan_type        free | once | recurring | on_demand
  status           active | inactive | trial
  start_date
  end_date
  features         json   // [{ key, quantity }]
  store_type       development | demo | live
  updated_at

根据上文 Entitlement Gating 一节,将 features[] 映射为权益(将方案和附加项的 功能合并为一个集合)。完整载荷: references/subscription-events.md。


风险信号

计费事件会授予和撤销付费访问权限,因此在此处走捷径,要么会泄露付费功能, 要么会错误地阻止付费商家访问。如果你的方案中有以下任一做法,请重新阅读指定步骤。

容易产生的想法错误原因
“插件上的 slug / price 是可选的——我就不填写了。”每个插件条目都必须填写 name、price 和 slug——否则服务端会拒绝不完整的插件。缺少其中任何一项都会导致验证错误(步骤 1)。
“我会直接根据事件负载解锁功能。”伪造或重放的 webhook 会授予付费访问权限。先验证签名并去重,再根据已存储的权益状态进行限制(步骤 3)。
“套餐和插件需要使用不同的事件处理程序。”它们共用一个 app.subscription.* 系列——根据 item_type 分支处理(插件由 item_slug 标识)。使用不同的处理程序会漏掉一半事件(步骤 3)。
“我只需要订阅事件;试用是另一回事。”试用采用相同的负载结构(app.trial.*)。忽略这些事件会导致试用开始或到期时的访问限制处理错误(步骤 3–4)。
“我会对订阅/试用事件调用 subscribe。”它们是应用事件——应用默认会订阅自己的应用事件,因此 Salla 会自动将整个系列(started/renewed/expired/canceled + app.trial.*)发送到你的 webhook_url。设置 webhook_url 并处理这些事件;salla_events action=subscribe 仅用于店铺事件(步骤 2、4)。
“我会信任客户端发送的余额。”在服务端计算用量,并通过你自己的身份验证调用 POST /apps/balance;然后读取余额进行确认。客户端报告的余额可以伪造(步骤 5b)。
“事件很可靠——我不需要对账。”停机期间漏掉 webhook 会导致存储状态永远过时。GET /apps/{app_id}/subscriptions 可用于兜底(步骤 5)。
“事件到达时,我就立即限制访问。”事件可能延迟或重复。根据已存储的 status 和 end_date 管理访问权限,并以幂等方式重新计算(步骤 4)。
“演示店铺的订阅事件也应该像真实事件一样计费。”当 store_type !== "live" 时跳过计费逻辑,否则开发环境中的安装会破坏真实套餐状态(步骤 3)。

Key Resources

ResourceURL
App Subscription Details (GET)https://docs.salla.dev/5401098e0.md
Update Subscription Balance (POST)https://docs.salla.dev/5401099e0.md
Apps APIhttps://docs.salla.dev/421412m0.md
App Eventshttps://docs.salla.dev/421413m0.md
Lifecycle wiringsalla-app-lifecycle skill
Feature gatingthis skill — see Entitlement Gating
Partners Portalhttps://salla.partners
Telegram communityhttps://t.me/salladev

评论

支持 Markdown