クーポンコンソール
注目すべき点
クーポンコンソールは v4.0.0 で追加され、入口は
/dashboard/admin/couponsです。provider: 'stripe'のプランにのみ有効です。
前提:Stripe が唯一のストア
ローカルにテーブルもマイグレーションもなく、クーポンのデータを一切保存しません。 ページ上の操作はすべて直接 Stripe を読み書きします。
理由は単純です。コードが何回使われたか、期限切れかどうか、無効化されているか —— 情報源は常に Stripe 側にあります。ローカルにコピーを持っても同期問題が増えるだけで、得るものはありません。クーポンの読み書き頻度はキャッシュを必要とするほど高くないからです。
代償として、一覧機能は Stripe API の表現力に制限されます。後述の「一覧のスキャン上限」を参照してください。
データモデル:1 つのクーポンは 2 つのオブジェクト
Stripe はクーポンを 2 層に分けており、ボイラープレートもその意味論に従います。
Coupon(割引そのもの)
├── 割引:percent_off または amount_off(排他)
├── 適用範囲:applies_to.products(空 = 全製品)
└── 課金期間:duration = once | repeating | forever
repeating の場合は duration_in_months も必要
Promotion Code(顧客が入力するコード)
├── code:決済画面で入力する文字列(例:LAUNCH20)
├── max_redemptions:利用回数の上限
├── expires_at:有効期限
└── restrictions:first_time_transaction(新規顧客のみ)/ minimum_amount(最低注文額)2 つのオブジェクトは一度にまとめて作成されます。2 段目(promotion code)が失敗した場合 —— よくある原因はコードの重複です —— 直前に作った coupon を削除するため、却下されたフォームが孤児オブジェクトを残すことはありません。
最も重要な制約:コードは作成後に変更できない
Stripe の promotion code は、いったん作成されると有効/無効のスイッチ以外は何も変更できません。過去の請求書が参照しているため、Stripe 側でも本当に削除されることはありません。
この制約が UI 全体を形づくっています。
| やりたいこと | 実際にできること |
|---|---|
| 割引率を変える | ❌ 旧コードを無効化し、複製・編集して新しいコードを作る |
| 適用プランを変える | ❌ 同上 |
| 有効期限を変える | ❌ 同上 |
| 利用回数上限を変える | ❌ 同上 |
| 配布を止める | ✅ 有効スイッチをオフにする |
| 削除する | ❌ Stripe が非対応。無効化が「削除」に相当 |
したがって行の操作は 2 つだけです。有効スイッチと複製です。「複製」は現在のコードの全パラメータを作成フォームへ事前入力するので、編集して送信すれば新しいコードになります。

ステータス
運用者から見た 4 つの状態:
| ステータス | 意味 | 再有効化の可否 |
|---|---|---|
active | 通常どおり利用可能 | — |
disabled | 手動で無効化 | ✅ 再度オンにできる |
expired | expires_at を過ぎた | ❌ 終端 |
exhausted | 利用回数を使い切った | ❌ 終端 |
expired と exhausted を 1 つの「inactive」にまとめず別の値にしているのは、まさに Stripe がこれらの再有効化を拒否するからです。行のトグルはステータスをそのまま読んで、自身を無効化するかどうかを決めます。
クーポンを作成する

