mcpField
S
salla-addon-purchase-embedded

salla-addon-purchase-embedded

github.com/sallaapp開発281 回インストール3 週間前に更新
リポジトリ

salla-addon-purchase-embedded について

Salla の組み込みアプリ内でアドオン購入フローを構築するスキルです。組み込み SDK の Checkout モジュールでアドオン一覧と決済ドロワーを扱い、サーバー側で有効化されるまで保留状態を表示します。

主な機能

  1. 01アドオンと識別子を取得
  2. 02ネイティブ決済ドロワーを表示
  3. 03決済結果を受信
  4. 04Webhook による有効化を待機

活用シーン

  1. 01組み込みアプリに購入可能なアドオンを表示
  2. 02取得したアドオン slug で決済を開始
  3. 03決済後に保留状態を表示
  4. 043DS リダイレクト後にフローを復元

MCP 対応

購入フローでは MCP ツールではなく、組み込み SDK の Checkout モジュールを使います。このスキルには決済関連の 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 の初期化/ハンドシェイク/No-Chromesalla-embedded-app
アドオンの定義、有効化 Webhook、利用権salla-addon-purchase
料金設定の基本機能salla-app-billing

前提条件 — 動作する埋め込みアプリ

まず salla-embedded-app を使って埋め込みアプリを構築します(SDK の初期化、ハンドシェイクの完了、セッショントークンのサーバー側での検証、No-Chrome)。以降は、認証済みの埋め込みセッションがあるものとします。

確認事項:「埋め込み SDK は初期化され、セッショントークンはサーバー側で検証済みですか?」


Checkout モジュール

購入のエントリーポイントは、埋め込み SDK の Checkout モジュールです。3 つのメソッドでフロー全体をカバーします。

これら 3 つの Checkout モジュールのドキュメントが、フロントエンドの購入サイクルの信頼できる情報源です。SDK のシグネチャとペイロードは @salla.sa/embedded-sdk のリリースによって異なるため、ドキュメントまたはパッケージで確認し、価格や URL をハードコードしないでください。アドオンの slug は事前に把握している固有の識別子です。公開時にアドオンの slug として設定し、サブスクリプション Webhook と API では item_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 を処理してください。返された slug の値をそのまま create() で使用します。利用可能なアドオンと一致しないスラッグでは、チェックアウトに失敗します。

確認事項:「スラッグと価格はハードコードせず、getAddons() から取得していますか?」


ステップ 2 — チェックアウトを開き(create)、結果を受け取る(onResult)

create() は、Salla ダッシュボードにネイティブの決済ドロワーを開きます。マーチャントは 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" です。iframe が 3DS 後に再読み込みされたときに復元できるよう、context に ルートやステップを保存してください。

チェックアウト後、マーチャントは iframe に戻ります。セッショントークンがローテーションされている 場合があります。バックエンド API の呼び出しが 401 を返したら、embedded.auth.refresh() を呼び出し (→ salla-embedded-app)、再度ブートストラップしてください。

確認事項:「create() の前に onResult を登録し、ハードコードした URL ではなく、実際の getAddons() の slug を使って create() 経由でチェックアウトを開いていますか?」


ステップ 3 — Webhook に引き継いでロックを解除する

success の結果(または status: "paid")は、iframe 内でチェックアウトが完了したことを示すだけです。 UI のシグナルとしてのみ扱ってください。機能のロック解除は、salla-addon-purchase が管理する アクティベーション webhook によって行います(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 が成功を返したので、今すぐアドオンのロックを解除しよう。」クライアントの結果によって UI が保留中の状態に変わるだけです — 署名検証済みの app.subscription.started webhook を受け取った場合にのみ、salla-addon-purchase(ステップ 2〜3)経由でエンタイトルメントを有効化してください(item_type: "addon")。
「埋め込みページに独自のカードフォーム/決済ステップを追加しよう。」課金は Salla が管理します。 checkout.create({type:"addon", slug}) でネイティブのドロワーを開いてください — 決済を自分で受け付けたり保存したりしないでください(Checkout モジュール/ステップ 2)。
「購入されたアドオンを名前や配列のインデックスで照合しよう。」item_slug で照合してください — これはアドオンの slug として設定し、すべてのライフサイクルイベントで受け取る、安定した識別子です(ステップ 3)。
「マーチャントが所有するものを追跡するため、クライアントの結果を信頼しよう。」クライアントの onResult ではなく、サーバー(webhook と subscriptions API)に照らしてエンタイトルメントを照合してください — フローの途中でトークンがローテーションされることもあります(embedded.auth.refresh)(ステップ 3)。

Key Resources

ResourceURL / Skill
Embedded SDK setup / No-Chromesalla-embedded-app skill
Checkout · Get Add-onshttps://docs.salla.dev/embedded-sdk/modules/checkout/add-ons.md
Checkout · Createhttps://docs.salla.dev/embedded-sdk/modules/checkout/create.md
Checkout · Subscribe Resulthttps://docs.salla.dev/embedded-sdk/modules/checkout/result.md
Activation + entitlement lifecyclesalla-addon-purchase skill
Pricing / definitionsalla-app-billing skill
@salla.sa/embedded-sdknpm package (Checkout module authority)
Partners Portalhttps://salla.partners

コメント