# Customize discount calculations

Use scripts to calculate discounts using custom coupons.

### Interested in getting early access to customize coupons with scripts?

Enter your email to request access.

```bash
curl https://docs.stripe.com/preview/register \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Referer: https://docs.stripe.com/billing/scripts/discount-calculation" \
  -d '{"email": "EMAIL", "preview": "scripts_preview"}'
```

Discount calculation extensions let you replace Stripe’s default coupon discount calculation options with your own business logic. Use custom coupons to calculate discounts from customer metadata, apply different discounts to different products, or enforce a maximum discount amount.

Example use cases include:

- Apply a percentage discount only to selected products or prices.
- Give different customers different discounts based on metadata.
- Cap a percentage discount at a maximum amount.
- Apply a discount based on the quantities of products purchased.

## Customize discount calculations 

> You’re responsible for ensuring your configuration is correct. Stripe doesn’t take responsibility for any issues that arise as a result of your configuration.
> 
> If your script raises an error (for example, returns an invalid result, throws an exception, or times out), Stripe defaults to standard functionality for the operation that caused the failure.

Out of the box, Stripe has two coupon types that you can use to calculate discounts: `percent_off` and `amount_off`. This extension point allows you to customize the discount calculation logic using a `custom` coupon type.

Your script receives the runtime context, configured values, and the item being evaluated, then returns the amount to subtract from the item’s gross amount.

### Author your own script

