salla-app-billing について
Salla App Billing Flowは、Sallaアプリのプラン、アドオン、トライアル、従量課金を実装する開発者向けスキルです。Salla APIとPartners MCPを使った価格設定、Webhook処理、権限管理、購読状態の照合を案内します。
主な機能
- 01価格設定でプランとアドオンを構成
- 02課金イベントのWebhookを接続
- 03プランとアドオンの権限を管理
- 04更新・期限切れ・トライアルを処理
- 05APIで購読状態を照合
活用シーン
- 01Sallaアプリの購読価格を設定
- 02プランやアドオンに応じて機能を制御
- 03Webhook欠落後に課金状態を復旧
- 04外部の定期更新を管理
MCP 対応
Salla Partners MCPを使い、公開価格の設定と検証、アプリのWebhook URL設定、購読詳細の読み取りを行います。また、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.mdsubscription-api.mdsubscription-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)と照合します。この
スキルが利用する実際のエンドポイントは2つあります。
GET /apps/{app_id}/subscriptions— プランとアドオンの両方について、サブスクリプションのステータスと詳細を取得します (item_typeでフィルタリング):プランの状態、利用権限、日付、subscription_balance(照合、手順5)。完全なOpenAPIスキーマ(信頼できる情報源): https://docs.salla.dev/5401098e0.mdPOST /apps/balance— 従量課金の使用残高を書き戻します(手順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、1日1回)。support_renew: trueのアドオンにも適用されます。(Salla管理の定期更新は自動的に更新されます。 受信するのはapp.subscription.renewedだけです。)リクエスト/レスポンスの正確なスキーマと エラーレスポンスの完全な仕様は、稼働中のOpenAPIドキュメントで確認してください。これが信頼できる情報源です: https://docs.salla.dev/37396517e0.md(データ構造を手作業で実装せず、ドキュメントに準拠させてください)。
実装前に、App Eventsリファレンス (https://docs.salla.dev/421413m0.md)または`salla_events action=list`でペイロードとフィールドの形式を確認してください。 操作はSalla Partners MCPで実行します。
| ツール | アクション | 内容 |
|---|---|---|
app_publish | set validate | pricingセクション(プラン/アドオン)を設定し、公開を検証します |
salla_apps | connect | アプリイベント(app.subscription.*/app.trial.*)が自動配信されるwebhook_urlを設定します |
salla_apps | subscriptions | 読み取り専用:アプリのサブスクリプション詳細 |
プランとアドオン
| 概念 | 内容 | item_type | item_slug |
|---|---|---|---|
| プラン | マーチャントがアプリで利用するメインのサブスクリプション | "plan" | null |
| アドオン | プランに追加して購入するもの | "addon" | アドオン識別子 |
どちらも同じapp.subscription.*/app.trial.*イベントを通じて処理されます。**このスキルは、
プラン、アドオン、利用権限の制御、使用量課金を管理します。**アプリ内でのアドオン購入 →
salla-addon-purchase。plan_typeの値:free・once(1回限り)・
recurring(月次/年次)・on_demand(従量課金)。
手順0 — 確認する
開始前に質問してください。
- どのプランタイプを提供しますか?(無料、月次、年次、トライアル、1回限り、従量課金)
- 各プランで何が利用可能になりますか?(
features[]から利用権限へのマッピング) - 有料プランの前に無料トライアルが必要ですか?
ステップ 1 — 料金プランを定義する(publication の pricing セクション)
プランおよびアドオンは、publication の pricing セクション内で定義します —
app_publish action=set section=pricing data={…} の後に、app_publish action=validate を実行します。
独立した料金エンドポイントはありません。Portal の料金設定ウィザードのステップは、同じセクションを操作する 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 の場合(配送/コミュニケーションアプリ、またはshow_app_free_plan機能)のみ利用できます。無料プランを提示する前に、アプリの詳細から値を確認してください(→ references/pricing-shapes.md)。- 定期課金 —
plans[](最大 8 件。月額 0〜4 件、年額 0〜4 件)。各プラン: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[]があります。 - 一回払い —
one_time_price、one_time_old_price(priceより大きい値)、plan_additional_features[]{key,name,price,adjustable,min,max}。plans[]はありません。 - 従量課金 —
balanceが必須のplans[]と、on_demand_type(emails|messages|per-transaction)。 - アドオン(すべてのタイプで最大 5 件)—
{name,description,price,slug,support_renew}。各エントリでname、price、slugは必須です。いずれかが欠けているアドオンはサーバーに拒否されます。すべての価格は SAR です。通貨フィールドはありません。常に一回払いです。唯一の定期課金制御はsupport_renewです。trueの場合はexternal_recurring(更新 API を使って更新を管理します。アドオンのタイトル/説明にサイクルを明記してください)、そうでない場合はonceイベントになります。 - トライアルはトップレベルです:
plan_trial(日数。最小 1 日。最大トライアル日数で上限が設定されます。デフォルトは 7 日)とtrial_description(30〜1000 文字)。解約時の特典:unsubscribe_reward、unsubscribe_email_reward。
命名上の注意点:
plan_type:"recurring"(モデル)と、プランごとのrecurring:"monthly"(期間)は異なります。 また、plan_additional_features(トップレベルの一回払い)と、プランごとのadditional_features(定期課金)も異なります。
すべてのフィールドの一覧、型、サーバーのルールについては、 references/pricing-shapes.md を読み込んでください。
このセクションは app_publish action=set section=pricing で設定し、その後 app_publish action=validate を実行します(ドラフトを保存します)。公開の全手順 → salla-publication-consistency。
| プランの種類 | 用途 |
|---|---|
| 無料 | 料金なし |
| 月額/年額 | 定期課金(plan_type: recurring) |
| トライアル | 有料プラン開始前の期間限定の無料利用 |
| 一回払い | 一度限りの課金(plan_type: once) |
| 従量課金 | 使用量/従量課金(plan_type: on_demand) |
| アドオン | プランに追加する機能(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.* など、アプリ以外の(ストア)イベントに のみ使用します。課金はすべてアプリイベントで処理されるため、このスキルでは購読の呼び出しは不要です。
アプリイベントは自動配信されます — webhook_url を設定したら、イベントを処理してください。 すべての課金イベント —
app.subscription.started、app.subscription.renewed、
app.subscription.expired、app.subscription.canceled、および app.trial.* イベントは、
salla_events action=list のカタログに掲載されているかどうかにかかわらず(renewed/expired/canceled はカタログにすらありません)、発生するとすぐに webhook_url に届きます。必要なのは、購読することではなく、Webhook ハンドラー(ステップ 4)でこれらすべてを処理することです。App Events リファレンス
(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 の呼び出しはないか — 購読はストアイベント専用です。」
ステップ 3 — イベントからプランの状態を追跡する
セキュリティ — イベントによって有料アクセスが付与されます。 偽造またはリプレイされたサブスクリプション/トライアルの Webhook によって、有料機能が付与されたり取り消されたりする可能性があります。プラン/権限の状態を変更する前に、Webhook 署名を検証し、冪等性を確保してください。このトランスポート層(署名検証、リプレイ保護、高速な 2xx 応答)は salla-webhooks が担当し、トークン/OAuth は salla-app-auth が担当します。権限の変更を正式なものとして扱うのは、検証済みのサーバーイベントまたは照合済みの Partners API(ステップ 5)からの情報に限り、クライアントが報告したプランを根拠にしてはいけません。権限の読み書きは、認証済みの独自管理者用パス経由に限定し、署名シークレットとトークンをログに記録しないでください。
イベントは salla-app-lifecycle 経由で接続します。ここで重要な差分は次のとおりです。
item_typeで分岐する —"plan"はプランの状態を更新し、"addon"はアドオンの権限を更新します(ペイロードの形式は共通で、どのアドオンかはitem_slugで識別します)。- トライアルも同じペイロード形式です —
app.trial.started/.expired/.canceledも処理してください。処理しないと、トライアルの開始/期限切れが見落とされ、マーチャントに誤ったアクセス制御が適用されます。 started/renewedではend_date ?? renew_dateを保存し、expired/canceledではサブスクリプションを非アクティブにします。store_type !== "live"の場合は課金ロジックをスキップする(開発/デモストア)。
ペイロードのフィールドと完全な例 → references/subscription-events.md。ライブドキュメント — App Subscription Webhook Events: https://docs.salla.dev/2213496m0.md ; App Events リファレンス(ペイロード例付きのライフサイクル Webhook): https://docs.salla.dev/421413m0.md。
ゲート:「デモストアのサブスクリプションイベントによって、保存済みプランが正しいステータスで更新されるか?」
Step 4 — 更新、期限切れ、トライアルへの対応
これら3つはアプリのイベントです。webhook_url に自動的に届くため、app.* イベントを購読する必要はありません(Step 2 を参照)。これらを処理してください。
app.subscription.renewed— 引き続き有効であることを確認し、新しいend_date/renew_dateを保存します。以前の終了日を前提にしないでください。app.subscription.expired— プランの有効期限が切れました(更新されていません)。アクセスを制限し、猶予期間を示すバナーの表示を検討してください。app.subscription.canceled— マーチャントがキャンセルしました。ポリシーに従ってアクセスを制限します。- トライアルは
app.trial.started/.expired/.canceled(価格が0のplan_type: once)として届きます。trial.startedではトライアル機能を有効にして終了日を記録し、期限切れまたはキャンセル時にはダウングレードします。トライアルから切り替えたマーチャントには、その後app.subscription.startedが届きます。
イベントの受信時点ではなく、保存した status と end_date に基づいて機能へのアクセスを制御してください(イベントは遅延または重複する可能性があるため、冪等に処理します)。Step 5 はイベントを受信し損ねた場合のバックアップです。
ゲート:「更新時に end_date が更新され、期限切れ/キャンセル時にアクセスが制限され、トライアルが正しく制御されますか?」
Step 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 Application ID(Salla Partners → My Apps → Your App)です。この呼び出しにはマーチャントのアクセストークンが使われるため、data[] にはそのマーチャントがアプリに対して契約しているサブスクリプションが含まれます。アクティブなプラン/アドオンごとに1項目ずつ返されます。ドキュメント:
https://docs.salla.dev/5401098e0.md。
レスポンス(200) — { status, success, data: [...] }。各項目は Subscription Detail であり、プランの状態、権限、利用残高を確認できます。item_type(プランの状態を確認するには "plan" でフィルタリング)、plan_type、start_date/end_date、features[]({ key, quantity })、および subscription_balance(利用残高。Step 5b で書き込みます)を確認してください。403 は、呼び出し元にこのアプリの権限がないことを意味します。
完全なレスポンス例と各フィールドの意味(および POST /apps/balance の形式)は、references/subscription-api.md を参照してください。
ゲート:「照合により、マーチャントの現在のプランと残高が返され、保存済みの状態と一致しますか?」
Step 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(Step 5)で値を読み戻してください。(Step 3 と同じ信頼モデルです。)
ゲート:「POST /apps/balance で残高を書き込み、サブスクリプションを再読み込みして確認しましたか?」
アドオン
アドオンはプランに追加できる有料オプションで、定期課金または買い切りです。公開ペイロードの addons 配列(Step 1)で定義します。プランと同じイベントファミリーで item_type: "addon" として届き(item_slug でアドオンを識別します)、プランと同様に記録して、その features[] を有効化します。アプリ内購入 UI(埋め込み SDK Checkout)については、salla-addon-purchase を参照してください。
エンタイトルメントの制御
アクティブなプランの features[] およびすべてのアクティブなアドオンの features[] を統合し、マーチャントごとに1つのエンタイトルメントセットを作成します。すべての 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
エンタイトルメントのゲーティングのセクションに従って、features[]をエンタイトルメントにマッピングします(プラン機能とアドオン機能を1つのセットに統合します)。ペイロード全体は
**references/subscription-events.md**を参照してください。
注意すべき点
課金イベントは有料アクセスを付与および取り消すため、ここで手を抜くと、有料機能を利用できる状態のままにしたり、料金を支払っているマーチャントを誤って締め出したりすることになります。以下に該当する方法を予定している場合は、指定されたステップを読み直してください。
| つい考えてしまうこと | それが誤りである理由 |
|---|---|
「アドオンの slug/価格は任意だから、省略しておこう。」 | すべてのアドオンエントリで name、price、slug が必須です。不完全なアドオンはサーバーに拒否されます。どれか1つでも省略すると検証エラーになります(ステップ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
| Resource | URL |
|---|---|
| App Subscription Details (GET) | https://docs.salla.dev/5401098e0.md |
| Update Subscription Balance (POST) | https://docs.salla.dev/5401099e0.md |
| Apps API | https://docs.salla.dev/421412m0.md |
| App Events | https://docs.salla.dev/421413m0.md |
| Lifecycle wiring | salla-app-lifecycle skill |
| Feature gating | this skill — see Entitlement Gating |
| Partners Portal | https://salla.partners |
| Telegram community | https://t.me/salladev |