volume-pricing について
数量に応じた価格設定を導入する EC チーム向けのスキルです。B2B のまとめ買いや注文額向上に向け、各プラットフォームの設定とヘッドレス向けの独自ロジックを解説します。
主な機能
- 01数量別の単価設定
- 02顧客グループ別の価格設定
- 03商品ページでの価格段階表示
- 04カート数量変更時の価格再計算
- 05主要プラットフォームと独自実装に対応
活用シーン
- 01卸売顧客への数量割引設定
- 02小売と B2B の価格を分けて管理
- 03まとめ買いの節約額を買い手に提示
- 04ヘッドレスストアで独自の数量価格を実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill volume-pricingインストール後、Claude Code で/volume-pricingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
tile.jsonevals/price-resolution-and-tier-priority-logic/criteria.jsontask.mdevals/pricing-table-display-and-cart-totals/criteria.jsontask.mdevals/volume-pricing-schema-design/criteria.jsontask.md詳細
数量別価格設定
概要
数量別価格設定は、数量ブレイクまたは段階的価格設定とも呼ばれ、注文数量が増えると単価を自動的に引き下げます。これはB2Bや卸売チャネルの中核機能であり、一般消費者向けストアで平均注文額を効果的に伸ばす方法でもあります(「あと3点追加すると10%割引」)。ほとんどのプラットフォームには数量別価格設定が標準搭載されていないため、アプリまたはプラグインが必要です。このスキルでは、各プラットフォームでの設定方法と、ヘッドレスストアフロント向けのカスタム実装について説明します。
このスキルを使用する場面
- 大口注文に対する単価の値下げを期待するB2B購入者に販売する場合
- 小売チャネルと価格設定を分けた卸売チャネルを併設する場合
- まとめ買いによる節約額を顧客に示して、平均注文額を増やしたい場合
- 異なる価格表を持つ複数の顧客グループ(小売、卸売、販売代理店)を管理する場合
- カスタムの大量注文向けに、価格設定・見積もり(CPQ)のフローを構築する場合
基本手順
ステップ1:マーチャントのプラットフォームを特定し、適切なツールを選択する
| プラットフォーム | 推奨ツール | 理由 |
|---|---|---|
| Shopify | Wholesale Club または Quantity Breaks & Discounts by FORSBERG | Wholesale Club は顧客グループ別価格設定を備えた完全なB2Bチャネルを追加します。Quantity Breaks は、商品ページに価格表を表示してB2C向けの段階的価格設定に対応します |
| Shopify Plus | Shopify B2B(標準搭載) | Shopify Plus には、企業アカウント、価格表、最低数量など、標準のB2B機能が含まれています |
| WooCommerce | WooCommerce Dynamic Pricing 拡張機能(年間約99ドル)または YITH WooCommerce Dynamic Pricing & Discounts(年間約70ドル) | どちらのプラグインも、数量に応じた段階的価格設定、顧客ロール別の価格設定、商品ページの価格表に対応しています |
| BigCommerce | 価格表(標準機能、Plus プラン以上) | BigCommerce の価格表機能はB2Bの数量別価格設定向けに設計されており、価格表を顧客グループに割り当てられます |
| カスタム/ヘッドレス | プラットフォームの価格設定APIを呼び出す段階的価格解決ロジックを構築 | 段階的価格設定の定義とカート再計算を完全に制御できます |
ステップ2:プラットフォームで数量別価格設定を行う
Shopify(Plus以外)
オプションA:Quantity Breaks & Discounts アプリ(B2C向け)
- Shopify App Store から Quantity Breaks & Discounts(FORSBERG+two 製)をインストールします(無料プランあり。有料プランは月額約10ドル)
- アプリで Discount Groups → Create に移動します
- 段階的価格設定を適用する商品またはコレクションを選択します
- 数量の段階を追加します:
- 段階1:数量1以上、0%割引(通常価格)
- 段階2:数量5以上、10%割引
- 段階3:数量10以上、20%割引
- 段階4:数量25以上、30%割引
- アプリは商品ページに価格表を自動表示し、顧客がカート内の数量を更新すると適切な割引を適用します
オプションB:Wholesale Club(B2B向け)
- Shopify App Store から Wholesale Club をインストールします(月額約30ドル)
- 卸売顧客グループを作成します(アプリがタグベースのシステムを作成します)
- 商品またはコレクションごとに、固定価格または割引率を設定して卸売価格を設定します
- 卸売顧客に卸売タグを付けて、アクセス権を付与します
- 卸売顧客には、商品ページとチェックアウト時に段階的価格が表示されます
Shopify Plus — 標準のB2B機能
Shopify Plus には、卸売アプリの代わりとなる標準の B2Bチャネル が含まれています:
- Sales channels → B2B に移動します
- B2Bクライアントごとに Company を作成します
- 会社の下に Location を作成し、Price list を割り当てます
- 価格表で、商品ごとに価格(固定価格または割合による調整)を設定します
- Payment terms(net 30、net 60など)と Order minimums を設定します
- 会社の担当者はストアのB2Bポータルからログインすると、割り当てられた価格を自動的に確認できます
WooCommerce
WooCommerce Dynamic Pricing の使用方法:
- WooCommerce.com から WooCommerce Dynamic Pricing、または YITH WooCommerce Dynamic Pricing & Discounts をインストールします
- WooCommerce → Dynamic Pricing → Add Rule に移動します
- 次の項目を設定します:
- Rule type:Product Pricing または Category Pricing
- Apply to:特定の商品、カテゴリー、またはすべての商品
- Pricing type:割引率、または固定価格への変更
- 数量別の価格帯を追加します:
最小数量 最大数量 割引 1 4 0% 5 9 10% 10 24 20% 25 — 30% - 必要に応じて User role で制限し、B2B/卸売専用の価格帯を設定します
- カート内の数量が変更されると、プラグインが価格を自動的に再計算します
商品ページに価格表を表示する: どちらの Dynamic Pricing プラグインにも、数量別の価格帯を商品ページに直接表示する価格表ウィジェットが含まれています。プラグイン設定で表示を調整してください。
BigCommerce
BigCommerce の Price Lists 機能は、Plus 以上のプランで数量別価格を標準で処理します。
数量別価格帯を設定した価格リストを作成する:
- Products → Price Lists → Create Price List に移動します
- リストに名前(例:「Wholesale 2026」)を付け、Customer Group に割り当てます
- 商品を追加して価格を設定します:
- 商品ごとに基本価格を上書きできます
- 数量別価格には、各商品の Bulk Pricing セクションを使用します
- 商品に移動し、Pricing タブ → Bulk Pricing を開きます:
- 価格帯を追加します。例:5 個以上の場合は 1 個あたり $X、10 個以上の場合は 1 個あたり $Y
- 一括価格をすべての顧客に適用するか、特定の価格リストに適用するかを設定します
- Customers → Customer Groups で、価格リストを顧客グループに割り当てます
カスタム/ヘッドレス
ヘッドレスのストアフロントでは、価格設定サービスに価格帯の解決処理を実装します:
interface PriceTier {
minQuantity: number;
maxQuantity?: number; // undefined = no upper limit
type: 'fixed' | 'percentage_off';
value: number; // cents if fixed; percentage (0-100) if percentage_off
customerGroup?: string; // null = all customers
}
async function resolveUnitPrice(
productId: string,
quantity: number,
customerGroup: string | null,
basePrice: number // cents
): Promise<{ unitPriceCents: number; savingsPct: number }> {
// 1. Check for customer-group-specific price list (highest priority)
if (customerGroup) {
const priceListItem = await db.priceLists
.findActive({ product_id: productId, customer_group: customerGroup, min_quantity: { lte: quantity } })
.orderBy('min_quantity', 'desc')
.first();
if (priceListItem) {
const savingsPct = Math.round((1 - priceListItem.price / basePrice) * 100);
return { unitPriceCents: priceListItem.price, savingsPct };
}
}
// 2. Find the best matching general volume tier
const tiers = await db.priceTiers.find({
product_id: productId,
customer_group: customerGroup ?? null,
});
const applicable = tiers.filter(t =>
quantity >= t.minQuantity && (t.maxQuantity === undefined || quantity <= t.maxQuantity)
).sort((a, b) => b.minQuantity - a.minQuantity); // highest-qualifying tier first
const tier = applicable[0];
if (!tier) return { unitPriceCents: basePrice, savingsPct: 0 };
const unitPriceCents = tier.type === 'fixed'
? tier.value
: Math.round(basePrice * (1 - tier.value / 100));
const savingsPct = Math.round((1 - unitPriceCents / basePrice) * 100);
return { unitPriceCents, savingsPct };
}
カート内の数量が変わるたびに再計算する:
async function updateCartLinePricing(cartId: string, lineId: string, newQuantity: number) {
const line = await db.cartLines.findById(lineId);
const { unitPriceCents } = await resolveUnitPrice(
line.productId, newQuantity, line.customerGroup, line.basePriceCents
);
await db.cartLines.update(lineId, {
quantity: newQuantity,
unitPriceCents,
lineTotalCents: unitPriceCents * newQuantity,
});
}
商品ページに価格表を表示する:
価格帯の境界値を取得して、表を表示します:
async function getPricingTable(productId: string, customerGroup: string | null, basePrice: number) {
const breakpoints = [1, 5, 10, 25, 50, 100];
const rows = await Promise.all(breakpoints.map(async qty => {
const { unitPriceCents, savingsPct } = await resolveUnitPrice(productId, qty, customerGroup, basePrice);
return { quantity: qty, unitPriceCents, savingsPct };
}));
// Only show rows where the price actually changes
return rows.filter((row, i) => i === 0 || row.unitPriceCents !== rows[i - 1].unitPriceCents);
}
ベストプラクティス
- 商品ページに価格表を表示する — 次の価格帯(「あと 3 個追加すると 10% オフ」など)を表示することは、平均注文額を高める効果が実証されています。この情報を隠さないでください
- カート内の数量が変わったら価格を再計算する — チェックアウト時だけでなく、カート内で単価がリアルタイムに更新されるようにします
- 価格リストと価格帯のロジックを分ける — B2B の価格リストによる上書きを数量別割引より優先し、明確な優先順位で解決します
- 卸売価格帯には最小注文数量を設定する — 卸売価格リストに最小注文数が必要な場合は、UI だけでなくチェックアウト時にも適用します
- チェックアウト時に最小数量を検証する — 一部の B2B 設定では最小数量が必要です。UI を回避した注文を防ぐため、サーバー側で適用します
- 割引額を目立つように表示する — 割引後の価格だけを表示するより、「$18.00(30%)お得です」と表示するほうが訴求力があります
よくある問題
| 問題 | 解決策 |
|---|---|
| カート内の数量が変わっても価格が更新されない | チェックアウト時だけでなく、カート内の数量が変わるたびに、すべての明細価格をリアルタイムで再計算します |
| B2B の購入者に注文確認メールで小売価格が表示される | 注文確認メールの生成を含むすべての価格解決呼び出しに customerGroup を渡します |
| 複数のグループに属する顧客に、一貫しない価格が表示される | 明確な優先順位で解決します:価格リスト > 商品の価格帯 > カテゴリーの価格帯 > 基本価格 |
| 価格帯を更新した後も、価格表に古い価格帯が表示される | 価格帯を変更したときに価格キャッシュを消去します。商品または価格帯のバージョン単位で無効化します |
| 検索結果ページに数量別価格が表示されない | 検索/コレクションページの商品カードでも、ログイン済みの B2B 顧客向け価格をサーバー側で解決する必要があります |
関連スキル
- @b2b-commerce
- @price-rules-engine
- @discount-engine
- @coupon-management
- @multi-channel-selling