Bubble × Stripe Connect で作る!予約決済プラットフォームの作り方【第4回】クーポンの罠と回避策
Bubble × Stripe Connect で作る!予約決済プラットフォームの作り方
第4回:クーポンの罠 – Stripe プロモーションコードが使えない問題と回避策
前回のおさらい
第3回では、予約決済の実装を解説しました。
- Destination Charges の仕組み
- application_fee_amount(手数料)の設定
- Checkout Session の作成
今回は、実装中に発覚した クーポンの罠 について解説します。
やりたかったこと
ユーザー向けの割引クーポンを実装したい。
通常: 3,000円
クーポン「WELCOME500」適用: 2,500円(500円引き)
Stripe には「プロモーションコード」という機能があります。
これを使えば簡単に実装できる…はずでした。
Stripe プロモーションコードとは
Stripe ダッシュボードで作成できる割引機能です。
作成方法
1. Stripe ダッシュボード → 商品 → クーポン
2. 「クーポンを追加」
3. 割引内容を設定(500円引き、10%オフなど)
4. プロモーションコードを発行(WELCOME500 など)
Checkout Session での使い方
allow_promotion_codes=true
これを Checkout Session に追加すると、決済画面に「プロモーションコードを入力」欄が表示されます。
簡単!これで終わり!
…のはずでした。
問題発生
テスト環境で動作確認。
1. 予約を作成
2. 決済画面に遷移
3. プロモーションコード「WELCOME500」を入力
4. 「適用」をクリック
結果: 何も起きない
エラーは出ない。でも割引されない。
コードを何度入力しても、金額が変わらない。
原因調査
Stripe のドキュメントを隅々まで読む。
Stack Overflow を漁る。
Stripe サポートに問い合わせる。
そして見つけた、小さな一文:
> Promotion codes are not supported for Checkout Sessions that use destination charges.
訳: Destination Charges を使う Checkout Session では、プロモーションコードはサポートされません。
なぜ使えないのか
Destination Charges の仕組み
ユーザー → Stripe → 店舗
↑
プラットフォームが手数料を取る
プロモーションコードで割引すると、誰がその割引分を負担するのかが曖昧になります。
通常: 3,000円
- 店舗: 2,850円
- プラットフォーム: 150円
500円割引後: 2,500円
- 店舗: ???円
- プラットフォーム: ???円
Stripe は「それは決められません」というスタンス。
だから、プロモーションコード自体を無視する仕様になっています。
回避策を探る
方法1: Direct Charges に変更
プロモーションコードを使いたいなら、Direct Charges に変更する方法があります。
でも:
- 決済の主体が店舗になる
- プラットフォームの手数料設定が複雑になる
- 返金処理が面倒になる
→ 却下
方法2: 割引済みの金額で Checkout Session を作成
Stripe のプロモーションコードを使わず、アプリ側で割引計算してから決済する。
→ これを採用
Bubble 側での実装
1. クーポンのデータベース設計
テーブル: クーポン
| フィールド | 型 | 説明 |
|---|---|---|
| code | text | クーポンコード |
| discount_amount | number | 割引額(円) |
| max_uses | number | 最大使用回数 |
| used_count | number | 使用済み回数 |
| is_active | yes/no | 有効フラグ |
| expires_at | date | 有効期限 |
テーブル: クーポン使用履歴
| フィールド | 型 | 説明 |
|---|---|---|
| coupon | クーポン | 使用したクーポン |
| user | User | 使用したユーザー |
| reservation | 予約 | 適用した予約 |
| used_at | date | 使用日時 |
2. UIの追加
予約確認画面に:
[クーポンコード入力欄] [適用ボタン]
[適用結果の表示]
✓ WELCOME500 適用(500円引き)
または
✗ このコードは無効です
3. クーポン検証のワークフロー
「適用」ボタンクリック時:
[検索]
クーポン where:
code = Input's value
is_active = yes
used_count < max_uses
expires_at > Current date/time
│
├─→ 見つかった
│ │
│ ↓
│ [Custom State に保存]
│ applied_coupon = Result
│ │
│ ↓
│ [表示を更新]
│ 「✓ 500円引き」
│
└─→ 見つからない
│
↓
[エラー表示]
「このコードは無効です」
4. 決済金額の計算
決済ボタンクリック時:
[計算]
基本金額 = メニュー料金 × 卓数 × 時間
[条件分岐]
applied_coupon is not empty:
決済金額 = 基本金額 - applied_coupon's discount_amount
else:
決済金額 = 基本金額
[計算]
手数料 = 決済金額 × 0.05 :rounded down
重要: 手数料は割引後の金額に対して計算
基本金額: 3,000円
割引: 500円
決済金額: 2,500円
手数料(5%): 125円 ← 2,500円に対して計算
5. クーポン使用履歴の記録
決済完了後(Webhook または success_url):
[Create クーポン使用履歴]
coupon = applied_coupon
user = Current User
reservation = この予約
[Make changes to クーポン]
used_count = used_count + 1
実装のポイント
1. 同一ユーザーの重複使用を防ぐ
「初回限定」クーポンの場合:
[検索時の追加条件]
クーポン使用履歴 where user = Current User :count is 0
2. マイナス金額を防ぐ
割引額が決済金額を超える場合:
決済金額 < 0 の場合 → 0 にする
または、そもそも適用できないようにする:
割引額 > 基本金額 → 「この予約には適用できません」
3. Custom State の活用
Bubble では、ページ全体で使える「Custom State」に適用済みクーポンを保存すると便利です。
Page の Custom State:
applied_coupon (type: クーポン)
discount_amount (type: number)
最終的なお金の流れ
基本金額: 3,000円
クーポン: -500円
決済金額: 2,500円
Stripe が受け取る
│
├─→ プラットフォーム手数料: 125円(5%)
│
└─→ 店舗への送金: 2,375円
│
└─→ Stripe手数料: 約90円
└─→ 店舗の受取額: 約2,285円
学んだこと
1. Stripe の機能には制約がある
- 組み合わせによっては使えない機能がある
- ドキュメントの隅々まで確認が必要
2. 回避策は存在する
- Stripe 側でできないなら、アプリ側で実装
- 結果的に柔軟な設計になることも
3. テストは実際の流れで
- 単体テストだけでなく、エンドツーエンドのテストが重要
- 「動くはず」ではなく「動いた」を確認
次回予告
第5回では、サブスク課金 を解説します。
- 店舗向け有料掲載プラン(年額)
- 広告プラン(月額)
- Stripe Subscription の設定
お楽しみに!
このシリーズの目次
| 回 | タイトル | 内容 |
|---|---|---|
| 1 | イントロ | プラットフォーム決済の設計 |
| 2 | Stripe Connect設計 | Express アカウント、オンボーディング |
| 3 | 予約決済の実装 | Destination Charges、手数料計算 |
| 4 | クーポンの罠 | ← 今ここ |
| 5 | サブスク課金 | 有料掲載・広告プランの実装 |
| 6 | Webhook設計 | 決済完了の検知、ステータス更新 |
| 7 | 本番デプロイ | テスト→本番の切り替え、審査対策 |
いいね・フォローしていただけると励みになります!
実装のご相談
予約システム・サブスク決済の導入でお困りの方へ。
・Stripe / Stripe Connect の設計・実装
・Bubble / Make.com でのノーコード開発
・テスト環境→本番デプロイまで一貫対応
初期設定から運用まで、初心者の方にも丁寧にサポートします。