> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waffo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 割引とプロモーション

> クーポンとプロモーションコードを作成し、購入者がチェックアウトで使えるようにする

## できること

* **クーポン**を作成：パーセンテージまたは固定額の割引に、対象商品、合計使用回数、有効期限を設定。購入者がチェックアウトで入力するコードを持ちます
* 同じクーポンに**プロモーションコード**を追加発行し、コードごとに独自の購入者制限を設定（対象メールアドレス、購入者ごとの回数、最低金額）—— インフルエンサーやキャンペーンチャネルごとのコード、使い切りコードの一括発行など
* 購入者がチェックアウトページでコードを入力する、コード適用済みの商品リンクを共有する、またはチェックアウトセッション作成時にサーバーからクーポンを適用したりプロモーションコードを事前入力したりする

> [!NOTE]
>
> 割引は**単発商品、新規サブスクリプション、プラン変更**に適用されます。サブスクリプションでは、クーポンの適用期間によって割引される請求期間の数が決まります —— [サブスクリプション](#subscriptions)を参照してください。プラン変更では、新しいコードがサブスクリプションの割引を置き換え、コードがなければ割引は継続できる限り引き継がれます —— [プラン変更](#plan-changes)を参照してください。

***

## クーポンとプロモーションコード

**クーポン**は割引の内容を定義し、購入者が入力できる自身のコードを持ちます（作成時に指定するか、Pancake に生成させます）。クーポンの制限は有効期限と合計使用回数のみなので、クーポン自身のコードはその範囲内でどの購入者も使用できます。**プロモーションコード**は同じクーポンの別のコードで、購入者の制限を持ちます。1 つのクーポンに複数のプロモーションコードを持たせられ、購入者はプロモーションコードの制限に加えて、クーポンの有効期限と合計使用回数も満たす必要があります。プロモーションコードはクーポンと同じリクエストで一緒に作成することも、後から追加することもできます。サーバーから `couponId` で適用するクーポンにはコードは不要です。

| ルール | 設定先 | 説明 |
| - | - | - |
| 割引 | クーポン | パーセンテージ、または通貨ごとの固定額。固定額に含まれない通貨の注文には使用できません。為替換算は行いません |
| 対象商品 | クーポン | 一部の商品に限定、またはストア全体 |
| 有効期限 | クーポン、プロモーションコード | 期限を過ぎるとコードは使用できません。プロモーションコードの期限はクーポンより後にできません |
| 合計使用回数 | クーポン、プロモーションコード | 全購入者の合計使用回数。クーポンの上限はクーポン自身のコードとすべてのプロモーションコードを合計して数えます。プロモーションコードの上限はクーポンを超えられません |
| 対象購入者 | プロモーションコード | 指定したメールアドレスの購入者のみ（大文字・小文字を区別しない） |
| 購入者ごとの使用回数 | プロモーションコード | 購入者ごとのコードの累計使用回数 |
| 最低金額 | プロモーションコード | 通貨ごとの最低金額。割引前・税抜の金額で判定します。指定のない通貨には最低金額がありません |

クーポンとそのコードは 1 つのストア、1 つの環境に属します。テストモードで作成したコードはライブモードでは使えません。コードは大文字・小文字を区別しません —— `spring-20` と入力しても `SPRING-20` として扱われます。

***

## クーポンの作成

### Dashboard 経由

**Dashboard → クーポン**で、クーポンの作成、プロモーションコードの追加、コードの一括生成、各コードの使用回数の確認ができます。Dashboard ガイドの[クーポン](https://docs.waffo.ai/ja/dashboard/discounts.md)を参照してください。

### API または SDK 経由

サーバーから[クーポン](https://docs.waffo.ai/ja/api-reference/endpoints/coupons/overview.md)と[プロモーションコード](https://docs.waffo.ai/ja/api-reference/endpoints/promotion-codes/overview.md)のエンドポイントで作成します：

```typescript TypeScript theme={"system"}
import { CouponDiscountType, WaffoPancake } from "@waffo/pancake-ts";

const client = new WaffoPancake({
  merchantId: process.env.WAFFO_MERCHANT_ID!,
  privateKey: process.env.WAFFO_PRIVATE_KEY!,
});

const { coupon } = await client.coupons.create(
  {
    storeId: "STO_xxx",
    name: "Spring sale",
    discount: { type: CouponDiscountType.Percentage, percentOff: 20 },
    maxRedemptions: 500,
    code: "SPRING-20", // the coupon's own code; omit it for a random one
    // Promotion codes created together with the coupon (up to 20), each with its own restrictions
    promotionCodes: [{ code: "SPRING-NEW", restrictions: { perCustomerLimit: 1 } }],
  },
  { idempotencyKey: "coupon-spring-2026" },
);

// A code for one influencer: 100 uses, at least $50 before tax
await client.promotionCodes.create({
  couponId: coupon.id,
  code: "KOL-ALICE",
  restrictions: { maxRedemptions: 100, minimumAmount: { USD: "50.00" } },
});
```

```go Go theme={"system"}
res, err := client.Coupons.Create(ctx, pancake.CreateCouponParams{
    StoreID:        "STO_xxx",
    Name:           "Spring sale",
    Discount:       pancake.CouponDiscount{Type: pancake.CouponDiscountTypePercentage, PercentOff: pancake.Ptr(20.0)},
    MaxRedemptions: pancake.Ptr(500),
    Code:           pancake.Ptr("SPRING-20"),
    PromotionCodes: []pancake.CreateCouponPromotionCodeParams{{
        Code:         pancake.Ptr("SPRING-NEW"),
        Restrictions: &pancake.PromotionCodeRestrictions{PerCustomerLimit: pancake.Ptr(1)},
    }},
}, pancake.WithIdempotencyKey("coupon-spring-2026"))
```

## よくあるキャンペーン

マーチャントがよく行うプロモーションごとの設定方法です。どれも 1 つのクーポンで、購入者ごとにルールを変えたい場合はプロモーションコードを追加します。[Dashboard](https://docs.waffo.ai/ja/dashboard/discounts.md) でも SDK でも設定できます。

> **新商品発売：期間限定の公開コード**
>
> 誰でも使える 20% オフのコードを 2 週間、上限付きで配布します。
>
> * **設定**：クーポンを 1 つ。パーセント 20、有効期限は 2 週間後、利用上限（たとえば 500）、対象は全商品または発売商品のみ。クーポン自身のコード（例：`LAUNCH-20`）が公開コードになります。
> * **購入者の使い方**：チェックアウトページでコードを入力します。クーポンを作成した時点で商品リンクにコード入力欄が表示されます。広告やメールではコードをリンクに組み込めば入力不要です：`…/product/{productId}?discount_code=LAUNCH-20`
> * **注意**：有効期限は入力時と注文時の両方で確認されるため、期限前にページを開いて期限後に支払うと通常価格になります。クーポンの期限切れは、適用済みのサブスクリプションの割引も終了させます。
>
> ```typescript theme={"system"}
> await client.coupons.create({
>   storeId: "STO_xxx",
>   name: "Launch sale",
>   code: "LAUNCH-20",
>   discount: { type: CouponDiscountType.Percentage, percentOff: 20 },
>   maxRedemptions: 500,
>   redeemBy: "2026-11-15T00:00:00.000Z",
> });
> ```

> **インフルエンサーとチャネル：パートナーごとに 1 コード**
>
> 割引は同じでも、どのパートナー経由の注文かを把握し、それぞれ別々に上限を設けたい場合です。
>
> * **設定**：割引を定義するクーポンを 1 つ作り、その上にパートナーごとのプロモーションコードを追加します：`KOL-ALICE`、`KOL-BOB`、`PODCAST-10`。それぞれに総利用回数を設定し、必要なら最低注文金額も設定します。クーポン自身のコードは公開しなくて構いません。
> * **購入者の使い方**：パートナーがコード、または `?discount_code=KOL-ALICE` 付きの商品リンクを共有します。
> * **確認**：クーポンの詳細でコードごとの利用回数を確認でき、CSV でエクスポートしてパートナーへの報告に使えます。契約終了時はそのパートナーのコードだけを無効化すれば、他のコードはそのまま使えます。
>
> ```typescript theme={"system"}
> await client.promotionCodes.create({
>   couponId: "CPN_xxx",
>   code: "KOL-ALICE",
>   restrictions: { maxRedemptions: 200, minimumAmount: { USD: "30.00" } },
> });
> ```

> **ニュースレターやプレゼント：1 人に 1 つの使い切りコード**
>
> 受信者ごとに 1 回だけ使えるコードを配り、コードの共有を防ぎます。
>
> * **設定**：クーポンを 1 つ作り、その上で**一括生成**します。必要な数（1 回の生成で最大 500）、`NEWS-` などのプレフィックス、「各コードは最大 1 回まで」。CSV をダウンロードしてメールツールに差し込みます。
> * **購入者の使い方**：各メールに 1 つのコードが入り、購入者がチェックアウトで入力します。
> * **注意**：コードは支払い済みの注文で使い切られます。未払いの注文は 72 時間使用を保持し、その後コードは再び使えるようになります。一括生成のコードはランダムなので、誰がどのコードを使ったかは CSV と注文のメールアドレスを突き合わせるしかありません。
>
> ```typescript theme={"system"}
> const { promotionCodes } = await client.promotionCodes.generate({
>   couponId: "CPN_xxx",
>   count: 500,
>   prefix: "NEWS-",
>   restrictions: { maxRedemptions: 1 },
> });
> ```

> **復帰促進や VIP リスト：特定の購入者だけ**
>
> コードが漏れても、リストにある購入者しか使えない割引です。
>
> * **設定**：プロモーションコードに「特定の購入者のみ利用可能」を設定してメールアドレスを貼り付け、「各購入者は最大 1 回まで」を設定します。
> * **購入者の使い方**：チェックアウトでコードを入力します。コードはチェックアウトページに入力されたメールアドレスと照合されるため、メールアドレス入力前に適用すると、ページが先にメールアドレスを求めます。リストにない購入者には、特定の購入者向けのコードであることが表示されます。
> * **注意**：メールアドレスの完全一致（大文字小文字は区別しません）で照合します。別のメールアドレスで決済する購入者は拒否されるので、どのアドレスに送ったかを伝えてください。
>
> ```typescript theme={"system"}
> await client.promotionCodes.create({
>   couponId: "CPN_xxx",
>   code: "COMEBACK-30",
>   restrictions: { customerEmails: ["jane.doe@example.com"], perCustomerLimit: 1 },
> });
> ```

> **会員ランクやパートナー価格：自分で決め、コードは見せない**
>
> 誰が割引を受けるかは自社システムが判断し（会員ランク、リセラー、合意した見積もり）、購入者にコードを見せたり共有させたりしたくない場合です。
>
> * **設定**：ランクごとにクーポンを 1 つ（`Gold 15%`、`Partner 25%`）。コードを公開する必要はありません。
> * **購入者の使い方**：サーバーが `couponId` を指定してチェックアウトセッションを作成し、その `checkoutUrl` に購入者を送ります。チェックアウトページには割引と割引後の合計が表示され、コードもコード入力欄も表示されず、購入者は割引を外せません。
> * **注意**：`couponId` には API Key が必要なので、対象かどうかはサーバーコードで判断し、ブラウザから送られた値は使わないでください。Next.js SDK の `createCheckoutAction` は `couponId` を取り除くため、サーバーコードから `@waffo/pancake-ts` を呼びます。購入者が自分のコードを重ねられるようにするには、代わりにプロモーションコードを `promotionCode` と `allowPromotionCodes: true` で渡します。クーポンはコード入力と併用できません。
>
> ```typescript theme={"system"}
> // サーバーコード。購入者のランクを確認してから
> const session = await client.checkout.createSession({
>   storeId: "STO_xxx",
>   productId: "PROD_xxx",
>   currency: "USD",
>   couponId: tier === "gold" ? "CPN_gold" : undefined,
>   buyerEmail: customer.email,
> });
> redirect(session.checkoutUrl);
> ```

> **サブスクリプション：最初の数か月を割引、または年額への移行促進**
>
> 月額プランで「3 か月間 25% オフ」、または年額プランでのみ使えるコードで購入者を年額へ誘導します。
>
> * **設定**：最初の数か月の割引には、期間「複数の請求サイクル」= 3 のクーポン。年額への誘導には年額商品限定のクーポンを使うと、月額プランではコードが `PRODUCT_NOT_ELIGIBLE` で拒否されます。
> * **購入者の使い方**：新規購入者はチェックアウトでコードを入力します。既存の月額購入者はカスタマーポータルからプランを変更します。年額プランで使えるコードがあればプラン変更ページにコード入力欄が表示されます。または `promotionCode` を指定したプラン変更セッションを自分で作成し、そのリンクを送ることもできます。
> * **注意**：無料トライアルは割引を消費せず、最初の有料期間から割引が始まります。有料トライアルは割引を消費し、最初の期間として数えられます。プラン変更時、新しいコードは現在の割引を置き換えます。コードがなければ現在の割引が使用済みの期間を引き継いで継続します。[サブスクリプション](#subscriptions)と[プラン変更](#plan-changes)を参照してください。
>
> ```typescript theme={"system"}
> await client.coupons.create({
>   storeId: "STO_xxx",
>   name: "Annual upgrade",
>   code: "GO-ANNUAL",
>   discount: { type: CouponDiscountType.Percentage, percentOff: 25 },
>   duration: CouponDuration.Repeating,
>   durationInPeriods: 3,
>   productIds: ["PROD_annual"],
> });
> ```

> **まずテスト、それから本番へ**
>
> クーポンは 1 つの環境にのみ属するため、テストモードで作成したクーポンは本番モードでは見えません。
>
> * **設定**：テストモードでクーポンとコードを作成し、テストカードでチェックアウトを試します。
> * **本番へ**：クーポンを開いて**本番環境へコピー**を選びます（商品を先に本番へ公開しておく必要があります）。コピーは利用回数 0 から始まり、オプションを選べば有効かつ期限内のプロモーションコードも一緒にコピーされます。API の場合は本番の API Key でクーポンを再作成します。
> * **注意**：本番ですでに使われているコードがあるとコピーは中止されます。先に本番でそのコードを無効化するか、コードなしでコピーしてください。

***

## チェックアウトでの割引の適用

割引は次の 4 つの方法のいずれかでチェックアウトページに届きます。購入者は支払い前に、割引後の税込合計を必ず確認できます。

| 方法 | やり方 | 購入者が変更できるか |
| - | - | - |
| コード入力欄 | 購入者がチェックアウトページでクーポンコードまたはプロモーションコードを入力 | できる —— 変更・削除が可能 |
| コード付き商品リンク | 商品リンクに `discount_code` を付ける | できない —— コードは適用済みでロックされる |
| サーバーで指定したクーポン | マーチャントの認証情報（API Key または JWT）でチェックアウトセッションを作成する際に `couponId` を渡す | できない —— 割引は適用済みでロックされる |
| サーバーで事前入力したプロモーションコード | マーチャントの認証情報（API Key または JWT）でチェックアウトセッションを作成する際に `promotionCode` を渡す | `allowPromotionCodes: true` も渡した場合のみ —— 変更・削除が可能 |

### コード入力欄

商品リンクとストアページでは、ストアにその商品で使えるコードがある場合にコード入力欄が表示されます。商品が対象で、期限切れでなく、合計使用回数の上限に達していない `active` なクーポンがあり、そのクーポン自身のコードまたはプロモーションコードが `active` で、期限切れでなく、自身の使用回数の上限に達していないことが条件です。対象購入者や最低金額など購入者に依存するルールは、コード入力時にのみ検証されます。期限切れや上限到達の直後のコードでは、最大 5 分間コード入力欄が表示されたままになることがあります。API Key で作成したチェックアウトセッションも、`allowPromotionCodes` を省略した場合は同じルールで表示されます。`true` を渡すと常に表示し、`false` を渡すと非表示になります：

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "PROD_xxx",
  currency: "USD",
  allowPromotionCodes: true,
});
```

#### よくあるシナリオ

よくある状況でチェックアウトページがどう判断し、何を渡せばよいか：

| シナリオ | コード入力欄 | 渡すもの |
| - | - | - |
| Dashboard で全商品対象のクーポンを作成し、購入者が商品リンクまたはストアページを開く | 表示 | 不要。ページがクーポンを検出します |
| クーポンが商品 A 限定で、購入者が商品 B を開く | B では非表示、A では表示 | 不要 |
| ストアで唯一のクーポンが期限切れ・使い切り・無効化されている | 非表示（最大 5 分以内に反映） | 不要。再び表示するには新しいクーポンを作成します |
| クーポンの唯一のコードが特定の購入者メールに限定されている | 表示。購入者ルールは判定に含まれません。他の購入者のコードは `CUSTOMER_NOT_ELIGIBLE` で拒否され、メールアドレス入力前に適用するとページが先にメールアドレスを求めます | 不要 |
| 唯一のクーポンが USD の固定額で、購入者が EUR で決済する | 表示。通貨は判定に含まれません。コードは `CURRENCY_MISMATCH` で拒否されます | EUR の金額を持つ新しいクーポンを作成するか、そのままにします |
| API Key でサーバーからセッションを作成する | 非表示。ストアにコードがあっても表示されません | 表示するには `allowPromotionCodes: true` |
| サーバーからセッションを作成し、コードを事前入力したい | コードは適用済み・ロック状態で表示 | `promotionCode: "KOL-ALICE"`。`allowPromotionCodes: true` を加えると購入者が変更・削除できます |
| 割引を自分で決め（会員ランク、パートナー価格）、コードを見せたくない | 非表示。割引は適用済み・ロック状態で、コードは表示されません | `couponId: "CPN_xxx"`。`promotionCode` や `allowPromotionCodes` とは併用できません |
| コードを組み込んだキャンペーンリンクを送る | 非表示。コードは適用済み・ロック状態。使えないコードは理由が表示され、**割引なしで続行**が提示されます | 商品リンクに `?discount_code=SPRING-20` |
| 自前のストアフロントが Store Slug（マーチャント認証なし）でセッションを作成する | 商品リンクと同じデフォルト判定 | `allowPromotionCodes: true` または `false` で判定を上書き |
| 購入者がカスタマーポータルからプランを変更する | 新プランの商品で使えるコードがあれば表示。コードなしの場合、現在の割引は継続できる限り引き継がれます | 不要。または自分で作成するプラン変更セッションに `couponId` / `promotionCode` |
| Next.js SDK の `createCheckoutAction` を使う | ボタンが `allowPromotionCodes` を渡さない限り非表示 | `<CheckoutButton>` に `allowPromotionCodes`。`couponId` と `promotionCode` はそこで取り除かれるため、サーバーコードで `@waffo/pancake-ts` を使って設定します |

### コード付き商品リンク

[商品リンク](https://docs.waffo.ai/ja/checkout/checkout-flow.md#product-purchase-links)に `discount_code` を付けます。ページを開いた時点でコードが適用され、コード入力欄は表示されません：

```
https://pancake.waffo.ai/store/{store-slug}/product/{productId}?currency=USD&discount_code=SPRING-20
```

コードが使えない場合（期限切れ、商品が対象外など）は、チェックアウトページが購入者に理由を表示します。

### サーバーで指定したクーポン

チェックアウトセッション作成時に `couponId`（クーポンの `CPN_` ID）を渡します。購入者には割引の内容（名前、金額、期間）が表示されますが、クーポンに自身のコードがあってもコードは表示されません。コードを表示したい場合は、代わりに `promotionCode` で事前入力します。クーポンはその場で検証され、別のストアや環境のクーポン、無効化済み・期限切れのクーポン、商品が対象外のクーポン、セッション通貨の金額がない固定額クーポンは `409` で拒否されます —— [チェックアウトセッションの作成](https://docs.waffo.ai/ja/api-reference/endpoints/orders/create-checkout-session.md#discounts)を参照してください。

```typescript theme={"system"}
const session = await client.checkout.authenticated.create({
  productId: "PROD_xxx",
  currency: "USD",
  buyerIdentity: "userIdInYourSystem",
  couponId: "CPN_xxx",
});
```

`couponId` は `allowPromotionCodes` や `promotionCode` と同時に指定できません。両方を送ると `400` が返ります。`couponId` はマーチャントの認証情報（API Key または JWT）でのみ受け付けます。Store Slug のリクエストや customer のセッショントークンは `403` になり、[Next.js SDK](https://docs.waffo.ai/ja/integrate/nextjs.md#discounts) の server action も `couponId` を取り除くため、購入者が自分でクーポンを選ぶことはできません。

### サーバーで事前入力したプロモーションコード

`promotionCode` —— クーポンコードまたはプロモーションコードの文字列。大文字小文字を区別しません —— を渡すと、チェックアウトページを開いた時点でそのコードが適用されます。`couponId` と同じルールでその場で検証され、使えないコードは `409` で拒否されます。購入者が変更できるかは `allowPromotionCodes` で決まります：

| `allowPromotionCodes` | チェックアウトページ |
| - | - |
| `true` | コードは適用済みで、購入者は別のコードに変更するか削除できます。削除後は再びコードを入力できます |
| 省略または `false` | コードは適用済みでロックされます |

```typescript theme={"system"}
const session = await client.checkout.createSession({
  productId: "PROD_xxx",
  currency: "USD",
  promotionCode: "KOL-ALICE",
  allowPromotionCodes: true, // let the customer replace or remove it
});
```

`couponId` と同じく、`promotionCode` はマーチャントの認証情報（API Key または JWT）でのみ受け付けます —— Store Slug のリクエストや customer のセッショントークンは `403` になり、Next.js SDK の server action も取り除きます。

***

## コードが使えない場合

Pancake は購入者がコードを入力したときに確認し、最終的には注文確定時の判定に従います。入力時の確認は事前チェックのため、次のルールは注文時に判定されます：

* ゲストのチェックアウトでは、入力時の合計使用回数には支払い済みの注文だけを数え、支払い済みの回数が上限に達したときにだけ拒否します。未払いの注文と購入者ごとの回数は注文時に判定します。残りの枠を他の購入者の未払い注文が使っている場合は、注文時に拒否されます。
* ログイン済みの購入者が同じコードで作成した自分の未払い注文は、使用回数に数えません。ただし、まだ使用枠を確保している未払いのプラン変更は除きます —— 確定済みで切り替え日を待っている変更、または現在の割引が引き継がれる先の変更です。
* 購入者が支払いページを離れて未払いのまま戻ってきても、同じコードをもう一度入力できます。新しい注文を確定すると、未払い注文が使っていた枠を新しい注文が引き継ぎます。

チェックアウトページには購入者の言語で理由が表示されます：

| 理由 | 意味 |
| - | - |
| `CODE_NOT_FOUND` | このストア・環境に有効なそのコードはありません。無効化済みのコードや別のストアのコードも同じ理由になります |
| `CODE_INACTIVE` | クーポンまたはプロモーションコードが利用できなくなっています —— 例えば、購入者が適用した後に無効化された場合 |
| `CODE_EXPIRED` | コードの期限が切れています。期限まで 60 秒未満の場合も期限切れとして扱います —— チェックアウトセッションの作成時、コード入力時、注文確定時のいずれも同じです |
| `MAX_REACHED` | コードの合計使用回数に達しました |
| `USER_LIMIT_REACHED` | 購入者の使用回数が上限に達しました |
| `CUSTOMER_NOT_ELIGIBLE` | 購入者のメールアドレスがコードの対象リストにありません。特定の購入者に限定したコードはメールアドレスでしか確認できないため、メールアドレスを入力する前に適用すると、チェックアウトページは先にメールアドレスの入力を求めます |
| `PRODUCT_NOT_ELIGIBLE` | コードはこの商品に使えません |
| `CURRENCY_MISMATCH` | 固定額にチェックアウト通貨の金額がありません |
| `MINIMUM_NOT_MET` | 注文がコードの最低金額（割引前・税抜）に達していません。最低金額が表示されます |
| `TEMPORARILY_UNAVAILABLE` | 予約値で、現在は返されません。購入者がコードを短時間に何度も入力した場合は HTTP `429` が返され、しばらくすると再試行できます |

注文確定の時点で初めてルールを満たさなくなった場合（直前に別の購入者が最後の 1 回を使ったなど）、注文は作成されず、請求も発生せず、チェックアウトセッションは引き続き使用できます。購入者が次にできることはコードの適用方法によって異なります：

| 適用方法 | 購入者ができること |
| - | - |
| コード入力欄 | コードを削除して通常価格で支払うか、別のコードを試す |
| コード付きの商品リンク | コードはロックされています：`discount_code` を付けない商品リンクを開き直します。リンクのコードがページを開いた時点で使えない場合、そのページに「割引なしで続ける」が表示されます |
| サーバーで指定したクーポン | クーポンはロックされています：`couponId` を付けずにチェックアウトセッションを作成し直します |
| サーバーで事前入力したプロモーションコード | `allowPromotionCodes: true` を付けた場合はコード入力欄と同じです。そうでなければコードはロックされています：`promotionCode` を付けずにチェックアウトセッションを作成し直します |

コードを使って注文した購入者がまだ支払っていない場合、同じコードで新しい注文を作成できます。新しい注文が以前の注文の使用枠を引き継ぎます。以前の注文も支払い可能なままですが、それも支払われた場合は要確認となり、もう 1 回の使用としては数えません。

未払いの注文が使用枠を確保するのは 72 時間までです。それを過ぎると、同じクーポンを使う次の注文 —— そのクーポンのどのプロモーションコードでも、どの購入者の注文でも —— がその枠を取り戻します。未払いの注文はキャンセルされず状態もそのままで、後から支払われた場合は要確認となり、もう 1 回の使用としては数えません。

***

## サブスクリプション

新規サブスクリプションでも、単発商品と同じ方法・同じルールで割引を付けられます。購入者は支払い前に、初回の請求額と以降の各期間の金額を、割引前と割引後の両方で確認できます。

### 割引される期間の数

| `duration` | 割引される請求期間 |
| - | - |
| `once` | 1 期間 |
| `repeating` | 連続する `durationInPeriods` 期間 |
| `forever` | クーポンの有効期限まで、すべての期間 |

割引前の金額が 0 より大きい期間だけが割引を消費します。100% オフの期間は請求額が 0 でも、通常どおり 1 期間を消費します。

### トライアル

| トライアル | 割引される請求 |
| - | - |
| トライアルなし | 初回の請求と、それに続く期間 |
| 無料トライアル | トライアル中は割引されません：請求がなく、期間も消費しません。割引は最初の有料期間から始まります |
| 有料トライアル | トライアルの請求が割引され、最初の期間として数えます —— `once` のクーポンはトライアルで使い切り、`repeating` のクーポンはトライアルから期間を数えます |

プロモーションコードの最低金額は、初回の請求の割引前・税抜金額で判定します：有料トライアルではトライアル価格、それ以外は通常価格です。更新時には再判定しません。

例えば月額 $10、20% オフ、`repeating` 3 期間の場合、第 1〜3 期は $8、第 4 期以降は $10 を請求します。7 日間の無料トライアルと `once` のクーポンの場合、トライアルは $0、最初の有料月が割引され、以降は通常価格です。

### 使用回数が加算されるとき

* 割引される最初の請求が成功したときに 1 回加算されます：最初の有料の請求、有料トライアルではトライアルの請求です。以降の更新では加算されません。
* それまではサブスクリプションが使用枠を確保しています。その請求の前にサブスクリプションが終了した場合 —— 無料トライアル中のキャンセル、または初回の支払いが成功しなかった場合 —— 使用枠は戻り、購入者は再びコードを使えます。72 時間を超えて未払いのサブスクリプション注文はキャンセルされませんが、使用枠は確保しなくなります：同じクーポンを使う次の注文（そのクーポンのどのプロモーションコードでも）がその枠を取り戻します。
* その請求の前にクーポンの有効期限が過ぎ、通常価格で請求された場合も、使用枠は戻ります。
* 同じ購入者が同じコードで新しい注文を作成したときに引き継がれるのは、一度も支払われていない以前の注文の使用枠だけです。無料トライアル中の有効なサブスクリプションは使用枠を確保したままです：新しい注文には引き継がれず、確保中の枠はコードの回数制限に数えられます。

### 割引が終わるとき

* 適用期間が終わると、更新は通常価格で請求されます。
* **クーポンの有効期限は、サブスクリプションに適用済みの割引も終了させます**：期限後の更新は `forever` でも通常価格で請求されます。プロモーションコード自体の有効期限は、新しい使用を止めるだけです。
* クーポンを無効化しても、サブスクリプションに適用済みの割引は次回のプラン変更まで終了しません —— [プラン変更](#plan-changes)を参照してください。
* サブスクリプションをキャンセルすると割引は完全に失効します。期間終了前にキャンセルを取り消した場合、割引はそのまま続きます。
* プラン変更では、割引が新しいプランに引き継がれない限り割引は終了します —— [プラン変更](#plan-changes)を参照してください。

### プラン変更

プラン変更は、サブスクリプションを新しいプランの新しいサブスクリプションに置き換えます。プラン変更セッションが許可していれば、プラン変更ページにコード入力欄が表示されます。また、新規購入と同じようにプラン変更セッションに `couponId` や `promotionCode` を設定できます —— [チェックアウトセッションの作成](https://docs.waffo.ai/ja/api-reference/endpoints/orders/create-checkout-session.md#discounts)を参照してください。商品リンクの `discount_code` はプラン変更には使えません。プラン変更ページではメールアドレスを入力しないため、プロモーションコードの対象購入者リストは元のサブスクリプションの購入者メールアドレスで照合します —— コード入力時もプラン変更の送信時も同じです。

| プラン変更 | 新しいサブスクリプションの割引 |
| - | - |
| コードあり —— 購入者がプラン変更ページで入力、または `couponId` / `promotionCode` で設定 | 新しい割引が現在の割引を置き換え、現在の割引の残りは無効になります。新しい利用として扱われます：変更の提出時にすべてのルールが再度確認され、使用枠を確保して 1 回加算され、期間は新しいサブスクリプションから数えます。サブスクリプションですでに使っているコードを入力した場合も新しい利用です |
| コードなし、現在の割引を継続できる | 現在の割引が引き継がれます。使用済みの期間はリセットされず、残りの期間が新しいプランで続きます。引き継ぎは新しい利用ではなく、使用枠の確保も加算もしません |
| コードなし、現在の割引を継続できない | 新しいサブスクリプションには割引がありません。割引の期間を使い切った、クーポンが期限切れまたは 60 秒以内に期限切れになる、クーポンが無効化された場合は継続できません。 |

つまり、**クーポンを無効化すると、サブスクリプションに適用済みの割引は次回のプラン変更で終了します。**

* **第 1 期。** 即時に適用される変更では、新しいサブスクリプションの第 1 期の金額は、新しいプランの価格から当期の未使用分のクレジットを引いた額 —— または `changeAmount` / `changeCreditAmount` で指定した金額 —— です。これが 0 より大きければ割引が適用され、1 期間を消費します。**割引は新しいプランの全額で計算し、そのあとで元のプランの残りのクレジットを差し引きます**：パーセント割引は新しいプランの価格に対する割合、定額割引は同じ金額で、割引額は第 1 期の金額が上限です。購入者の支払いは第 1 期の金額から割引を引いた額で、0 を下回りません。割引が第 1 期の金額全体をカバーする場合も 1 期間を消費します。0 の場合 —— 多くはクレジットが新しいプランの価格以上になるダウングレード —— 割引は適用されず期間も消費せず、割引は第 2 期から始まります。次の期間から適用される変更は通常価格の期間から始まり、トライアルを付与する変更は上記のトライアルのルールに従います。
* **クレジットは購入者が支払う金額に基づきます。** 当期の未使用分のクレジットは、その期間に購入者が支払う金額から計算されます：その期間が割引されていれば割引後の金額、そうでなければ表示価格です。
* **プロモーションコードの最低金額**は、新しいプランの通常価格（割引前・税抜）で判定します。クレジットは差し引きません。
* **使用回数が加算されるとき。** 新しいコードは、新しいサブスクリプションで割引される最初の請求が成功したときに加算されます —— 第 1 期、または第 1 期が 0 の場合は第 2 期です。変更が完了しなかった場合 —— 購入者が完了しなかった、決済チャネルが失敗を報告した、次の期間から適用される変更がキャンセルされた —— 使用枠は戻ります。開始されたまま完了しない変更は、72 時間後に使用枠を確保しなくなり、同じクーポンを使う次の注文（そのクーポンのどのプロモーションコードでも）がその枠を取り戻します —— すでに終了したサブスクリプションからその変更へ引き継がれる途中の枠も同様です。現在のサブスクリプション自身のコードが割引される最初の請求をまだ待っている場合、引き継がれる割引はその確保中の枠を新しいサブスクリプションに移します。新しいコードまたはコードなしの場合、その枠は戻ります。

例えば月額 $5、毎期 $1 引き、`repeating` 3 期間で 1 期間使用済みのとき、購入者が期間の半ばにコードなしで月額 $10 にアップグレードしたとします。クレジットは $2（支払った $4 の半分）なので、第 1 期は割引前 $8・支払額 $7、次の期間は $9、それ以降は $10 です。代わりに新しい 20% オフ `once` のコードを入力した場合、割引は $2（月額 $10 の新しいプランの 20%）なので、第 1 期は割引前 $8・支払額 $6、それ以降は $10 で、元の割引の残り 1 期間は無効になります。

### サブスクリプションの支払い金額

* サブスクリプションの注文には表示価格が記録されます。各支払いには、その期間について Pancake が想定する金額 —— 元の価格、割引額、支払額 —— が記録されます。[`subscription.payment_succeeded`](https://docs.waffo.ai/ja/api-reference/webhooks) webhook は `listPrice` にこれらの金額（`listTotal`、`discountAmount`、`total`）を含めます。
* 請求額が想定した割引と一致しない場合 —— 割引が早く終了した、または多く・少なく請求された場合 —— Pancake が内部で照合します。支払いには想定金額が記録されたままで、実際の請求額は webhook の `chargedAmount` にあります。
* 初回の領収書 —— 有料トライアルではトライアルの請求、トライアルなしでは最初の請求 —— には、その請求が割引される場合、元の価格、割引、小計、税額、支払額が表示されます。無料トライアルの初回の領収書は \$0 で、内訳は表示されません。
* 各更新の領収書にも、その期間が割引される場合は同じ内訳が表示されます。トライアル終了後の最初の請求では更新の領収書は送られません —— 無料トライアルでは、これがまさに割引される期間です。購入者は請求予定の通知でその想定金額を確認できます。
* メールに表示される次回の請求額は、次の期間も割引される場合にのみ割引が反映されます。
* プラン変更後、新しいサブスクリプションの最初の支払いは第 1 期の金額を想定として記録します（[プラン変更](#plan-changes)を参照）が、この支払いの領収書は送信されません。第 1 期が 0 の場合、この支払いは \$0 の支払いです。以降の支払いは通常期間の金額を想定として記録します。顧客には代わりに「プラン変更」のメールが届き、新しいプランの割引前の通常価格、次回請求日、次回請求額（その期間に割引が適用される場合は割引後の金額）が表示されます。第 1 期の金額や割引の内訳は表示されません。支払い記録には第 1 期の各金額が残ります。以降の更新では上記のとおり領収書が送信されます。

***

## 金額、領収書、使用回数

* **割引は税込価格に適用されます。** パーセンテージ割引は通貨の最小単位に切り捨てます。固定額で合計がゼロを下回ることはなく、100% オフでは 0 円の注文になります。
* **注文、支払い記録、領収書、Dashboard には 3 つの金額が表示されます**：元の価格、割引額、購入者の支払額。返金の上限は支払額です。
* **注文の価格スナップショットには表示価格が記録されます。** 単発注文でもサブスクリプション注文でも、スナップショットの `total` は割引前の税込価格です。各請求の割引後の金額は支払い記録（`snapshotAmountDetails.total`）にあります。
* **割引のある領収書**には、元の価格、割引、小計（割引後・税抜）、税額、支払額の順に表示されます。小計と税額の行は課税される場合のみ表示されます。
* **使用回数は支払い成功時に加算されます** —— サブスクリプションでは割引される最初の請求です（[サブスクリプション](#subscriptions)を参照）。注文がキャンセルまたは期限切れになると回数が戻り、購入者は再びコードを使えます。返金しても使用回数は戻りません。

***

## 次のステップ

> **[クーポンエンドポイント](https://docs.waffo.ai/ja/api-reference/endpoints/coupons/overview.md)**
>
> クーポンの作成、名前の変更、無効化

> **[チェックアウトセッションの作成](https://docs.waffo.ai/ja/api-reference/endpoints/orders/create-checkout-session.md#discounts)**
>
> クーポンの適用、またはコード入力欄の表示


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.