# サブスクリプションの比例配分計算のカスタマイズ

カスタムロジックを実装して、サブスクリプションの変更に対する比例配分額を計算する。

Stripe Billing では、サブスクリプションがサイクルの途中で変更された場合の比例配分額の計算方法をカスタマイズできる。カスタム比例配分を使用すると、アップグレード、ダウングレード、数量の変更、請求期間の調整を処理するためのビジネス固有のルールを実装できる。

デフォルトでは、既存の比例配分設定に基づいてサブスクリプションの変更が発生すると、Stripe は比例配分を自動的に計算します。カスタム比例配分スクリプトを使用すると、この動作を独自の計算ロジックで上書きできます。

以下にユースケースの例を示します。

- 比例配分を最も近い日に丸める
- 項目レベルでの比例配分の制御
- キャンペーン中にプロモーション比例配分率を提供する

## 比例配分ロジックのカスタマイズ

> 設定が正しいことを確認する責任はユーザーが負う。設定に起因して発生した問題について、Stripe は責任を負わない。
> 
> プレビューでは、設定が何らかの理由で失敗すると、Stripe は[このセクション](https://docs.stripe.com/billing/scripts/prorations.md#customize-proration-logic)で説明されているように、機能の動作をデフォルトに戻します。

標準比例配分ロジックでは、請求期間の開始タイムスタンプと比例配分の[トリガーイベント](https://docs.stripe.com/billing/subscriptions/prorations.md#what-triggers-prorations)のタイムスタンプに基づいて、Stripe は秒単位で比例配分を計算します。この拡張ポイントにより、比例配分の_「計算方法」_を正確に制御できます。

スクリプトは請求書の項目を受け取り、各項目に対して計算された比例配分係数を返します。

### 比例配分係数

比例配分係数は、項目の全コストに乗算して最終的な比例配分コストを計算するための乗数です。標準比例配分係数の計算式は、項目のサービス期間 (使用期間) を請求期間で割ったものです。

比例配分ではない項目の場合、項目のサービス期間が請求期間に相当するため、係数は 1 になります。

請求期間の途中でトリガーされる比例配分項目の場合、サービス期間は請求期間の半分であるため、係数は 0.5 です。

比例配分係数は、DEBIT 項目 (決済) の場合は正の数、CREDIT 項目 (クレジット) の場合は負の数です。

### Stripe が作成したスクリプト

まず始めに、Stripe は一般的な比例配分のカスタマイズに対応する 2 つのスクリプトを作成しました。

- [カスタム間隔で比例配分する](https://docs.stripe.com/billing/scripts/stripe-authored.md#prorate-by-custom-interval): デフォルトでは、Stripe は秒単位で比例配分を計算します。このスクリプトを使用すると、比例配分計算で使用される開始タイムスタンプと終了タイムスタンプを丸めるために、異なる時間間隔 (「時間」、「日」、「週」、「月」) を選択できます。
- [商品の全額を貸方計上および請求する](https://docs.stripe.com/billing/scripts/stripe-authored.md#credit-and-debit-full-product-price): 特定のメタデータを持つ商品を日割り計算する場合、このスクリプトは日割り金額ではなく全額を請求します。たとえば、請求期間の途中で商品 A から B にアップグレードする場合、このスクリプトは商品 A の料金を全額貸方計上し、商品 B の料金を全額請求します。

### 独自のスクリプトを作成

比例配分計算ロジックをさらにカスタマイズするために、[TypeScript のサブセット](https://docs.stripe.com/billing/scripts/script-language.md) を使用して [独自のスクリプトを作成](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin) することができます。

> #### スクリプトを確認する
> 
> スクリプトに目的の機能が反映されていることを確認する責任はお客様にあります。専有情報、機密情報 (たとえば、*PII* (Personally identifiable information (PII) is information that, when used alone or with other relevant data, can identify an individual. Examples include passport numbers, driver's license, mailing address, or credit card information))、または悪意のあるコードは入力しないでください。

## Before you begin

カスタム比例配分ロジックを実装する前に、[Stripe の標準比例配分](https://docs.stripe.com/billing/subscriptions/prorations.md)の仕組みを理解しておく必要があります。カスタム比例配分スクリプトは、同じ Billing ワークフローを維持しながら、デフォルトの計算を上書きします。

スクリプトの概要と展開方法については、[スクリプトの概要](https://docs.stripe.com/billing/scripts.md)を参照してください。

参照情報

- [サブスクリプション比例配分ガイド](https://docs.stripe.com/billing/subscriptions/prorations.md)
- [ サブスクリプションの変更](https://docs.stripe.com/billing/subscriptions/change.md)
- [ サブスクリプション API](https://docs.stripe.com/api/subscriptions.md)

[ユーザー作成スクリプトに共通する制限](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin) に加えて、比例配分スクリプトには以下のような制限があります:

- アカウントレベルで設定されます。個々の顧客やサブスクリプションごとに異なるスクリプトを適用することはできません。
- 完全なサービス期間 (開始日と終了日) のない 1 回限りの請求書の項目は、比例配分リクエストに含まれない。これは、これらの項目に定期的な期間 (日、月、年など) に関連付けられていない価格が設定されているためである。
- 比例配分イベント (アップグレード、ダウングレード、サブスクリプションのキャンセル) に関連する項目のみが、比例配分係数と項目期間を変更できる。
- 比例配分が_「いつ」_作成されるかは制御できません。比例配分の作成を制御するには、[proration_behavior](https://docs.stripe.com/api/subscriptions/update.md#update_subscription-proration_behavior) パラメータを使用します。

## 実装例

以下は、日単位に切り上げるカスタム比例配分計算を実装した完全な例である。

```typescript
import type { Billing, Context } from '@stripe/extensibility-sdk/extensions';
import { Decimal } from '@stripe/extensibility-sdk/stdlib';

interface MyProrationsConfig extends Record<string, unknown> {
  roundToNearestDay: boolean;
}

export default class MyProrations implements Billing.Prorations<MyProrationsConfig> {
  prorateItems(
    request: Billing.Prorations.ProrateItemsInput,
    configuration: MyProrationsConfig,
    _context: Context
  ): Billing.Prorations.ProrateItemsResult {
    const items: Array<Billing.Prorations.ItemWithProration> = request.items.map((item) => {
      const prorationFactor = calculateProrationFactor(
        item,
        configuration.roundToNearestDay,
      );

      return {
        key: item.key,
        prorationFactor,
        lineItemPeriod: item.servicePeriod,
      };
    });

    return {items};
  }
}

function calculateProrationFactor(
  item: Billing.Prorations.ProratableItem,
  roundToNearestDay: boolean,
) {
  if (!roundToNearestDay) {
    return item.currentProrationFactor;
  }

  const periodStart = new Date(item.servicePeriod.startDate).getTime() / 1000;
  const periodEnd = new Date(item.servicePeriod.endDate).getTime() / 1000;
  const periodDuration = periodEnd - periodStart;

  if (item.type === 'debit') {
    const daysUsed = Math.ceil(periodDuration / 86400);
    const totalDays = Math.ceil(item.priceIntervalDuration / 86400);

    return Decimal.from(daysUsed).div(Decimal.from(totalDays), 12, 'half-even');
  }

  const debitPeriodStart = new Date(item.correspondingDebit!.servicePeriod.startDate).getTime() / 1000;
  const debitPeriodEnd = new Date(item.correspondingDebit!.servicePeriod.endDate).getTime() / 1000;
  const debitPeriodDuration = debitPeriodEnd - debitPeriodStart;

  const daysUsed = Math.ceil(periodDuration / 86400);
  const totalDays = Math.ceil(debitPeriodDuration / 86400);

  return Decimal.from(daysUsed)
    .div(Decimal.from(totalDays), 12, 'half-even')
    .neg();
}
```

### 主な実装ポイント

この例は、次の点を示しています。独自のスクリプトを作成する際には、これらの点に注意してください。

- **各項目のマッピング**: 各 `ProratableItem` を処理し、 `ItemWithProration` を返す。
- **係数の計算**: `prorationFactor` は、請求期間のうち請求またはクレジットの対象となる割合を表します。正の値は請求 (DEBIT 項目)、負の値はクレジット (CREDIT 項目) を表します。値の大きさは、DEBIT 項目に適用する価格の割合を示します (たとえば、`0.5` は 50% の請求を意味します)。CREDIT 項目の場合は、以前のデビット項目のうちどの程度をクレジットとして戻すかを表します (`-0.75` は、以前のデビット請求額の 75% をクレジットすることを意味します)。
- **キーの保持**: 常に同じ `key` を返し、リクエスト内の項目を照合する。
- **項目タイプを尊重**: 請求の動作を正しく維持するために、DEBIT 項目にプラスの要因、CREDIT 項目にマイナスの要因があることを確認してください。そうでない場合、項目に対して返される比例配分要因は無視されます。
- **時間の処理**: 使用期間の計算には、`servicePeriod` の日付を使用します。
- **Configuration (設定)**: カスタマイズ可能な動作に対して独自の設定タイプを定義する。

### SDK リファレンス

TypeScript の完全な型定義とインターフェイスについては、[GitHub の Extensibility SDK :external: link-to-extensibility-sdk](https://github.com/stripe/extensibility-sdk/tree/master/libs/extensibility-sdk/src/extensions/billing/prorations.ts) を参照してください。

### 拡張メソッド

比例配分計算をカスタマイズするには、`prorateItems` 関数を実装します。

```typescript
export default class MyProrations implements Billing.Prorations<MyProrationsConfig> {
  prorateItems(
    request: ProrateItemsInput,
    configuration: MyProrationsConfig,
    context: Context,
  ): ProrateItemsResult {
    // ...
  }
}
```

#### パラメーター

| フィールド           | 種類                   | 説明                                                              |
| --------------- | -------------------- | --------------------------------------------------------------- |
| `request`       | `ProrateItemsInput`  | 比例配分計算が必要な項目。                                                   |
| `configuration` | `MyProrationsConfig` | カスタムの設定オブジェクトです。ニーズに基づいて構造を定義します。                               |
| `context`       | `Context`            | 拡張機能の識別子、livemode ステータス、および任意の Stripe 固有のコンテキスト値を含むランタイムコンテキスト。 |

#### 返品

| フィールド                | 説明               |
| -------------------- | ---------------- |
| `ProrateItemsResult` | 各項目の計算された比例配分係数。 |

### 入力タイプと出力タイプ

#### 入力タイプ

```typescript
export interface ProrateItemsInput {
  items: Array<ProratableItem>;
}

export type ProratableItem = {
  key: string;
  type: ItemType;
  isProration: boolean;
  servicePeriod: TimeRange;
  currentProrationFactor: Decimal;
  priceIntervalDuration: number;
  correspondingDebit?: PreviousDebit;
} & (
  | { priceKind: 'price'; price: Price }
  | { priceKind: 'licenseFee'; licenseFee: LicenseFee }
  | { priceKind: 'rateCardRate'; rateCardRate: RateCardRate }
  | { priceKind: 'customPricingUnitOverageRate'; customPricingUnitOverageRate: CustomPricingUnitOverageRate }
  | { priceKind: 'other'; otherPriceKind: string }
);

export type ItemType = 'credit' | 'debit';
```

##### フィールドの説明

| フィールド                    | 種類                                                                                   | 説明                                                                                                      |
| ------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
| `items`                  | `Array<ProratableItem>`                                                              | 比例配分係数と項目期間をスクリプトで変更できる項目のリスト。                                                                          |
| `key`                    | `string`                                                                             | 実行時の多数の項目の変更を管理するための項目のキー。                                                                              |
| `type`                   | `ItemType`                                                                           | 項目が顧客に返すクレジットか、顧客が支払うデビットかに応じて、`credit` または `debit` のいずれかです。                                            |
| `priceKind`              | `'price' | 'licenseFee' | 'rateCardRate' | 'customPricingUnitOverageRate' | 'other'` | 項目で使用可能な料金ペイロードを示す識別情報。                                                                                 |
| `isProration`            | `boolean`                                                                            | 比例配分イベントから項目が生成されるかどうか                                                                                  |
| `servicePeriod`          | `TimeRange`                                                                          | 現在の比例配分係数の計算に使用される期間。                                                                                   |
| `currentProrationFactor` | `number`                                                                             | Stripe が計算するデフォルトの比例配分係数。                                                                               |
| `priceIntervalDuration`  | `number`                                                                             | 項目の全期間の秒数。たとえば、月次サブスクリプションの場合、1 ヵ月間の秒数が用いられます。                                                          |
| `correspondingDebit`     | `PreviousDebit`                                                                      | クレジット項目がクレジットする対応するデビットに関する情報 (たとえば、クレジット項目がすでに比例配分されたデビットに対してクレジットされている場合、このフィールドにはそのデビットに関する情報が含まれます) |

##### 入力例

```json
{
  "items": [
    {
      "key": "ubi_123456",
      "type": "debit",
      "priceKind": "price",
      "price": {
        "id": "price_123456",
        "metadata": {},
        "recurring": {
          "interval": "month",
          "intervalCount": 1,
          "usageType": "licensed",
          "meter": null
        },
        "type": "recurring",
        "unitAmount": "0.2e4",
        "product": {
          "name": "Basic plan",
          "id": "prod_123456",
          "metadata": {
            "abc": "123"
          }
        },
        "tiersMode": null,
        "tiers": null,
        "currency": "usd",
        "billingScheme": "per_unit",
      },
      "isProration": true,
      "servicePeriod": {
        "startDate": "2024-01-15T12:00:00Z",
        "endDate": "2024-02-01T00:00:00Z"
      },
      "currentProrationFactor": 0.5,
      "priceIntervalDuration": 2678400
    },
    {
      "key": "ubi_654321",
      "type": "credit",
      "priceKind": "price",
      "price": {
        "id": "price_123456",
        "metadata": {},
        "recurring": {
          "interval": "month",
          "intervalCount": 1,
          "usageType": "licensed",
          "meter": null
        },
        "type": "recurring",
        "unitAmount": "0.2e4",
        "product": {
          "name": "Basic plan",
          "id": "prod_123456",
          "metadata": {
            "abc": "123"
          }
        },
        "tiersMode": null,
        "tiers": null,
        "currency": "usd",
        "billingScheme": "per_unit",
      },
      "isProration": true,
      "servicePeriod": {
        "startDate": "2024-01-15T12:00:00Z",
        "endDate": "2024-02-01T00:00:00Z"
      },
      "currentProrationFactor": -0.5,
      "priceIntervalDuration": 2678400,
      "correspondingDebit": {
        "servicePeriod": {
          "startDate": "2024-01-01T00:00:00Z",
          "endDate": "2024-02-01T00:00:00Z"
        }
      }
    }
  ]
}
```

#### 出力タイプ

```typescript
export interface ProrateItemsResult {
  items: Array<ItemWithProration>;
}

export interface ItemWithProration {
  key: string;
  prorationFactor: Decimal;
  lineItemPeriod: TimeRange;
}
```

##### フィールドの説明

| フィールド             | 種類          | 説明                                                                                                                      |
| ----------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| `key`             | `string`    | 項目の一意の識別子。これは、入力項目のキーである必要があります。                                                                                        |
| `prorationFactor` | `Decimal`   | 各項目に設定する上書きされた日割り計算係数。正の値は請求 (DEBIT 項目)、負の値はクレジット (CREDIT 項目) を表します。変更が不要な場合は、入力内の元の `currentProrationFactor` 値に設定できます。 |
| `lineItemPeriod`  | `TimeRange` | 請求書項目に表示される期間。変更が不要な場合は、入力内の元の `servicePeriod` 値に設定できます。                                                                |

##### 出力例

```json
{
  "items": [
    {
      "key": "ubi_654321",
      "prorationFactor": 0.51612903,
      "lineItemPeriod": {
        "startDate": "2024-01-15T00:00:00Z",
        "endDate": "2024-02-01T00:00:00Z"
      }
    },
    {
      "key": "ubi_123456",
      "prorationFactor": -0.51612903,
      "lineItemPeriod": {
        "startDate": "2024-01-15T00:00:00Z",
        "endDate": "2024-02-01T00:00:00Z"
      }
    }
  ]
}
```

## スクリプトをテスト

本番環境にデプロイする前に:

1. [テスト環境](https://docs.stripe.com/keys.md#test-live-modes)でさまざまな比例配分シナリオを使用してテストします。
1. 計算された比例配分額が想定どおりであることを確認します。

## データ検証要件

スクリプトを正常に実行するには、これらの要件を満たす必要があります。違反すると検証エラーが発生します。

- 入力内の各キーに対して `ItemWithProration` を返します。各比例配分項目には、出力内に対応するエントリが必要です。
- 各 `ItemWithProration` には、`proration_factor` (数値) と、`start_date` と `end_date` を含む `line_item_period` (TimeRange) の両方を含める必要があります。
- DEBIT 項目には正の `prorationFactor`、CREDIT 項目には負の `prorationFactor` が必要です。

## ベストプラクティス

- **有効な係数を返す**: 比例配分係数には妥当な絶対値を指定すること。絶対値が極端に高いと、顧客に対して過剰なクレジットや過剰な引き落としが発生する。負の値はクレジット (CREDIT 項目)、正の値は支払い (DEBIT 項目) を表すことに注意すること。
- **項目タイプの尊重**: 各項目タイプに適切な符号を維持する。DEBIT 項目には正の比例配分係数が割り当てられ、CREDIT 項目には負の比例配分係数が割り当てられる。
- **有効な項目期間を返す**: 顧客への請求書の項目の横に表示される期間である。
- **すべての項目を処理する**: リクエストのすべての項目を処理し、対応する応答を返す。
- **デフォルト値を返すようにフォールバックします**: 必要に応じて、`ProratableItem` で指定された比例配分係数と明細項目期間のデフォルト値を使用します。ロジックで特定の項目を上書きする必要がない場合は、入力内の `current_proration_factor` と `service_period` にフォールバックします。
- **タイムゾーンを考慮する**: 日付はすべて UTC である。
- **徹底的なテスト**: テスト環境またはサンドボックスを使用して、本番環境に移行する前にロジックを検証する。
- **ロジックの文書化**: 比例配分の計算方法を説明するコメントを追加する。
- さらに:
  - CREDIT 項目では、正しいクレジット比率を計算するために `corresponding_debit.service_period` を使用します。
  - 妥当な `line_item_period` の値を返してください。これらの値は請求書で顧客に表示されます。
  - アップグレード (DEBIT) とダウングレード (CREDIT) の両方でテストしてください。

## 次のステップ

- [スクリプト](https://docs.stripe.com/billing/scripts.md)についてもっと知る
- [スクリプト言語](https://docs.stripe.com/billing/scripts/script-language.md)を参照
