Extend Stripe Billing with custom discount logicPrivate preview
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.
You can use these scripts as a foundation to build your own logic using our scripting language. We recommend starting in a sandbox.
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. We don’t support scripts in test mode. For any testing, you must use a sandbox environment.
Create a coupon with no code 
You can create a coupon with Stripe-authored custom logic by navigating to the create a coupon page, 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_ | See script definition |
Configuration fields
|
For example, configuring a 20% off up to 100 USD coupon using Percentage off up to maximum
looks like this:
Apply a coupon 
After creating the coupon, apply it to subscriptions or invoices following the same process as for a standard coupon. See coupons 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 coupons.
- You can’t use script coupons on Quote objects.
- Script coupons aren’t supported in
payment
mode for Checkout Sessions. - Script coupons are only supported in API versions later than
2025-03-31.
.preview
Author a script 
To meet specific business needs, you can create your own custom logic using our scripting language.
Note
You’re responsible for checking that your script reflects your desired functionality. Make sure not to enter any proprietary, confidential information (for example, PII), or malicious code.
Discount calculator interface 
All coupon scripts must abide by the discount calculator interface defined by Stripe.
export type DiscountFunction<T> = ( configuration: T, discountableItem: 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.
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. We provide your script with the following information:
export interface DiscountableItem { id: string; gross_amount: MonetaryAmount; line_items: Array<DiscountableLineItem>; } export interface DiscountableLineItem { id: string; subtotal: MonetaryAmount; price_id?: string; is_free_trial: boolean; is_recurring: boolean; quantity?: number; unit_amount?: MonetaryAmount; period?: TimeRange; }
DiscountResult
Your script must return a discount result.
export type DiscountStatus = 'APPLIED' | 'NOT_APPLIED'; export type Discount = { amount: MonetaryAmount; status: DiscountStatus; reason?: string; }; export type ItemDiscount = { discountable_item_id: string; discount: Discount; }; export type DiscountResult = { discount?: Discount; line_item_discounts: Array<ItemDiscount>; };
Note
line_
are discounts applied to the invoice line items and don’t roll up to the top-level discount
. The top-level discount
is the discount applied to the invoice independent of the line_
s.
Example of returning a discount result:
return { discount: { amount: { amount: discount_amount, currency: item.gross_amount.currency, }, status: discount_amount > 0 ? 'APPLIED' : 'NOT_APPLIED', reason: discount_amount > 0 ? 'Discount applied' : 'No discount applied', }, line_item_discounts: [], };
Best practices 
- Use Stripe defined types in your configuration where possible (for example,
MonetaryAmount
) - Perform a currency check before applying your discount.
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 
import {MonetaryAmount, Percent} from '@stripe/scripts'; import { DiscountCalculatorFunction, DiscountableItem, DiscountResult, } from '@stripe/scripts/discounts'; /** * Configuration for the discount calculator function */ export type DiscountCalculatorConfiguration = { max_discount_amount: MonetaryAmount; discount_percent: Percent; }; /** * Gives a percentage off discount upto a maximum discount amount * * @param {DiscountCalculatorConfiguration} config - The configuration containing max discount amount and discount percent * @param {DiscountableItem} item - The items to apply discounts to * @returns {DiscountResult} - The discounts applied to the items */ const percentOffUptoMaxDiscount: DiscountCalculatorFunction< DiscountCalculatorConfiguration > = ( 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, }, status: discountAmount > 0 ? 'APPLIED' : 'NOT_APPLIED', reason: discountAmount > 0 ? 'Discount applied' : 'No discount applied', }, line_item_discounts: [], }; }; export default percentOffUptoMaxDiscount;
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 on your account, where you can go through the necessary testing to verify that it’s working as expected. After you validate it in your sandbox environment, Stripe uploads your script to your production account.
Submit your script for review and testing 
After you’re accepted to the private preview, you receive access to an SDK 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 for testing before you apply it to your production environment.
Private-preview eligibility criteria 
This feature is in private preview and still under development. Functionality can change as we continue development of scripts. Be aware of the following considerations:
- You must use version
2025-03-31.
or later. This is the default version if you’re a new user, but you can upgrade if you’re an existing user.preview - You can only attach script coupons to new subscriptions or invoices.
- Checkout only supports the use of script-enabled promotion codes in
subscription
mode. - During the preview, you can’t distribute scripts to connected accounts in Connect.