To customize discount calculation behavior, [author your own script](https://docs.stripe.com/billing/scripts/author-your-own.md#before-you-begin).

> #### 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 a discount calculation script, familiarize yourself with [Coupons](https://docs.stripe.com/billing/subscriptions/coupons.md), [Discounts](https://docs.stripe.com/api/discounts/object.md), invoice finalization, and the [Billing scripts overview](https://docs.stripe.com/billing/scripts.md).

You also need:

- A [sandbox](https://docs.stripe.com/sandboxes.md) for development and testing.
- The [Stripe CLI](https://docs.stripe.com/cli.md), Node.js, pnpm, and the Stripe Apps CLI plugins required to build extensions.

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

- The script can’t change the items included on an invoice or modify the underlying prices.
- Multiple custom coupons can’t be applied to the same `Subscription`.
- Custom coupons can’t be applied to a `Customer` directly.
- Custom coupons aren’t supported in Quotes, Prebilling, or Amendments.
- Custom coupons can’t be used as retention coupons in the Customer Portal.
- Custom coupons can’t be used to provide direct invoice item level discounts. Calculated discounts are distributed evenly across all invoice items on the invoice.
- The returned discount amount must use the same currency as the item’s gross amount and must be non-negative.

## Example implementation

Stripe provides this implementation as the [Percent off up to maximum](https://docs.stripe.com/billing/scripts/stripe-authored/discount-calculation.md#percent-off-up-to-maximum) Stripe-authored script, which you can configure and use without writing code. The example below shows how the script applies a configurable percentage discount and caps the result at a maximum amount:

```typescript
import type { Commerce, Context, MonetaryAmount } from '@stripe/extensibility-sdk';
import { Decimal } from '@stripe/extensibility-sdk';

export interface PercentOffUpToMaximumConfig extends Record<string, unknown> {
  /**
   * Percentage of the invoice total to discount.
   * @displayName Percentage discount
   * @format percent
   */
  percentageDiscount: number;

  /**
   * Maximum monetary amount that can be discounted.
   * @displayName Maximum discount
   * @minimum :amount 0
   */
  maximumDiscount: MonetaryAmount;
}

function minimum(left: Decimal, right: Decimal): Decimal {
  return left.lte(right) ? left : right;
}

export default class PercentOffUpToMaximum implements Commerce.DiscountCalculation<PercentOffUpToMaximumConfig> {
  computeDiscounts(
    request: Commerce.DiscountCalculation.DiscountableItem,
    config: PercentOffUpToMaximumConfig,
    _context: Context
  ): Commerce.DiscountCalculation.DiscountResult {
    const { grossAmount } = request;
    const zero = Decimal.zero;

    if (
      grossAmount.currency !== config.maximumDiscount.currency ||
      !grossAmount.amount.isPositive() ||
      !config.maximumDiscount.amount.isPositive() ||
      config.percentageDiscount <= 0
    ) {
      return { discount: { amount: { amount: zero, currency: grossAmount.currency } } };
    }

    const percentage = Math.min(config.percentageDiscount, 1);
    const percentageDiscount = grossAmount.amount.mul(percentage);
    const cappedDiscount = minimum(
      minimum(percentageDiscount, config.maximumDiscount.amount),
      grossAmount.amount
    );

    return {
      discount: {
        amount: {
          amount: cappedDiscount.isNegative() ? zero : cappedDiscount,
          currency: grossAmount.currency,
        },
      },
    };
  }
}
```

### Key implementation points

This example illustrates the following best practices, which you should also follow when you author your own script:

- **Use configuration when needed**: If your script needs configurable values, define them as a configuration schema so account administrators can change the values without editing the script. Scripts that don’t need configurable values can omit configuration fields.
- **Non-negative result**: This script enforces that the returned amount is non-negative.

### Extension method

Implement the `computeDiscounts` function to define your custom discount logic:

```typescript
export default class MyDiscountCalculation implements Commerce.DiscountCalculation<MyDiscountCalculationConfig> {
  computeDiscounts(
    request: Commerce.DiscountCalculation.DiscountableItem,
    config: MyDiscountCalculationConfig,
    context: Context,
  ): Commerce.DiscountCalculation.DiscountResult {
    // ...
  }
}
```

#### Parameters

`computeDiscounts` receives the runtime context, configured values, and one `DiscountableItem`.

| Parameter | Description |
| --- | --- |
| `context` | A `Context` containing the extension invocation ID, extension type, livemode, and other runtime information. |
| `config` | The values configured for this extension. Define its TypeScript type and annotations so Stripe can validate and render the configuration form. |
| `request` | The gross amount of the invoice and the billing data that your discount logic can inspect. |

#### Returns

Return a `DiscountResult` containing one `discount.amount`. The amount is subtracted from the gross amount. Return zero when the item isn’t eligible or when your configuration can’t be applied.

### Input and output types

#### Input type

The `DiscountableItem` contains the following values:

| Field | Type | Description | **Description** |
| --- | --- | --- | --- |
| `grossAmount` | `MonetaryAmount` | The total amount before discounts. It includes a decimal `amount` and an ISO currency code. |
| `lineItems` | `Array<DiscountableLineItem>` | The individual items that make up the gross amount. Each item includes a subtotal, optional quantity, billing period, and optional price. |
| `billingReason` | `BillingReason` | The reason for billing, when available. |
| `customer` | `Customer` | The customer ID and metadata, when a customer is associated with the item. |

#### Billing reasons

The `billingReason` can be one of the following values:

- `automatic_pending_invoice_item_invoice`
- `contract_activate`
- `contract_cancel`
- `contract_cycle`
- `contract_update`
- `manual`
- `quote_accept`
- `subscription`
- `subscription_cancel`
- `subscription_create`
- `subscription_cycle`
- `subscription_threshold`
- `subscription_trial_ended`
- `subscription_update`
- `upcoming`

#### Line items, products, and prices

Each line item can include a price with its ID, metadata, product, price type, billing scheme, unit amount, and tiers. Recurring prices also include their interval, interval count, usage type, and meter. Products include their ID, name, and metadata.

Periods are either a one-time event or a time range with a start and end date. Use line-item data to apply discounts to selected products, prices, quantities, or service periods.

### Configuration

Configuration values are set when an account installs or configures your extension in a custom coupon. Define configuration as a TypeScript interface and use the supported SDK types, such as `MonetaryAmount`, `Decimal`, and `Timestamp`.

Use TSDoc annotations to provide Dashboard labels, defaults, and validation constraints. For example:

```typescript
import type {MonetaryAmount} from '@stripe/extensibility-sdk';

interface DiscountConfiguration extends Record<string, unknown> {
  /**
   * @displayName Percentage discount
   * @format percent
   */
  percentageDiscount: number;

  /**
   * @displayName Maximum discount
   * @minimum :amount 0
   */
  maximumDiscount: MonetaryAmount;
}
```

See [define configuration and custom input](https://docs.stripe.com/extensions/scripts/define-config-and-schemas.md) for supported types and annotations.

### Validation and errors

Your script must:

- Return a `DiscountResult` with `discount.amount.amount` and `discount.amount.currency` on every invocation.
- Return a non-negative discount amount.
- Return a discount amount that doesn’t exceed `grossAmount.amount`.
- Return a currency that matches `grossAmount.currency`.
- Preserve decimal precision by using the SDK’s decimal type instead of converting amounts to native JavaScript numeric values and possibly losing precision.

Stripe validates the result before applying it. Invalid results or runtime script failures cause the extension call to fail, and Stripe doesn’t retry failed discount-calculation requests. Return a zero discount when an item isn’t eligible or when required values don’t match. If the script fails, Stripe falls back to a zero discount amount.

## Test your extension

Write unit tests for each business rule in your extension’s `src/index.test.ts`. Include tests for:

- Percentage calculations and maximum caps.
- Currency mismatches and zero or empty inputs.
- Multiple line items, quantities, and tiered prices.
- Each billing reason your extension supports.
- Customer and subscription metadata rules.
- Invalid configuration and validation boundaries.

Test in a sandbox before activating the extension for live invoices. Build the app to regenerate and validate its configuration schema, then upload and install it in the sandbox. After you confirm the results, configure and activate the extension for the intended account.

## Configure your extension with a coupon

Configure your extension by creating a new `custom` coupon from either the Dashboard or the API.

#### Dashboard

1. Navigate to the Create Coupon page in the Dashboard.
2. Select **Custom** when selecting the coupon type.
3. Select your extension from the list of discount calculators.
4. If required, set the configuration for the extension.
5. Set other valid fields for the coupon.
6. Save and create the coupon.

#### API

1. Use the [Coupon](https://docs.stripe.com/api/coupons.md) to create a new coupon.
2. Set the `custom` field with `custom.extension.id`, `custom.extension.version`, and `custom.configuration` subfields.
3. Set the `type` field with value `custom`.
4. Set other valid fields for the coupon.
5. Send the request to create the coupon.

## Best practices

- Check currencies before calculating a discount and return a zero discount on a mismatch.
- Keep the calculation deterministic by using only the request, configuration, and context. Discount calculation scripts can’t make network calls or reference external state.
- Use SDK types such as `Decimal` and `MonetaryAmount` along with TSDoc annotations to get built-in configuration validation.
- Use customer, product, and price metadata for eligibility instead of hard-coding IDs.
- Test invoices with several line items to verify that the intended discount applies to the invoice’s gross amount.

## See also

- [Define configuration and custom input](https://docs.stripe.com/extensions/scripts/define-config-and-schemas.md)
- [Billing scripts](https://docs.stripe.com/billing/scripts.md)
