Définition du langage de scriptVersion bêta privée
Découvrez la syntaxe et la sémantique du langage de script utilisé dans Stripe Billing.
Le langage de script de Stripe est un sous-ensemble de TypeScript. Bien qu’il conserve les fonctionnalités de TypeScript, il présente quelques différences majeures, notamment un ensemble plus restrictif d’opérations autorisées. Le langage de script peut aider à éviter les problèmes potentiels tels que les longues durées d’exécution et la consommation excessive de mémoire.
Syntaxe
Types de base et littéraux
Le langage prend en charge les types primitifs suivants :
// 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 et déclarations
Déclarez des variables à l’aide de let
ou 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";
Les annotations de type sont facultatives, mais recommandées :
// Type inferred as number let score = 100; // Explicitly typed let highScore: number = 0;
Fonctions et lambdas
Définissez des fonctions de plusieurs façons :
// 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;
Objets et tableaux
Définissez des objets et des tableaux de la même manière que 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];
Flux de contrôle
Les instructions de flux de contrôle incluent les boucles if-else, while et for :
// 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); }
Les instructions de rupture fonctionnent comme prévu :
// Break in a loop let i = 0; while (true) { if (i >= 5) { break; } i++; }
Opérateurs
Vous trouverez ci-dessous les opérateurs standard pris en charge :
// 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
Importation et exportation
Organisez votre code en modules :
// 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 }
Nous ne prenons pas en charge les bibliothèques tierces.
Système de type
Annotations de type
Spécifiez les types après les deux-points :
let count: number = 0; let name: string = "John"; let active: boolean = true; let items: string[] = ["apple", "banana"];
Interfaces et objets de type
Définissez les objets de type en ligne ou en tant qu’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 };
Unions de types
Les unions de types permettent à une variable d’avoir plusieurs types :
// Union type let id: string | number; id = "abc123"; // Valid id = 123; // Also valid
Déclarations de types
Créez des alias de type :
// 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; };
Analyse statique
Notre langage de script comprend des fonctionnalités d’analyse statique pour garantir la fiabilité et l’efficacité des scripts.
Analyse de la fin
L’une des principales fonctionnalités est la vérification de fin, qui garantit que les scripts s’arrêtent toujours lorsque vous les exécutez sur l’infrastructure de Stripe, empêchant ainsi les boucles infinies ou la récursivité. Étant donné qu’il n’est pas possible de prouver que tous les codes de fin se terminent, nous rejetons certains programmes valides. Vous trouverez ci-dessous des conseils pour écrire des scripts de fin.
L’analyseur utilise un système de couleurs :
- **T (résiliation) **: Code dont il est prouvé qu’il prend fin.
- **U (inconnu) **: Code qui peut ne pas se terminer.
// 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"); } }
Récursivité fondée
L’analyseur vérifie que les fonctions récursives ont une mesure décroissante :
// 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 }
Modèles d’analyse statique courants
Les boucles For à limites définies sont sûres :
// Safe loop pattern - marked as T for (let i = 0; i < array.length; i++) { // Loop body with terminating operations }
Alors que les boucles ont besoin d’un compteur décroissant :
// Safe while loop - marked as T let counter = 10; while (counter > 0) { // Do something counter--; // Counter decreases }
Écriture d’un code de fin sécurisé
Pour vous assurer que votre code passe l’étape de vérification de fin, suivez ces instructions :
- Utilisez les boucles For avec des limites claires et définies
- Assurez-vous que les boucles while ont un compteur décroissant ou une condition qui finit par devenir
false
- Inclure un cas de base et un argument décroissant dans les fonctions récursives
- Évitez les conditions de boucle complexes, la récursivité mutuelle ou l’imbrication profonde de fonctions et de boucles
// Good pattern for loops function processItems(items: any[]): void { for (let i = 0; i < items.length; i++) { processItem(items[i]); } }
Environnement d’exécution
Le langage de script fournit des objets intégrés que vous pouvez utiliser dans vos scripts.
Objets intégrés
Plusieurs objets intégrés sont disponibles :
Objet mathématique
// 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
Tableaux
// 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);
Opérations sur les chaînes
// String concatenation let firstName = "John"; let lastName = "Doe"; let fullName = firstName + " " + lastName; // "John Doe" // String with template literals let greeting = `Hello, ${firstName}!`; // "Hello, John!"
Exemples
Fonction de réduction en pourcentage
Cet exemple montre une fonction de remise qui donne un pourcentage de réduction jusqu’à un montant maximum :
/** * 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 } from '@stripe/scripts'; import { DiscountFunction, DiscountableItem, DiscountResult, } from '@stripe/scripts/discounts'; type Configuration = { max_amount: MonetaryAmount; percent: Percent; }; const maxAmountPercentOff: DiscountFunction<Configuration> = ( 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, }, status: discount_amount > 0 ? 'APPLIED' : 'NOT_APPLIED', reason: discount_amount > 0 ? 'Discount applied' : 'No discount applied', }, line_item_discounts: [], }; }; export default maxAmountPercentOff;
Réduction échelonnée en fonction de la quantité
Cet exemple montre une réduction qui applique différents tarifs en fonction de la quantité :
/** * 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 { DiscountFunction, DiscountableItem, DiscountResult, ItemDiscount, } from '@stripe/scripts/discounts'; type Configuration = { tiers: Array<{ minimum_quantity: number; discount_percent: number; }>; }; const tieredDiscount: DiscountFunction<Configuration> = ( configuration: Configuration, item: DiscountableItem, ): DiscountResult => { let discountPercent = 0; let totalQuantity = 0; for (let i = 0; i < item.line_items.length; i++) { totalQuantity += item.line_items[i]?.quantity ?? 0; } const sortedTiers = [...configuration.tiers].sort( (a, b) => b.minimum_quantity - a.minimum_quantity, ); for (let i = 0; i < sortedTiers.length; i++) { const tier = sortedTiers[i]; if (totalQuantity >= tier.minimum_quantity) { discountPercent = tier.discount_percent; break; // Stop after finding the first applicable tier } } let totalDiscountAmount = 0; const lineItemDiscounts: ItemDiscount[] = []; for (let i = 0; i < item.line_items.length; i++) { const lineItem = item.line_items[i]; const lineItemDiscount = lineItem.subtotal.amount * (discountPercent / 100); totalDiscountAmount += lineItemDiscount; lineItemDiscounts.push({ discountable_item_id: lineItem.id, discount: { amount: { amount: lineItemDiscount, currency: lineItem.subtotal.currency, }, status: lineItemDiscount > 0 ? 'APPLIED' : 'NOT_APPLIED', reason: lineItemDiscount > 0 ? `${discountPercent}% tier discount applied` : 'No discount applied', }, }); } return { discount: { amount: { amount: totalDiscountAmount, currency: item.gross_amount.currency, }, status: totalDiscountAmount > 0 ? 'APPLIED' : 'NOT_APPLIED', reason: totalDiscountAmount > 0 ? `${discountPercent}% tier discount applied` : 'No discount applied', }, line_item_discounts: lineItemDiscounts, }; }; export default tieredDiscount;
Conseils de débogage
Modèles d’erreurs courants
Si votre script échoue à l’analyse statique, recherchez les problèmes courants suivants :
**Boucles infinies **: boucles sans condition de fin claire
// Problem: No clear exit condition while (x > 0) { doSomething(); // x never changes } // Fix: Add a decreasing counter while (x > 0) { doSomething(); x--; }
**Récursivité sans fin **: appels récursifs sans mesure décroissante
// 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); }