# Definition der Skriptsprache

Erfahren Sie mehr über die Syntax und Semantik der in Stripe Billing verwendeten Skriptsprache.

The Stripe scripting language is a subset of [TypeScript](https://www.typescriptlang.org/). While it maintains TypeScript’s features, it has some key differences including a [more restrictive set of allowed operations](https://docs.stripe.com/billing/scripts/script-language.md#static-script-analysis). The scripting language can help prevent potential issues such as long runtimes and excessive memory consumption.

## Syntax

### Basistypen und Literale

Die Sprache unterstützt die folgenden primitiven Typen:

```typescript
// 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;
```

### Variablen und Erklärungen

Deklarieren Sie Variablen mit `let` oder `const`:

```typescript
// Variable declarations
let count: number = 0;
let name: string = "John";

// Constants (must be initialized)
const PI: number = 3.14159;
const API_KEY: string = "abcd1234";
```

Typanmerkungen sind optional, werden aber empfohlen:

```typescript
// Type inferred as number
let score = 100;

// Explicitly typed
let highScore: number = 0;
```

### Funktionen und Lambdas

Definieren Sie Funktionen auf verschiedene Weise:

```typescript
// 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;
```

### Objekte und Arrays

Definieren Sie Objekte und Arrays ähnlich wie TypeScript:

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

### Ablaufsteuerung

Zu den Ablaufsteuerungsanweisungen gehören if-else-, while- und for-Schleifen:

```typescript
// 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-Anweisungen funktionieren wie erwartet:

```typescript
// Break in a loop
let i = 0;
while (true) {
  if (i >= 5) {
    break;
  }
  i++;
}
```

### Operatoren

Nachfolgend finden Sie unterstützte Standardoperatoren:

```typescript
// 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 und Export

Organisieren Sie Ihren Code in Modulen:

```typescript
// Importing modules
import { DiscountableItem } from '@stripe/scripts/discounts';

// Export a function
export function calculateTotal(items: DiscountableItem[]): number {
  let sum = 0;
  for (let i = 0; i < items.length; i++) {
    sum += items[i].price;
  }
  return sum;
}

// Default export
export default function main() {
  // Main function logic
}
```

Wir unterstützen keine Bibliotheken von Drittanbietern.

## Typsystem

### Typanmerkungen

Geben Sie Typen nach einem Doppelpunkt an:

```typescript
let count: number = 0;
let name: string = "John";
let active: boolean = true;
let items: string[] = ["apple", "banana"];
```

### Schnittstellen und Objekttypen

Definieren Sie Objekttypen inline oder als Schnittstellen:

```typescript
// 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
};
```

### Unionstypen

Unionstypen ermöglichen es einer Variablen, mehrere Typen zu haben:

```typescript
// Union type
let id: string | number;

id = "abc123";  // Valid
id = 123;       // Also valid
```

### Typerklärungen

Erstellen von Typ-Aliassen:

```typescript
// 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;
};
```

## Statische Analyse

Unsere Skriptsprache beinhaltet statische Analysen, um sicherzustellen, dass Skripte zuverlässig und effizient sind.

### Analyse der Beendigung

A key feature is termination checking, which guarantees that scripts always terminate when you run them on Stripe 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.

Der Analyzer verwendet ein Färbesystem:

- **T (Termination&nbsp;–&nbsp;Beendigung)**: Code, der nachweislich beendet wird.
- **U (Unbekannt):** Code, der möglicherweise nicht beendet wird.

```typescript
// 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");
  }
}
```

#### Fundierte Rekursion

Der Analyzer prüft, ob rekursive Funktionen ein abnehmendes Maß haben:

```typescript
// 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
}
```

### Häufige statische Analysemuster

For-Schleifen mit endlichen Grenzen sind sicher:

```typescript
// Safe loop pattern - marked as T
for (let i = 0; i < array.length; i++) {
  // Loop body with terminating operations
}
```

While-Schleifen erfordern einen abnehmenden Zähler:

```typescript
// Safe while loop - marked as T
let counter = 10;
while (counter > 0) {
  // Do something
  counter--;  // Counter decreases
}
```

### Schreiben von beendigungssicherem Code

Befolgen Sie diese Richtlinien, um sicherzustellen, dass Ihr Code die Beendigungsprüfung besteht:

- Für Schleifen mit klaren, endlichen Grenzen verwenden
- Stellen Sie sicher, dass while-Schleifen einen abnehmenden Zähler oder eine abnehmende Bedingung haben, die schließlich `false` wird
- Aufnahme eines Basisfalls und eines abnehmenden Arguments in rekursive Funktionen
- Vermeiden Sie komplexe Schleifenbedingungen, wechselseitige Rekursion oder tiefe Verschachtelung von Funktionen und Schleifen

```typescript
// Good pattern for loops
function processItems(items: any[]): void {
  for (let i = 0; i < items.length; i++) {
    processItem(items[i]);
  }
}
```

## Laufzeitumgebung

Die Skriptsprache stellt integrierte Objekte bereit, die Sie in Ihren Skripten verwenden können.

### Integrierte Objekte

Es stehen mehrere integrierte Objekte zur Verfügung:

#### Mathematisches Objekt

```typescript
// 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

```typescript
// 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);
```

### Zeichenfolgenvorgänge

```typescript
// String concatenation
let firstName = "John";
let lastName = "Doe";
let fullName = firstName + " " + lastName;  // "John Doe"

// String with template literals
let greeting = `Hello, ${firstName}!`;  // "Hello, John!"
```

## Schemavalidierung

Sie können Ihre Typen mit Anmerkungen versehen, um eine grundlegende Validierung von Feldern bereitzustellen. Verwenden Sie diese Option, wenn Sie das Konfigurationsschema für Ihre Skripts definieren.

### Zeichenfolgenvalidierungen

```typescript
/** @minLength 0 @maxLength 10 */
type Greeting = string;
```

```typescript
/** @pattern ^hello world$ */
type Greeting = string;
```

### Validierungen von Zahlen

```typescript
/** @minimum 0 @maximum 100 */
type Percent = number;
```

```typescript
/** @exclusiveMinimum -1 @exclusiveMaximum 101 */
type Percent = number;
```

### Array-Validierungen

```typescript
/** @minItems 1 @maxItems 5 */
type Names = string[];
```

```typescript
/** @uniqueItems */
type Names = string[];
```

Wenn wir Ihr Anwendungsszenario für die Validierung nicht unterstützen, empfehlen wir, die Validierungsprüfung in Ihre Funktionsdefinition zu integrieren und [scripts-preview@stripe.com](mailto:scripts-preview@stripe.com) zu kontaktieren.

## Beispiele

### Prozentuale Rabattfunktion

Dieses Beispiel zeigt eine Rabattfunktion, die einen prozentualen Rabatt bis zu einem Höchstbetrag gewährt:

```typescript
/**
 * Max Amount Percent Off Discount
 *
 * This discount function applies a percentage discount to the gross amount,
 * but caps the total discount at a maximum amount. It calculates the discount
 * as a percentage of the gross amount and then ensures it doesn't exceed the
 * configured maximum.
 *
 * Configuration:
 * - max_amount: The maximum monetary amount that can be discounted
 * - percent: The percentage discount to apply
 */
import { MonetaryAmount, Percent, RunContext } from '@stripe/scripts';
import {
  ComputeDiscountsFunction,
  DiscountCalculation,
  DiscountableItem,
  DiscountResult,
} from '@stripe/scripts/discounts';

type Configuration = {
  max_amount: MonetaryAmount;
  percent: Percent;
};

const maxAmountPercentOff: ComputeDiscountsFunction<Configuration> = (
  context: RunContext,
  configuration: Configuration,
  item: DiscountableItem,
): DiscountResult => {
  const { max_amount, percent } = configuration;
  const discount_amount = Math.min(max_amount.amount, item.gross_amount.amount * (percent / 100));

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

const computeMaxAmountPercentOff: DiscountCalculation<Configuration> = {
  computeDiscounts: maxAmountPercentOff,
};

export default computeMaxAmountPercentOff;
```

### Gestaffelter Rabatt je nach Menge

Dieses Beispiel zeigt einen Rabatt, für den je nach Menge unterschiedliche Tarife gelten:

```typescript
/**
 * Tiered Discount
 *
 * This discount function applies percentage discounts based on quantity tiers.
 * It sums the quantities across all line items and applies the discount percentage
 * from the highest applicable tier. The discount is calculated on each line item's
 * subtotal amount.
 *
 * Configuration:
 * - tiers: Array of objects with minimum_quantity and discount_percent
 *   The tiers are sorted by minimum_quantity in descending order to find
 *   the highest applicable tier.
 */
import type {
  ComputeDiscountsFunction,
  DiscountableItem,
  DiscountResult,
  DiscountCalculation,
} from '@stripe/scripts/discounts';
import type {RunContext} from '@stripe/scripts';

/**
 * Configuration for the discount calculator function
 */
export type TieredPercentOffDiscountConfiguration = {
  currency: string;
  tier_1_minimum_spend_amount: number;
  tier_1_discount_percent: number;
  tier_2_minimum_spend_amount: number;
  tier_2_discount_percent: number;
};

/**
 * Gives a percentage off based on minimum spend amount.
 * It is assumed tier1 amount < tier2 amount
 *
 * @param {TieredPercentOffDiscountConfiguration} configuration - The config containing tier specifications
 * @param {DiscountableItem} item - The items to apply discounts to
 * @returns {DiscountResult} - The discounts applied to the items
 */
const tieredPercentOffDiscountCalculator: ComputeDiscountsFunction<
  TieredPercentOffDiscountConfiguration
> = (
  context: RunContext,
  configuration: TieredPercentOffDiscountConfiguration,
  discountable_item: DiscountableItem,
): DiscountResult => {
  const {
    currency,
    tier_1_minimum_spend_amount,
    tier_1_discount_percent,
    tier_2_minimum_spend_amount,
    tier_2_discount_percent,
  } = configuration;
  let discountAmount = 0;
  let discountPercent = 0;
  const invoiceTotal = discountable_item.gross_amount.amount;
  if (
    discountable_item.gross_amount.currency.toLowerCase().trim() ===
    currency.toLowerCase().trim()
  ) {
    // Get discount percent based on gross amount
    switch (true) {
      case invoiceTotal >= tier_2_minimum_spend_amount:
        discountPercent = tier_2_discount_percent;
        break;
      case invoiceTotal >= tier_1_minimum_spend_amount:
        discountPercent = tier_1_discount_percent;
        break;
      default:
        break;
    }
    discountAmount = (invoiceTotal * discountPercent) / 100;
  }
  return {
    discount: {
      amount: {
        amount: discountAmount,
        currency: discountable_item.gross_amount.currency,
      },
    },
  };
};

const computeTieredPercentOffDiscountCalculator: DiscountCalculation<TieredPercentOffDiscountConfiguration> =
  {
    computeDiscounts: tieredPercentOffDiscountCalculator,
  };

export default computeTieredPercentOffDiscountCalculator;
```

## Tipps zur Fehlerbehebung

### Häufige Fehlermuster

Wenn die statische Analyse bei Ihrem Skript fehlschlägt, überprüfen Sie die folgenden häufigen Probleme:

1. **Endlosschleifen**: Schleifen ohne klare Beendigungsbedingung

   ```typescript
   // Problem: No clear exit condition
   while (x > 0) {
     doSomething(); // x never changes
   }
   
   // Fix: Add a decreasing counter
   while (x > 0) {
     doSomething();
     x--;
   }
   ```

2. **Nicht beendende Rekursion**: Rekursive Aufrufe ohne abnehmendes Maß

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