# Custom coupons

Implement custom logic to compute discount amounts for subscriptions and invoices.

Stripe Billing lets you configure a coupon with custom logic to compute the discount amount for subscriptions and invoices, beyond fixed percentages or amounts off. Here are some examples of what you can do with a single coupon:

- Provide 10% off for annual subscribers, but no discount for monthly subscribers.
- Provide 20% off, but up to a maximum of 1000 USD. Only provide this for your high-value customers (tagged using metadata).
- Provide tiered discounts (for example, 100 USD off if more than 10 seats are purchased, otherwise 30 USD off).

## Get started 

To understand how custom logic integrates with the billing workflow, you can explore the Stripe-authored scripts available to Billing users in our [script SDK](https://github.com/stripe/scripts).

You can use these scripts as a foundation to build your own logic using our [scripting language](https://docs.stripe.com/billing/scripts/script-language.md). We recommend starting in a [sandbox](https://docs.stripe.com/sandboxes.md).

## Use Stripe-authored custom logic 

You can create a coupon with **Stripe-authored** custom logic by navigating to the **Create a Coupon** page and selecting **Percentage off up to maximum** type. This gives you an idea of how custom logic scripts are applied to a coupon before authoring your own script.

### Create a coupon 

We recommend starting this process in a [sandbox](https://docs.stripe.com/sandboxes.md).

#### Create a coupon with no code 

You can create a coupon with Stripe-authored custom logic by navigating to the [create a coupon page](https://dashboard.stripe.com/test/coupons/create), and using the `Percentage off up to maximum` coupon type, which uses a Stripe-authored script.

#### Create a coupon using the API 

To create script coupons with the API, refer to the table below for the script specifications.

| Name | ID | Script definition |
| --- | --- | --- |
| Percentage off up to maximum | `bfsimpl_61S9T2y7zlKu9YlG116S8pZoN2SQpuwOX348dhXM0R9c` | [See script definition :external: link-to-percent-off-up-to-maximum-script](https://docs.stripe.com/billing/subscriptions/script-coupons.md#percentage-off-up-to-maximum-example) |
| ```js
{
  max_discount_amount: {
    amount: number;
    currency: string;
  },
  discount_percent: number;
}
```

- `max_discount_amount.amount`: value in the [currency’s minor unit](https://docs.stripe.com/currencies.md#minor-units)
- `max_discount_amount.currency`: The three-letter [ISO code for the currency](https://docs.stripe.com/currencies.md#presentment-currencies)
- `discount_percent`: value as percentage |

For example, configuring a 20% off up to 100 USD coupon using `Percentage off up to maximum` looks like this:

```curl
curl https://api.stripe.com/v1/coupons \
  -u "<<YOUR_SECRET_KEY>>:" \
  -d duration=forever \
  -d "name=My coupon" \
  -d "script[id]=bfsimpl_61S9T2y7zlKu9YlG116S8pZoN2SQpuwOX348dhXM0R9c" \
  -d "script[configuration][max_discount_amount][amount]=10000" \
  -d "script[configuration][max_discount_amount][currency]=usd" \
  -d "script[configuration][discount_percent]=20"
```

### Apply a coupon 

After creating the coupon, apply it to subscriptions or invoices following the same process as for a standard coupon. See [coupons](https://docs.stripe.com/billing/subscriptions/coupons.md) for detailed instructions.

### Script coupon limitations 

Script coupons have the following limitations:

- You can’t use script coupons on line item coupons.
- You can’t stack script coupons with other script coupons.
- You can’t use script coupons on [Quote](https://docs.stripe.com/api/quotes.md) objects.
- You can’t use script coupons on [Customer](https://docs.stripe.com/api/customer.md) objects.

## Author a script 

To meet specific business needs, you can create your own custom logic using our [scripting language](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 not to 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.

### Discount calculator interface 

All coupon scripts must abide by the discount calculator interface defined by Stripe.

```typescript
export type DiscountCalculationFunction<C> = (
  run_context: RunContext,
  configuration: C,
  discountable_item: DiscountableItem,
) => DiscountResult;
```

#### Configuration 

You can define configuration values that users must provide when they’re creating a script type coupon. The configuration values set for the coupon are passed into the discount function each time you invoke it.

```typescript
export type DiscountCalculatorConfiguration = {
  max_discount_amount: {
    amount: number;
    currency: string;
  },
  discount_percent: number;
};
```

#### DiscountableItem

We pass the `DiscountableItem` into the discount calculator and make this data available for you to use within your script. As an example, the discountable item might look like the following. For the latest definition of the parameter, refer to our [SDK](https://github.com/stripe/scripts/blob/main/src/discount_calculation/types.ts). You’ll see familiar Stripe objects like [Customer](https://docs.stripe.com/api/customer/object.md) passed into the script. However, these objects will only contain a subset of the data that is available in the API.

```typescript

export interface DiscountableLineItem {
  subtotal: MonetaryAmount;
  quantity?: number | null;
  period: TimeRange;
  price?: Price | null;
}

export interface DiscountableItem {
  line_items: Array<DiscountableLineItem>;
  gross_amount: MonetaryAmount;
  customer?: Customer | null;
  billing_reason?: BillingReason | null;
  subscription?: Subscription | null;
}
```

#### DiscountResult

Your script must return a discount result.

```typescript

export interface DiscountResult {
  discount: Discount;
}

```

Example of returning a discount result:

```typescript
  return {
    discount: {
      amount: {
        amount: discountAmount,
        currency: item.gross_amount.currency,
      },
    },
  };
```

## Sample Scripts 

If you have access to the private preview, you can submit your custom discounting logic to the Stripe Billing team through email. The below sample scripts allow you to get a sense of the custom logic you can create, and gives you a template to start working with.

### Percentage-off discount with a maximum amount 

```typescript
import type {
  ComputeDiscountsFunction,
  DiscountCalculation,
  DiscountableItem,
  DiscountResult,
} from '@stripe/scripts/discount_calculation';

import type {PositiveMonetaryAmount, Percent, RunContext} from '@stripe/scripts';

/**
 * Configuration for the discount calculator function
 */
export type DiscountCalculatorConfiguration = {
  max_discount_amount: PositiveMonetaryAmount;
  discount_percent: Percent;
};

/**
 * Gives a percentage off discount up to a maximum discount amount
 *
 * @param {DiscountCalculatorConfiguration} config - The configuration containing max discount amount and discount percent
 * @param {DiscountableItem} item - The item to apply discounts to
 * @returns {DiscountResult} The discounts applied to the item
 */
const percentOffUptoMaxDiscount: ComputeDiscountsFunction<
  DiscountCalculatorConfiguration
> = (
  run_context: RunContext,
  config: DiscountCalculatorConfiguration,
  item: DiscountableItem,
): DiscountResult => {
  const {max_discount_amount, discount_percent} = config;
  let discountAmount = 0;

  if (
    item.gross_amount.currency.toLowerCase().trim() ===
    max_discount_amount.currency.toLowerCase().trim()
  ) {
    const discountAmountValue =
      (item.gross_amount.amount * discount_percent) / 100;
    discountAmount = Math.min(discountAmountValue, max_discount_amount.amount);
  }

  return {
    discount: {
      amount: {
        amount: discountAmount,
        currency: item.gross_amount.currency,
      },
    },
  };
};

const computePercentOffUptoMaxDiscount: DiscountCalculation<
  DiscountCalculatorConfiguration
> = {
  computeDiscounts: percentOffUptoMaxDiscount,
};

export default computePercentOffUptoMaxDiscount;
```

## Test your script 

We recommend that you test Stripe-authored logic to get familiarized with the workflow. If you have access to the private preview, we load your custom logic into a sandbox, where you can go through the necessary testing to verify that it’s working as expected. After you validate it in your testing environment, Stripe uploads your script to your production account.

### Submit your script for review and testing 

You can access our public [SDK](https://github.com/stripe/scripts) with tools for authoring, testing, and packaging scripts. You can submit your packaged scripts to Stripe for review.

Upon approval, we’ll import your custom logic into a sandbox of your choice or your test mode. Once you verify that it’s working as expected, we’ll apply it to your production environment.

## Data validation requirements

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

- Return a `DiscountResult` with `discount.amount` containing both `amount` (number) and `currency` (string) on every invocation.
- `amount` must be non-negative.
- `currency` must match the invoice currency.

## Best practices 

- Perform a currency check before computing the discount. If currencies don’t match, return a zero discount.
- Use Stripe-provided SDK types like `PositiveMonetaryAmount` and `Percent` in your configuration for built-in validation.
- Test invoices with multiple line items to confirm the discount applies correctly against the gross amount.
