关于 salla-app-lifecycle
面向 Salla 应用开发者的生命周期处理技能,指导配置 webhook 并编写安装、授权、订阅与卸载事件处理器。重点是按商家记录进行幂等更新,并处理签名验证与异步任务可靠性。
主要功能
- 01配置生命周期事件 webhook
- 02处理安装、更新与卸载
- 03处理试用和订阅状态
- 04按商家幂等更新记录
- 05指导签名校验与异步重试
适用场景
- 01接入 Salla 应用安装流程
- 02同步商家令牌与订阅状态
- 03实现卸载后的数据清理
MCP 相关
通过 Salla Partners MCP 的 `salla_apps` 工具设置 webhook 接收地址并检查应用状态;应用生命周期事件会自动投递到该地址。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sallaapp/ salla-partners-agent-kit --skill salla-app-lifecycle安装后在 Claude Code 中输入/salla-app-lifecycle确认已加载。
附带文件references/lifecycle-payloads.md
详细介绍
Salla 应用生命周期流程
让后端与每位商家和应用的关系保持同步——从安装到试用、订阅和卸载。请按顺序完成各步骤;完成每个关卡后再继续。步骤 1 使用 Salla Partners MCP 设置 Webhook 接收端;步骤 2–6 是你需要编写的运行时处理程序。
工具与 MCP
App Events 文档(https://docs.salla.dev/421413m0.md)是所有生命周期负载结构的权威来源。**Salla Partners MCP** 负责执行操作:
| 工具 | 操作 | 功能 |
|---|---|---|
salla_apps | connect / get | 设置 webhook_url(生命周期应用事件会自动投递到此 URL)/检查应用状态 |
生命周期事件是
app.*事件——无需订阅调用,系统会自动投递。 默认情况下,应用已订阅其自身的应用事件,因此app.installed、app.store.authorize、app.updated、app.uninstalled、app.trial.*、app.subscription.*和app.settings.updated都会在触发时自动发送到你的webhook_url。 你无需对其中任何事件调用salla_events action=subscribe——此操作仅用于非应用(商店)事件(order.*、product.*等)→ salla-webhooks。
所有事件都会通过标准封装(
event、merchant、created_at、data)发送到你的单一 Webhook 端点。验证签名,快速返回200确认,然后异步处理。始终以merchant为键执行 upsert。 前置条件:签名验证 + 幂等性 + 快速返回 200 → salla-webhooks;从app.store.authorize持久化令牌 → salla-app-auth。
事件目录(参考)
| 事件 | 触发时机 | 操作建议 |
|---|---|---|
app.installed | 首次安装 | 配置商家资源,设置默认值 |
app.store.authorize | 安装或刷新令牌 | 按商家保存/更新 access_token + refresh_token + 过期时间 |
app.updated | 商家更新应用 | Salla 随后会立即触发 app.store.authorize — 等待该事件以获取新令牌 |
app.settings.updated | 商家更改应用设置 | 应用 data.settings — 架构/验证由 salla-app-settings 负责 |
app.trial.started | 试用开始 | 启用试用功能 |
app.trial.expired | 试用结束且未升级 | 限制访问 |
app.trial.canceled | 试用已取消 | 限制访问 |
app.subscription.started | 付费计划或附加项已激活 | 解锁功能;根据 data.item_type 分支处理 |
app.subscription.renewed | 计划/附加项已续订 | 确认处于有效状态;保存新的 data.end_date / renew_date |
app.subscription.expired | 计划/附加项已到期 | 限制访问,通知商家 |
app.subscription.canceled | 计划/附加项已取消 | 限制访问 |
app.feedback.created | 商家提交评价 | 记录评分/评论 |
app.subscription.*会针对计划和附加项同时触发 —data.item_type("plan"|"addon")用于区分两者。app.trial.*的载荷比订阅事件更精简(不包含subscription_id、item_type、定价等)— 请参阅参考资料。完整的订阅载荷和计划状态处理 → salla-app-billing。各事件的完整 JSON(以 https://docs.salla.dev/421413m0.md 中的权威结构为准)→ references/lifecycle-payloads.md。
步骤 0 — 探索
开始前请先询问:
- 你的应用关注哪些生命周期状态?(仅安装,还是也包括试用和订阅?)
- 安装时要配置哪些内容,卸载时必须清理哪些内容(数据保留/GDPR 政策)?
- 是否要根据计划/试用状态限制功能?(如果是,请配合 salla-app-billing)
步骤 1 — 设置 Webhook URL(生命周期事件会自动投递)
生命周期事件是应用事件——应用默认已订阅这些事件,因此设置 webhook_url 后,事件会立即送达。无需调用订阅接口;只需通过 Partners MCP 配置接收端:
- 配置接收端:
salla_apps action=connect、app_id、webhook_url、webhook_security_strategy: "signature"。connect不会生成签名密钥——请在 Partner Portal(https://portal.salla.partners/apps/{app_id})中创建或轮换密钥,并在部署前通过salla_apps action=get(webhook_secret字段)读取当前值。将其存入密钥管理器/KMS(绝不要存入源代码或日志)——安全存储和 HMAC 验证由 salla-webhooks 负责。 - 投递配置到此为止:
app.installed、app.store.authorize、app.updated、app.uninstalled、app.trial.*、app.subscription.*和app.settings.updated现在都会自动发送到该 URL。(salla_events action=subscribe用于店铺事件——order.*、product.*等——生命周期处理无需订阅这些事件 → salla-webhooks。)
随时使用 salla_apps action=get、app_id 查看应用的当前配置。
检查点:“已通过 salla_apps action=connect 设置 webhook_url(应用事件会自动发送到该地址,无需调用订阅接口),且你的端点会返回 200?”
步骤 2 — 处理安装流程
首次安装时,Salla 会发送 app.installed 和 app.store.authorize。请将每个处理程序视为独立且幂等的;事件到达顺序无法保证。
在每个处理程序中对每个
merchant记录执行插入或更新(创建或更新)。 任何生命周期事件——例如app.trial.started——都可能先于app.store.authorize到达,因此记录可能尚不存在。每个处理程序都应在商户记录不存在时创建,存在时更新,并以merchant为键。这样无论事件以何种顺序到达,或应用重新安装,每个处理程序都能安全运行。
if (payload.event === "app.installed") {
// Provision: create the merchant row, seed defaults, queue a welcome step.
await db.merchants.upsert({
where: { id: payload.merchant },
create: {
id: payload.merchant,
status: "installed",
installedAt: new Date(),
},
update: { status: "installed" },
});
}
if (payload.event === "app.store.authorize") {
// Persist tokens — see salla-app-auth for the full upsert + refresh rules.
// Encrypt access_token/refresh_token at rest and keep them out of logs.
const { access_token, refresh_token, expires, scope } = payload.data;
await db.merchants.upsert({
where: { id: payload.merchant },
create: {
id: payload.merchant,
accessToken: access_token,
refreshToken: refresh_token,
tokenExpiresAt: new Date(expires * 1000),
scope,
},
update: {
accessToken: access_token,
refreshToken: refresh_token,
tokenExpiresAt: new Date(expires * 1000),
scope,
},
});
}
使用带版本控制的迁移工具管理商户/令牌表(使用带版本号的
ALTER TABLE)。随着添加或更改列,这样可确保架构与代码保持同步;而启动时创建表的步骤会跳过已存在的表。
检查点:“演示店铺安装后,无论事件以何种顺序到达,都会创建商户记录并持久化令牌?”
步骤 3 — 处理更新流程
商户更新应用时,Salla 会先发送 app.updated,然后立即发送包含新令牌的 app.store.authorize。
- 将
app.updated仅视为一个信号——不要指望其负载中包含令牌。 - 让现有的
app.store.authorize处理程序持久化新令牌。
检查点:“应用更新会通过授权处理程序刷新已存储的令牌,而不是通过更新处理程序刷新?”
步骤 4 — 处理卸载清理
app.uninstalled 表示商户已移除你的应用。清理是 App Store 的要求——请按照你的数据保留/GDPR 政策进行处理。
if (payload.event === "app.uninstalled") {
await jobs.enqueue("cleanupMerchant", { merchantId: payload.merchant });
// In the job: revoke cached tokens, delete or anonymize PII, cancel scheduled work,
// mark merchant status = "uninstalled". Keep only what your policy/law requires.
}
请说明存储了哪些数据,以及卸载时如何删除这些数据——提交审核时会检查此项。
检查点:“卸载会将清理任务加入队列,且任务会撤销令牌并移除/匿名化 PII?”
步骤 5 — 处理试用与订阅事件
| 事件 | 效果 |
|---|---|
app.trial.started | 启用试用版功能;记录试用结束时间 |
app.trial.expired | 降级/限制功能,直到商户订阅 |
app.trial.canceled | 立即限制功能 |
商户通常会经历 trial.started →(转为付费时为 subscription.started;未转为付费时为 trial.expired)。此处仅提供最低限度的订阅处理——完整逻辑见 salla-app-billing:
if (payload.event === "app.subscription.started") {
const { item_type, item_slug, end_date } = payload.data; // item_type: "plan" | "addon"
if (item_type === "addon") {
await entitlements.activateAddon(payload.merchant, item_slug, end_date);
} else {
await merchants.activatePlan(payload.merchant, payload.data);
}
}
留意 data.store_type(development | demo | live),以免将测试店铺的订阅视为真实收入。
检查点:“试用和订阅事件会根据 item_type 分支,并更新商户已存储的状态?”
步骤 6 — 加固:可靠性与状态机
以上每个处理程序都必须满足以下要求:
- 首先验证签名(HMAC-SHA256,使用时序安全比较),否则拒绝请求。
- 快速确认,返回
200,然后异步处理 — Salla 会重试失败的投递; 超时与重试策略由 salla-webhooks 管理。 - 确保幂等 — 使用载荷中的稳定标识符(例如
subscription_id)或原始请求体的哈希作为键。(created_at精确到秒,因此 单独使用${merchant}:${event}:${created_at}可能发生冲突。) - 使用 upsert,以便同一商家可以重新安装并重新授权。
- 处理异步任务失败 —
200已经发出,因此排队的工作进程中抛出的异常对 Salla 不可见。将失败的任务路由到死信队列,采用退避策略重试(确保幂等),并在重试耗尽时发出告警,以免安装或卸载事件丢失而无人察觉。
建议的商家状态机 — 按商家持久化 status,并从以上处理程序触发状态转换:
app.installed
│
▼
┌──────► installed ──────┐
│ │ │ app.trial.started
│ │ ▼
│ │ trial ──── app.trial.expired/canceled ──► restricted
│ │ │ app.subscription.started
│ ▼ ▼
│ active ◄── app.subscription.renewed
│ │
│ │ app.subscription.expired/canceled
│ ▼
│ restricted
│ │ app.subscription.started (merchant re-purchases)
└───────────┘
│ app.uninstalled (from any state)
▼
uninstalled (run cleanup)
根据 status(以及附加组件的权益 — 参见 salla-app-billing)控制功能。
检查:“每个处理程序都会验证签名、快速返回 200、确保幂等,并按照状态机更新 status 吗?”
风险信号
在快速测试中看似成立、但面对真实商家时会出问题的生命周期投递假设。如果你的方案包含以下任何一点,请重新阅读所标注的步骤。
| 容易产生的想法 | 错误原因 |
|---|---|
"app.installed 会先于 app.store.authorize 到达。" | 事件顺序不保证 — 任何事件都可能先到。让每个处理程序彼此独立并确保幂等,按 merchant 执行 upsert(步骤 2)。 |
"app.updated 会携带新令牌。" | 它只是一条信号 — 令牌会在之后的 app.store.authorize 中提供。不要从更新事件的载荷中读取令牌(步骤 3)。 |
"insert 没问题 — 应用只会安装一次。" | 商家可能会重新安装并重新授权。仅插入会报错或产生重复数据;始终按 merchant 执行 upsert(步骤 2、6)。 |
| "异步任务已经入队,因此工作是安全的。" | 200 已经发出 — 工作进程中抛出的异常对 Salla 不可见。使用死信队列,进行幂等重试,并发出告警(步骤 6)。 |
"${merchant}:${event}:${created_at} 是唯一键。" | created_at 精确到秒;同一秒内两个同类型事件会发生冲突。添加 subscription_id 或原始请求体的哈希(步骤 6)。 |
| "我会把 test-store 的订阅事件计为收入。" | 检查 data.store_type — 只有 live 才是真实数据;开发或演示安装会污染收入和权益(步骤 5)。 |
| "卸载只需切换状态标记。" | 清理工作(撤销令牌、删除或匿名化 PII)是 App Store 的要求,提交时会进行审核 — 将完整任务加入队列(步骤 4)。 |
关键资源
| 资源 | URL |
|---|---|
| 应用事件文档 | https://docs.salla.dev/421413m0.md |
| Webhooks(安全) | salla-webhooks 技能 |
| 令牌处理 | salla-app-auth 技能 |
| 套餐/试用状态 | salla-app-billing 技能 |
| Partners Portal | https://salla.partners |
| Telegram 社区 | https://t.me/salladev |