Private preview
Script language definition Private preview
Learn about the syntax and semantics of the script language used in Stripe Billing.
Stripe’s scripting language is a subset of TypeScript. While it maintains TypeScript’s features, it has some key differences including a more restrictive set of allowed operations. The scripting language can help prevent potential issues such as long runtimes and excessive memory consumption.
Syntax
Basic types and literals
The language supports these primitive types:
// Numeric values
let num: number = 42;
let pi: number = 3.14159;
// Strings
let greeting: string = "Hello, world!";
let multiline: string = `This is a
multi-line string`;
// Booleans
let active: boolean = true;
let completed: boolean = false;
// Null
let empty: null = null;
Variables and declarations
Declare variables using let or const:
// Variable declarations
let count: number = 0;
let name: string = "John";
// Constants (must be initialized)
const PI: number = 3.14159;
const API_KEY: string = "abcd1234";
Type annotations are optional but recommended:
// Type inferred as number
let score = 100;
// Explicitly typed
let highScore: number = 0;
Functions and lambdas
Define functions in multiple ways:
// Function declaration
function add(a: number, b: number): number {
return a + b;
}
// Arrow function (lambda)
const multiply = (a: number, b: number): number => {
return a * b;
};
// Inline arrow function
const double = (x: number): number => x * 2;
Objects and arrays
Define objects and arrays similar to TypeScript:
// Object literal
let person = {
name: "Alice",
age: 30,
address: {
city: "Wonderland",
zipCode: "12345"
}
};
// Array literal
let numbers = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
// Array spread
let moreNumbers = [...numbers, 6, 7, 8, 9, 10];
// Accessing properties
let cityName = person.address.city;
let firstNumber = numbers[0];
Control flow
Control flow statements include if-else, while, and for loops:
// If-else statement
if (score > 100) {
console.log("High score!");
} else if (score > 50) {
console.log("Good score!");
} else {
console.log("Try again!");
}
// While loop
let i = 0;
while (i < 5) {
console.log(i);
i++;
}
// For loop
for (let j = 0; j < 5; j++) {
console.log(j);
}
Break statements work as expected:
// Break in a loop
let i = 0;
while (true) {
if (i >= 5) {
break;
}
i++;
}
Operators
You can find standard supported operators below:
// Arithmetic operators
let sum = 5 + 3;
let difference = 10 - 4;
let product = 3 * 7;
let quotient = 20 / 4;
let remainder = 10 % 3; // Modulo
// Comparison operators
let equal = (5 == 5);
let notEqual = (5 != 3);
let greater = (10 > 5);
let less = (5 < 10);
let greaterOrEqual = (5 >= 5);
let lessOrEqual = (4 <= 4);
// Logical operators
let and = (true && false); // false
let or = (true || false); // true
let not = !true; // false
// Compound assignment
let value = 5;
value += 3; // value is now 8
Import and export
Organize your code into modules:
We don’t support third-party libraries.
Type system
Type annotations
Specify types after a colon:
let count: number = 0;
let name: string = "John";
let active: boolean = true;
let items: string[] = ["apple", "banana"];
Interfaces and object types
Define object types inline or as interfaces:
// Inline object type
let person: { name: string; age: number } = {
name: "Alice",
age: 30
};
// Interface definition
interface Product {
id: string;
name: string;
price: number;
inStock?: boolean; // Optional property
}
// Using the interface
let laptop: Product = {
id: "lt-001",
name: "Laptop",
price: 999.99,
inStock: true
};
Union types
Union types allow a variable to have multiple types:
// Union type
let id: string | number;
id = "abc123"; // Valid
id = 123; // Also valid
Type declarations
Create type aliases:
// Type alias
type ID = string | number;
// Using the type alias
let userId: ID = "user123";
let productId: ID = 456;
// Complex type declaration
type ApiResponse = {
status: number;
data: {
items: any[];
count: number;
};
error?: string;
};
Static analysis
Our script language includes static analysis to make sure scripts are reliable and efficient.
Termination analysis
A key feature is termination checking, which guarantees that scripts always terminate when you run them on Stripe’s infrastructure, preventing infinite loops or recursion. Because not all terminating code can be proven to terminate, we reject some valid programs. Below are tips for writing terminating scripts.
The analyzer uses a coloring system:
- T (Terminating) : Code that is proven to terminate.
- U (Unknown) : Code that might not terminate.
// Guaranteed to terminate - marked as T
function countdown(n: number): void {
while (n > 0) {
n = n - 1; // Decreasing counter
}
}
// Not guaranteed to terminate - marked as U
function infinite(): void {
while (true) {
console.log("This runs forever");
}
}
Well-founded recursion
The analyzer checks that recursive functions have a decreasing measure:
// Safe recursion - marked as T
function factorial(n: number): number {
if (n <= 1) return 1;
return n * factorial(n - 1); // n decreases with each call
}
// Unsafe recursion - marked as U
function badRecursion(n: number): number {
return badRecursion(n + 1); // n increases, no termination
}
Common static analysis patterns
For loops with finite bounds are safe:
// Safe loop pattern - marked as T
for (let i = 0; i < array.length; i++) {
// Loop body with terminating operations
}
While loops need a decreasing counter:
// Safe while loop - marked as T
let counter = 10;
while (counter > 0) {
// Do something
counter--; // Counter decreases
}
Writing termination-safe code
To make sure your code passes the termination checker, follow these guidelines:
- Use for loops with clear, finite bounds
- Make sure while loops have a decreasing counter or condition that eventually becomes false
- Include a base case and decreasing argument in recursive functions
- Avoid complex loop conditions, mutual recursion, or deep nesting of functions and loops
// Good pattern for loops
function processItems(items: any[]): void {
for (let i = 0; i < items.length; i++) {
processItem(items[i]);
}
}
Runtime environment
The scripting language provides built-in objects that you can use in your scripts.
Built-in objects
Several built-in objects are available:
Math object
// Math operations
let min = Math.min(5, 3, 7); // 3
let max = Math.max(5, 3, 7); // 7
let floor = Math.floor(3.7); // 3
let ceil = Math.ceil(3.2); // 4
Arrays
// Array creation
let numbers = [1, 2, 3, 4, 5];
// Array properties
let length = numbers.length; // 5
// Array indexing
let firstItem = numbers[0]; // 1
let lastItem = numbers[numbers.length - 1]; // 5
// Array methods
let sorted = numbers.sort((a, b) => a - b);
String operations
// String concatenation
let firstName = "John";
let lastName = "Doe";
let fullName = firstName + " " + lastName; // "John Doe"
// String with template literals
let greeting = `Hello, ${firstName}!`; // "Hello, John!"
Schema validation
You can annotate your types to provide basic validation for fields. Use this when you define the configuration schema for your scripts.
String validations
length_validation.ts
/** @minLength 0 @maxLength 10 */
type Greeting = string;
pattern_validation.ts
/** @pattern ^hello world$ */
type Greeting = string;
Number validations
inclusive_range_validation.ts
/** @minimum 0 @maximum 100 */
type Percent = number;
exclusive_range_validation.ts
/** @exclusiveMinimum -1 @exclusiveMaximum 101 */
type Percent = number;
Array validations
length_inclusive_validation.ts
/** @minItems 1 @maxItems 5 */
type Names = string[];
uniqueness_validation.ts
/** @uniqueItems */
type Names = string[];
If we don’t support your validation use case, we recommend building the validation check into your function definition and contacting .
Examples
Percentage-off discount function
This example shows a discount function that gives a percentage off up to a maximum amount:
max_amount_percent_off.ts
Tiered discount based on quantity
This example shows a discount that applies different rates based on quantity:
tiered_discount.ts
Debugging tips
Common error patterns
If your script fails static analysis, check for these common issues:
- Infinite loops: Loops without a clear termination condition
// Problem: No clear exit condition while (x > 0) { doSomething(); // x never changes } // Fix: Add a decreasing counter while (x > 0) { doSomething(); x--; } - Non-terminating recursion: Recursive calls without a decreasing measure
// Problem: No decreasing measure function process(data: any): void { process(transformData(data)); } // Fix: Add a depth limit and decreasing measure function process(data: any, depth: number = 10): void { if (depth <= 0) return; process(transformData(data), depth - 1); }
