关于 salla-addon-purchase-embedded
用于构建 Salla 嵌入式应用内附加项购买流程的技能。它通过嵌入式 SDK Checkout 模块列出附加项、打开 Salla 结账抽屉,并在服务端激活前显示待处理状态。
主要功能
- 01获取附加项及其标识
- 02打开原生结账抽屉
- 03监听结账结果
- 04等待基于 Webhook 的激活
适用场景
- 01在嵌入式应用中展示可购附加项
- 02使用返回的附加项 slug 发起结账
- 03付款后显示待处理状态
- 04在 3DS 跳转后恢复流程
MCP 相关
购买流程使用嵌入式 SDK Checkout 模块,而非 MCP 工具;本技能未描述结账相关的 MCP 操作。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ sallaapp/ salla-partners-agent-kit --skill salla-addon-purchase-embedded安装后在 Claude Code 中输入/salla-addon-purchase-embedded确认已加载。
详细介绍
Salla 插件购买 — 嵌入式 UX
购买插件在嵌入式应用内进行——原生结账抽屉会在 Salla Merchant Dashboard 中打开,而不是跳转到单独的页面或外部链接。此技能仅负责购买 UX:列出插件、打开结账抽屉、处理结果。支付后的所有事项——激活、权益、续订——均由 Webhook 驱动,并由 salla-addon-purchase 负责。
工具与 MCP
iframe 内购买使用的是嵌入式 SDK Checkout 调用,而不是 MCP 工具——此处不会运行任何 MCP 操作。
| 交接内容 | 技能 |
|---|---|
| SDK 初始化/握手/无导航栏 | salla-embedded-app |
| 插件定义、激活 Webhook、权益 | salla-addon-purchase |
| 定价基础要素 | salla-app-billing |
前置条件 — 可正常运行的嵌入式应用
先通过 salla-embedded-app 构建嵌入式应用(SDK 已初始化、握手已完成、会话令牌已在服务端验证、无导航栏)。从此处开始,假设会话已通过身份验证并处于嵌入式环境中。
检查项:“嵌入式 SDK 是否已初始化,并且会话令牌已在服务端验证?”
Checkout 模块
购买入口是嵌入式 SDK 的 Checkout 模块——三个方法即可覆盖完整流程:
embedded.checkout.getAddons()— 获取应用的插件(slug、名称、价格)。 文档:https://docs.salla.dev/embedded-sdk/modules/checkout/add-ons.mdembedded.checkout.create(input, config?)— 打开原生结账抽屉。 文档:https://docs.salla.dev/embedded-sdk/modules/checkout/create.mdembedded.checkout.onResult(callback)— 接收支付结果(包括 3DS 重定向后的结果)。 文档:https://docs.salla.dev/embedded-sdk/modules/checkout/result.md
这三份 Checkout 模块文档是前端购买周期的权威依据。SDK 签名和载荷会因 @salla.sa/embedded-sdk 版本不同而有所变化——请对照文档或软件包确认,不要硬编码价格或 URL。插件的 slug 是你预先确定的标识符——发布时将其设为插件的 slug,并在订阅 Webhook 和 API 中将其作为 item_slug 进行匹配——因此直接引用自己的 slug 是预期做法。
步骤 1 — 列出应用的插件(getAddons)
插件在 Partners Portal 的应用发布表单的 Pricing 步骤中定义。
getAddons 会在运行时返回权威列表;宿主会缓存约 30 分钟,因此重复调用的开销很低。
来源:https://docs.salla.dev/embedded-sdk/modules/checkout/add-ons.md
import { embedded } from "@salla.sa/embedded-sdk";
// embedded.checkout.getAddons(): Promise<GetAddonsResult>
// GetAddonsResult = { success: boolean; addons?: AddonInfo[]; error?: { code; message } }
// AddonInfo = { slug; name; price; product_id; product_price_id }
async function loadAddons() {
embedded.ui.loading.show();
const result = await embedded.checkout.getAddons();
embedded.ui.loading.hide();
if (!result.success) {
embedded.ui.toast.error(result.error?.message ?? "Could not load addons.");
return;
}
// Render with Salla's design tokens (No-Chrome): name, what it unlocks, price.
// Each "Buy" wires the addon's slug straight into startAddonPurchase (Step 2).
result.addons!.forEach((addon) => {
renderAddonCard({
name: addon.name,
price: addon.price,
onBuy: () => startAddonPurchase(addon.slug),
});
});
}
getAddons() 遇到网络/超时错误时会正常返回(不会抛出异常)——务必处理 success: false。在 create() 中原样使用返回的 slug 值;与可用插件不匹配的 slug 会导致结账失败。
检查项:“Slug 和价格是否来自 getAddons(),而非硬编码?”
Step 2 — 打开结账界面(create)并监听结果(onResult)
create() 会在 Salla Dashboard 中打开原生支付抽屉。商家在 Salla 账单页面内完成付款 — 你无需收款。请先注册 onResult(在调用任何 create() 之前),以免错过响应,包括 3DS 重定向重新加载 iframe 后收到的结果。
来源: https://docs.salla.dev/embedded-sdk/modules/checkout/create.md · https://docs.salla.dev/embedded-sdk/modules/checkout/result.md
import { embedded } from "@salla.sa/embedded-sdk";
// Register the result listener once, during init — catches post-3DS-redirect results too.
// embedded.checkout.onResult(callback): () => void (returns an unsubscribe fn)
// CheckoutResult = {
// success: boolean;
// order_id?: string;
// status: "paid" | "pending" | "failed" | "cancelled" | "success";
// error?: { code; message };
// context?: unknown; // whatever you passed to create()'s config.context
// }
const unsubscribe = embedded.checkout.onResult((result) => {
if (result.context?.route) router.push(result.context.route); // restore state after 3DS
if (result.success) {
// Checkout completed — show PENDING, do NOT unlock here (Step 3).
embedded.ui.toast.show("Finishing your purchase…");
} else if (result.error) {
embedded.ui.toast.error(result.error.message);
}
});
// call unsubscribe() on unmount to avoid leaks.
// embedded.checkout.create(input, config?): void
// input = CheckoutItem | CheckoutItem[]
// CheckoutItem = { type: "addon"; slug: string; quantity?: number /* default 1 */ }
// config = { context?: unknown } // persisted across 3DS redirects, returned in onResult
function startAddonPurchase(slug: string) {
try {
embedded.checkout.create(
{ type: "addon", slug },
{ context: { source: "embedded-upsell" } },
);
} catch (e) {
// create() throws synchronously on invalid input
// ("At least one item is required", "...must have a valid slug/type").
embedded.ui.toast.error("Couldn't start checkout");
}
}
对于商品篮,请传入一个数组 — create([{ type: "addon", slug, quantity: 2 }, …])。type 目前始终为 "addon"。使用 context 暂存路由/步骤,以便 iframe 在 3DS 后重新加载时恢复。
结账后,商家会返回 iframe;会话令牌可能已轮换。如果后端 API 调用返回 401,请调用 embedded.auth.refresh()(→ salla-embedded-app)并重新初始化。
检查项:“是否已在 create() 之前注册 onResult,并通过 create() 使用真实的 getAddons() slug 打开结账,而非使用硬编码 URL?”
Step 3 — 移交给 Webhook 以解锁功能
success 结果(或 status: "paid")表示结账已在 iframe 中完成 — 仅将其视为 UI 信号。功能通过激活 Webhook 解锁,该 Webhook 由 salla-addon-purchase 负责(它会处理 app.subscription.started、item_type: "addon",通过 item_slug 匹配,并进行签名验证和幂等处理 — 传输规则见 salla-webhooks)。
收到成功结果后,显示待处理状态,并等待激活完成:
// inside onResult, on result.success:
embedded.ui.toast.show("Finishing your purchase…");
// when salla-addon-purchase has activated the entitlement, reveal the feature:
embedded.ui.toast.success("Addon activated"); // after entitlement is persisted server-side
从经过验证的后端轮询/刷新权益;只有在权益持久化到服务器后才显示该功能,并在 Webhook 到达之前保持待处理状态。
检查项:“结果是否仅显示待处理状态?功能是否仅在 salla-addon-purchase 持久化权益后才显示?”
端到端(此技能负责上半部分)
[embedded page] getAddons() → render offer (this skill)
│ merchant clicks Buy
▼
checkout.create({ type:"addon", slug }) (this skill — opens native drawer)
│ merchant pays in Salla billing (3DS may redirect & reload the iframe)
▼
checkout.onResult(result) → pending state (this skill; token may rotate → embedded.auth.refresh)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
▼ (webhook — salla-addon-purchase owns this)
app.subscription.started (addon) → activate entitlement → reveal feature
危险信号
| 容易产生的想法 | 为什么不对 |
|---|---|
“onResult 返回了 success,所以我现在就解锁插件。” | 客户端结果只能将 UI 切换到待处理状态 — 仅在通过签名验证的 app.subscription.started Webhook 上激活权益(item_type: "addon"),由 salla-addon-purchase 处理(步骤 2–3)。 |
| “我要在嵌入页面中添加自己的银行卡表单/支付步骤。” | 账单由 Salla 负责。 使用 checkout.create({type:"addon", slug}) 打开原生抽屉 — 绝不自行收集或存储付款信息(结账模块/步骤 2)。 |
| “我要通过已购插件的名称或数组索引进行匹配。” | 通过 item_slug 匹配 — 这是你为插件设置的稳定标识符 slug,每次生命周期事件都会收到它(步骤 3)。 |
| “我要相信客户端结果来跟踪商家拥有的内容。” | 通过服务器核对权益(Webhook + 订阅 API),而非客户端 onResult — 令牌甚至可能在流程中途轮换(embedded.auth.refresh)(步骤 3)。 |
Key Resources
| Resource | URL / Skill |
|---|---|
| Embedded SDK setup / No-Chrome | salla-embedded-app skill |
| Checkout · Get Add-ons | https://docs.salla.dev/embedded-sdk/modules/checkout/add-ons.md |
| Checkout · Create | https://docs.salla.dev/embedded-sdk/modules/checkout/create.md |
| Checkout · Subscribe Result | https://docs.salla.dev/embedded-sdk/modules/checkout/result.md |
| Activation + entitlement lifecycle | salla-addon-purchase skill |
| Pricing / definition | salla-app-billing skill |
@salla.sa/embedded-sdk | npm package (Checkout module authority) |
| Partners Portal | https://salla.partners |