フォームの項目:
| 項目 | 説明 |
|---|---|
| コード | 空欄なら Stripe が生成します。手入力する場合は ^[A-Z0-9_-]{3,30}$ に一致する必要があり、自動的に大文字化されます |
| 名称 | クーポンの表示名。Stripe ダッシュボードと請求書に表示されます。最大 40 文字 |
| 割引種別 | パーセント(0 < x ≤ 100)または固定額(整数セント)。排他 |
| 適用プラン | 複数選択、最大 20 件。空欄 = 全製品 |
| 課金期間 | once(初回のみ)/ repeating(最初の N か月、1〜36)/ forever(毎回の請求) |
| 利用回数上限 | 空欄 = 無制限 |
| 有効期限 | 「なし / 7 日 / 30 日 / 90 日」のプリセット、または任意指定。未来の日時である必要があります |
| 新規顧客のみ | 一度も支払ったことがない顧客に限定 |
| 最低注文額 | 整数セント。空欄 = 下限なし |
インタラクションで特筆すべき点が 2 つあります。
リアルタイムの重複チェック。 コードを入力すると checkPromotionCodeAvailability が Stripe に使用済みかを問い合わせるため、重複は作成失敗としてではなく送信前に判明します。
効果サマリー文。 ダイアログ下部に「顧客が最終的に何を得るのか」を平易な文章で表示します。たとえば「最初の 3 か月間、毎回 $10 引き。Pro 月額のみ。新規顧客限定」といった具合です。割引・範囲・期間の組み合わせは誤りやすく、この一文がその防止策です。
適用範囲が Stripe へ届くまで
フォームで選ぶのはプランの slug で、送信時に Stripe の product id へ変換されます。
planId(config/pricing.ts)
↓ getPlanById + isStripePlan の検証
Stripe Price ID
↓ 逆引き
Stripe Product ID
↓ 書き込み
coupon.applies_to.products選択したプランが Stripe 経由でない場合(provider が stripe でない場合)、リクエストは拒否され、該当プラン名が示されます。
テスト/本番モードに注意
product id はテストモードと本番モードで異なります。テストモードで作成したクーポンはテストモードの製品にしか適用されないため、本番公開前に同じコードを本番モードで作り直す必要があります。
自動適用
config/pricing.ts にプロモーションコードを設定しておくと、チェックアウト時に自動適用され、顧客は何も入力しません。
// サイト全体のキャンペーン
export const pricingCampaign: PricingCampaign = {
promotionCode: 'LAUNCH20',
}
// またはプラン単位(こちらが優先)
{ id: 'pro-monthly', promotionCode: 'PRO30', ... }自動適用されるコードには一覧でバッジが付きます。このバッジは重要です。この種のコードはすべての訪問者に対して有効なので、無効化するとサイト全体の割引が音もなく消えます。 顧客にはエラーが一切表示されず、価格が変わったことだけが見えます。無効化する前に、config/pricing.ts がそのコードを参照していないか確認してください。
逆に、設定したコードが現在のモードに存在しない、または無効化されている場合、チェックアウトは失敗しません。プロバイダー標準の手入力欄へフォールバックします。古いキャンペーン設定が売上を止めることはありません。
詳細は料金設定のプロモーションコードの節を参照してください。
一覧のスキャン上限
Stripe の promotion code 一覧エンドポイントはカーソルページングのみで、テキスト検索がありません。運用者に「コード検索 + ステータス絞り込み + プラン絞り込み」を提供するには、全件を一度取得してメモリ上で絞り込むしかありません。
そのため上限があります。
const MAX_SCANNED_CODES = 5001 回のスキャンで最大 500 件(1 ページ 100 件で自動ページング)。結果には追加で 2 つのフィールドが含まれます。
| フィールド | 用途 |
|---|---|
scanned | 絞り込み前に Stripe から取得した件数。「0 / 42」の分母になります |
truncated | 500 件の上限に達したか。達した場合は古いコードが切り捨てられています |
truncated が真の場合、ページにスキャン上限の警告が表示されます。Stripe は新しい順に返し、それはコードを作成した直後に運用者が期待する順序でもあるため、並べ替えずそのまま保持しています。
コード数が恒常的に 500 を超えるようなら、戦略を見直す頃合いです —— 共通のプレフィックスを付けて Stripe ダッシュボードで管理する、あるいは過去のコードを定期的に整理する、といった方法があります。
サーバー側の API
actions/coupons/admin.ts の 4 つのアクション。すべて管理者チェック付きです。
| アクション | 役割 |
|---|---|
getAdminPromotionCodes({ pageIndex, pageSize, search?, status?, appliesTo? }) | 一覧:1 回のスキャンとメモリ上での絞り込み・ページング |
checkPromotionCodeAvailability(code) | 送信前の重複チェック |
createAdminPromotionCode(input) | coupon と promotion code を作成。promo 側が失敗したら coupon をロールバック |
setPromotionCodeActive({ id, active }) | Stripe が許す唯一の変更操作 |
Stripe が未設定の場合、すべてのアクションはエラーを返し、ページも利用不可の状態を表示します。
よくある質問
Q: Creem と PayPal のクーポンはどうなりますか?
このコンソールは Stripe のみを対象とします。Creem は独自の discountCode を使い(config/pricing.ts に設定したプロモーションコードがチェックアウト時に discountCode として Creem へ渡されます)、PayPal はプロモーションコードの自動適用に対応していません。
Stripe のプロモーションコードは Stripe の product にしか限定できないことが、このコンソールが Stripe 専用である根本的な理由です。
Q: なぜクーポンを編集できないのですか?
これはボイラープレートの選択ではなく Stripe の制約です。promotion code は過去の請求書から参照されており、編集を許すと過去の請求記録が信頼できなくなります。
Q: 無効化したコードはまだ使えますか?
新規の利用はできません。ただし、既にそれを使ったサブスクリプションは、duration が repeating または forever であれば元の条件どおり割引が続きます —— 無効化が止めるのは新規の利用だけです。
Q: あるコードが何回使われたかはどこで見られますか?
一覧に利用進捗(利用済み / 上限)が表示されます。さらに詳しく見たい場合は、行内の Stripe ダッシュボードへのディープリンクから、対応するモードのページへ直接移動できます。