# Customize subscription proration calculations

Implement custom logic to calculate proration amounts for subscription changes.

Stripe Billing lets you customize how proration amounts are calculated when subscriptions change mid-cycle. With custom proration, you can implement business-specific rules for handling upgrades, downgrades, quantity changes, and billing period adjustments.

By default, Stripe calculates prorations automatically when subscription changes occur based on your existing proration setting. Custom proration scripts allow you to override this behavior with your own calculation logic.

Example use cases include:

- Rounding proration to the nearest day
- Control proration on a line item level
- Provide promotional proration rates during campaigns

## Customize proration logic 

With the standard proration logic, Stripe calculates proration down to the second based on the starting timestamp of the billing period and the timestamp of the proration’s [triggering event](https://docs.stripe.com/billing/subscriptions/prorations.md#what-triggers-prorations). This extension point gives you exact control over *how* we calculate prorations.

Your script receives the invoice items, then returns computed proration factors for each item.

### Proration factor 

The proration factor is a multiplier to the item’s full cost to compute the final prorated cost. The standard formula for proration factor is the item’s service period duration (time of use) divided by the billing period duration.

For a non-prorated item, the factor is 1 because the item’s service period duration is equivalent to the billing period duration.

For a prorated item triggered in the middle of the billing period, the factor is 0.5 because the service period is half of the billing period.

The proration factor is a positive number for DEBIT items (charges) and negative for CREDIT items (credits).

### Stripe-authored scripts

To get you started, Stripe has authored two scripts that address common proration customizations:

- [Prorate by custom interval](https://docs.stripe.com/billing/scripts/stripe-authored.md#prorate-by-custom-interval): By default, Stripe calculates proration down to the second. With this script, you can select a different time interval (“hour”, “day”, “week”, “month”) to round the start and end timestamps used in proration computation.
- [Credit and debit full product price](https://docs.stripe.com/billing/scripts/stripe-authored.md#credit-and-debit-full-product-price): When prorating a product with particular metadata, this script will charge the full cost instead of the prorated amount. For example, if you upgrade from product A to B mid-cycle, this script fully credits the charge for product A and fully charges for product B.

### Author your own script

To further customize proration calculation logic, you can [author your own script](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin) in a [subset of TypeScript](https://docs.stripe.com/billing/scripts/script-language.md).

> #### Verify your script
> 
> You’re responsible for verifying that your script reflects your desired functionality. Make sure you don’t enter any proprietary, confidential information (for example, *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)), or malicious code.

## Before you begin

Before implementing custom proration logic, familiarize yourself with how [Stripe’s standard prorations](https://docs.stripe.com/billing/subscriptions/prorations.md) work. Custom proration scripts override the default calculation while maintaining the same billing workflow.

For an introduction to scripts and how to deploy them, see the [Scripts overview](https://docs.stripe.com/billing/scripts.md).

See also:

- [Subscription prorations guide](https://docs.stripe.com/billing/subscriptions/prorations.md)
- [Changing subscriptions](https://docs.stripe.com/billing/subscriptions/change.md)
- [Subscription API](https://docs.stripe.com/api/subscriptions.md)

In addition to the [global limitations for user-authored scripts](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin), proration scripts have the following limitations:

- Configured at the account level. You can’t apply different scripts to individual customers or subscriptions.
- One-off invoice items without full service periods (start and end dates) aren’t included in proration requests. This is because these items have prices not associated with a recurring time interval (day, month, year, and so on).
- Only items related to proration events (upgrade, downgrade, and cancel subscription) will be available to modify their proration factor and line item period.
- You can’t control *when* prorations are created. To control the creation of prorations, use the [proration_behavior](https://docs.stripe.com/api/subscriptions/update.md#update_subscription-proration_behavior) parameter.

## Example implementation

Here’s a complete example that implements a custom proration calculation rounding up to the nearest whole day:

```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();
}
```

### Key implementation points

This example illustrates the following points. You should keep these in mind when you author your own script:

- **Map each item**: Process each `ProratableItem` and return an `ItemWithProration`.
- **Calculate factor**: The `prorationFactor` represents the portion of the billing period to charge or credit. Positive values represent charges (DEBIT items), negative values represent credits (CREDIT items). The magnitude indicates the portion of the price to apply for DEBIT items (for example, `0.5` means 50% charge). For CREDIT items it represents the portion of the previous debit to credit back (`-0.75` means 75% credit of the previous debit charge).
- **Preserve keys**: Always return the same `key` to match items in the request.
- **Respect item types**: Make sure DEBIT items have positive factors and CREDIT items have negative factors to maintain correct billing behavior. Otherwise we’ll ignore the returned proration factor for the item.
- **Time handling**: Use the `servicePeriod` dates to calculate usage duration.
- **Configuration**: Define your own configuration type for customizable behavior.

### SDK reference

For complete TypeScript type definitions and interfaces, see the [Extensibility SDK on GitHub :external: link-to-extensibility-sdk](https://github.com/stripe/extensibility-sdk/tree/master/libs/extensibility-sdk/src/extensions/billing/prorations.ts).

### Extension method

Implement the `prorateItems` function to customize proration calculations:

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

#### Parameters

| Field           | Type                 | Description                                                                                                       |
| --------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `request`       | `ProrateItemsInput`  | The items requiring proration calculation.                                                                        |
| `configuration` | `MyProrationsConfig` | Your custom configuration object. You define the structure based on your needs.                                   |
| `context`       | `Context`            | Runtime context including the extension identifier, livemode status, and optional Stripe-specific context values. |

#### Returns

| Field                | Description                                     |
| -------------------- | ----------------------------------------------- |
| `ProrateItemsResult` | The calculated proration factors for each item. |

### Input and output types

#### Input type

```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';
```

##### Field descriptions 

| Field                    | Type                                                                                 | Description                                                                                                                                                                                          |
| ------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`                  | `Array<ProratableItem>`                                                              | The list of items that can have their proration factor and line item period modified by your script.                                                                                                 |
| `key`                    | `string`                                                                             | The key of the item for managing modification of many items during runtime.                                                                                                                          |
| `type`                   | `ItemType`                                                                           | Either `credit` or `debit` based on whether the item is a credit being given back to the customer or a debit being owed by the customer.                                                             |
| `priceKind`              | `'price' | 'licenseFee' | 'rateCardRate' | 'customPricingUnitOverageRate' | 'other'` | The discriminant that tells you which pricing payload is available on the item.                                                                                                                      |
| `isProration`            | `boolean`                                                                            | Whether the item is generated from a proration event.                                                                                                                                                |
| `servicePeriod`          | `TimeRange`                                                                          | Period used to calculate the current proration factor.                                                                                                                                               |
| `currentProrationFactor` | `number`                                                                             | The default proration factor calculated by Stripe.                                                                                                                                                   |
| `priceIntervalDuration`  | `number`                                                                             | Amount of time in seconds for the whole period of the item. For example, a monthly subscription has the number of seconds in a month.                                                                |
| `correspondingDebit`     | `PreviousDebit`                                                                      | Information about the corresponding debit that a CREDIT item credits against. For example, if a credit item is crediting for an already prorated debit, this field has information about that debit. |

##### Example input

```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"
        }
      }
    }
  ]
}
```

#### Output type

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

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

##### Field descriptions 

| Field             | Type        | Description                                                                                                                                                                                                                                                    |
| ----------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`             | `string`    | The unique identifier of the item. This must be a key of an item in the input.                                                                                                                                                                                 |
| `prorationFactor` | `Decimal`   | The overridden proration factor that you set on each item. Positive values represent charges (DEBIT items), negative values represent credits (CREDIT items). Can be set to the original `currentProrationFactor` value from the input if no change is needed. |
| `lineItemPeriod`  | `TimeRange` | The displayed period for the invoice line item. Can be set to the original `servicePeriod` value from the input if no change is needed.                                                                                                                        |

##### Example output

```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"
      }
    }
  ]
}
```

## Test your script

Before deploying to production:

1. Test in [test mode](https://docs.stripe.com/keys.md#test-live-modes) with various proration scenarios.
1. Verify that computed proration amounts are what you expect.

## Data validation requirements

Your script must meet these requirements to execute successfully. Violations result in a validation error.

- Return an `ItemWithProration` for every key in the input. Every proration item must have a corresponding entry in the output.
- Each `ItemWithProration` must include both `proration_factor` (number) and `line_item_period` (TimeRange with `start_date` and `end_date`).
- DEBIT items must have a positive `prorationFactor`. CREDIT items must have a negative `prorationFactor`.

## Best practices

- **Return valid factors**: Ensure proration factors have reasonable absolute values. Extremely high absolute values will result in over-crediting or over-debiting your customer. Remember that negative values represent credits (CREDIT items) and positive values represent charges (DEBIT items).
- **Respect item types**: Maintain the appropriate sign for each item type. DEBIT items are assigned positive proration factors, and CREDIT items are assigned negative proration factors.
- **Return valid line item periods**: These are the periods that will be shown alongside the items on the invoice to your customer.
- **Handle all items**: Process every item in the request and return a corresponding response.
- **Fall back to returning default values**: Use the default values for proration factor and line item period provided on the `ProratableItem` as needed. Fall back to `current_proration_factor` and `service_period` from the input when your logic doesn’t need to override a specific item.
- **Consider time zones**: All dates are in UTC.
- **Test thoroughly**: Use test mode or sandboxes to validate your logic before production.
- **Document your logic**: Add comments explaining your proration calculation method.
- Additionally:
  - For CREDIT items, use `corresponding_debit.service_period` to calculate the correct credit proportion.
  - Return reasonable `line_item_period` values, these are displayed to the customer on the invoice.
  - Test with both upgrades (DEBIT) and downgrades (CREDIT).

## Next steps

- Learn more about [scripts](https://docs.stripe.com/billing/scripts.md)
- See the [script language](https://docs.stripe.com/billing/scripts/script-language.md)
