# Créer une page d'abonnement préconfigurée avec Stripe Checkout # Page d’abonnement préconfigurée avec Stripe Checkout Commencez à utiliser notre exemple d’application pour exécuter une intégration d’abonnement complète et fonctionnelle à l’aide de [Stripe Billing](https://docs.stripe.com/billing.md) et [Stripe Checkout](https://docs.stripe.com/payments/checkout.md). L’application test montre comment rediriger vos clients depuis votre site vers une page de paiement prédéfinie hébergée sur Stripe. Les API Stripe Billing permettent de créer et de gérer des abonnements, des factures et des paiements récurrents, tandis que Checkout fournit l’interface utilisateur préconfigurée, sécurisée et hébergée par Stripe pour collecter les informations du paiement. Vous pouvez modéliser les clients dans votre intégration soit sous forme d’objets [Account configurés comme clients](https://docs.stripe.com/api/v2/core/accounts/create.md#v2_create_accounts-configuration-customer) à l’aide de l’API Accounts v2 (recommandé dans la plupart des cas), soit sous forme d’objets [Customer](https://docs.stripe.com/api/customers/object.md) à l’aide de l’API Customers v1. Pour en savoir plus sur les différences entre ces options, consultez la rubrique [Utiliser les comptes comme clients](https://docs.stripe.com/connect/use-accounts-as-customers.md). Cliquez après chaque étape pour voir l’exemple de code correspondant. Au fur et à mesure des étapes, par exemple lorsque vous ajoutez des données de tarification, l’outil met à jour l’exemple de code. Téléchargez et personnalisez l’application test localement pour tester votre intégration. ### Ajouter vos produits et tarifs Créez de nouveaux *produits* (Products represent what your business sells—whether that's a good or a service) and *tarifs* (Prices define how much and how often to charge for products. This includes how much the product costs, what currency to use, and the interval if the price is for subscriptions) à utiliser dans cet exemple. > Connectez-vous à votre compte Stripe pour configurer vos produits et tarifs. ### Ajouter des fonctionnalités à votre produit Créez des fonctionnalités, par exemple un cadeau d’anniversaire annuel, et associez-les à votre abonnement pour y [donner droit](https://docs.stripe.com/billing/entitlements.md) aux nouveaux abonnés. Écoutez les [événements récapitulatifs des droits actifs](https://docs.stripe.com/billing/entitlements.md#webhooks) pour votre [destination d’événement](https://docs.stripe.com/event-destinations.md) et utilisez l’[API List Active Entitlements](https://docs.stripe.com/api/entitlements/active-entitlement/list.md) pour un client donné afin de satisfaire aux droits de votre client. ### (Facultatif) Activer les moyens de paiement Utilisez votre [Dashboard](https://dashboard.stripe.com/settings/payment_methods) pour activer les [moyens de paiement pris en charge](https://docs.stripe.com/payments/payment-methods/payment-method-support.md) que vous souhaitez accepter en plus des cartes. Checkout affiche dynamiquement les moyens de paiement activés par ordre de pertinence, en fonction de la localisation du client et d’autres caractéristiques. ### Ajouter une page d’aperçu des tarifs Ajoutez à votre site une page qui affiche votre produit et permet à vos clients de s’y abonner. Lorsqu’il clique sur **Paiement**, le client est redirigé vers une page [Checkout](https://docs.stripe.com/payments/checkout.md) hébergée par Stripe, ce qui finalise la commande et empêche toute modification ultérieure. Envisagez d’intégrer une [grille tarification](https://docs.stripe.com/payments/checkout/pricing-table.md) pour afficher dynamiquement vos informations tarification via le Dashboard. En cliquant sur une option tarification, votre client est redirigé vers la page de paiement. ### Ajouter un bouton de paiement Le bouton de la page d’aperçu de votre commande redirige votre client vers la page de paiement hébergée par Stripe et utilise le `lookup_key` de votre produit pour récupérer le `price_id` sur le serveur. ### Ajouter une page de confirmation de paiement Créez une page de confirmation de commande pour présenter le message de confirmation de commande ou les détails de la commande à votre client. Associez cette page à la `success_url` de la session Checkout, vers laquelle Stripe redirige le client une fois qu’il a terminé le paiement. ### Ajouter un bouton destiné au portail client Ajoutez un bouton de redirection vers le portail client pour permettre aux clients de gérer leur abonnement. Lorsque les clients cliquent sur ce bouton, ils sont redirigés vers la page du portail client hébergée par Stripe. ### Rediriger vers la session du portail client Envoyez une requête à l’endpoint de votre serveur afin de créer une redirection vers une nouvelle session du portail client. Cet exemple utilise le `session_id` de la [session Checkout](https://docs.stripe.com/api/checkout/sessions/object.md#checkout_session_object-id) pour illustrer la récupération du `customer_id`. Dans un environnement de production, nous vous recommandons de sauvegarder cette valeur avec l’utilisateur authentifié dans votre base de données. ### Installer la bibliothèque Node de Stripe Installez le package et importez-le dans votre code. Si vous partez de zéro et qu’il vous faut un fichier package.json, vous pouvez également télécharger les fichiers du projet à l’aide du lien de téléchargement dans l’éditeur de code. #### npm Installez la bibliothèque : ```bash npm install --save stripe ``` #### GitHub Vous pouvez sinon télécharger le code source de la bibliothèque Node de Stripe directement [depuis GitHub](https://github.com/stripe/stripe-node). ### Installer la bibliothèque Ruby de Stripe Installez le gem Ruby de Stripe et exigez-le dans votre code. Si vous partez de zéro et avez besoin d’un Gemfile, téléchargez les fichiers du projet à l’aide du lien dans l’éditeur de code. #### Terminal Installez le gem : ```bash gem install stripe ``` #### Bundler Ajoutez cette ligne à votre Gemfile : ```bash gem 'stripe' ``` #### GitHub Vous pouvez sinon télécharger le code source du gem Ruby de Stripe directement [depuis GitHub](https://github.com/stripe/stripe-ruby). ### Installer la bibliothèque Java Ajoutez la dépendance à votre build et importez la bibliothèque. Ou bien, si vous partez de zéro et avez besoin d’un exemple de fichier pom.xml (pour Maven), téléchargez les fichiers du projet à l’aide du lien de téléchargement dans l’éditeur de code. #### Maven Ajoutez la dépendance suivante à votre POM et remplacez {VERSION} par le numéro de version que vous souhaitez utiliser. ```bash \ncom.stripe\nstripe-java\n{VERSION}\n ``` #### Gradle Ajoutez la dépendance à votre fichier build.gradle et remplacez {VERSION} par le numéro de version que vous souhaitez utiliser. ```bash implementation "com.stripe:stripe-java:{VERSION}" ``` #### GitHub Téléchargez le fichier JAR directement [depuis GitHub](https://github.com/stripe/stripe-java/releases/latest). ### Installer le package Python de Stripe Installez le package Stripe et importez-le dans votre code. Si vous partez de zéro et qu’il vous faut un fichier requirements.txt, téléchargez les fichiers du projet à l’aide du lien de téléchargement dans l’éditeur de code. #### pip Installez le package via pip : ```bash pip3 install stripe ``` #### GitHub Téléchargez le code source de la bibliothèque stripe-python directement [depuis GitHub](https://github.com/stripe/stripe-python). ### Installer la bibliothèque PHP Installez la bibliothèque avec composer et initialisez-la avec votre clé API secrète. Si vous partez de zéro et que vous avez besoin d’un fichier composer.json, vous pouvez également télécharger les fichiers à l’aide du lien de téléchargement dans l’éditeur de code. #### Composer Installez la bibliothèque : ```bash composer require stripe/stripe-php ``` #### GitHub Vous pouvez sinon télécharger le code source de la bibliothèque php de Stripe directement [depuis GitHub](https://github.com/stripe/stripe-php). ### Configurer votre serveur Ajoutez la dépendance à votre build et importez la bibliothèque. Si vous partez de zéro et avez besoin d’un fichier go.mod, téléchargez les fichiers du projet à l’aide du lien de téléchargement dans l’éditeur de code. #### Go Veillez à initialiser avec des modules Go : ```bash go get -u github.com/stripe/stripe-go/v85 ``` #### GitHub Vous pouvez sinon télécharger le code source de la bibliothèque Go de Stripe directement [depuis GitHub](https://github.com/stripe/stripe-go). ### Installer la bibliothèque Stripe.net Installez le package avec .NET ou NuGet. Si vous partez de zéro, vous pouvez également télécharger les fichiers qui contiennent un fichier .csproj configuré. #### .NET Installez la bibliothèque : ```bash dotnet add package Stripe.net ``` #### NuGet Installez la bibliothèque : ```bash Install-Package Stripe.net ``` #### GitHub Vous pouvez sinon télécharger le code source de la bibliothèque .NET de Stripe directement [depuis GitHub](https://github.com/stripe/stripe-dotnet). ### Installer les bibliothèques Stripe Installez les packages et importez-les dans votre code. Si vous partez de zéro et qu’il vous faut un fichier `package.json`, vous pouvez également télécharger les fichiers du projet à l’aide du lien de téléchargement dans l’éditeur de code. Installez les bibliothèques : ```bash npm install --save stripe @stripe/stripe-js next ``` ### Créer une session Checkout La [session Checkout](https://docs.stripe.com/api/checkout/sessions.md) détermine ce que votre client voit sur la page de paiement hébergée par Stripe, notamment les postes de factures, le montant et la devise de la commande, ainsi que les moyens de paiement acceptés. ### Obtenir le prix à partir de la clé de recherche Transmettez la clé de recherche que vous avez définie pour votre produit dans l’endpoint [Price](https://docs.stripe.com/api/prices/list.md) afin d’appliquer son tarif à la commande. ### Définir les postes de facture Conservez toujours sur votre serveur les informations sensibles relatives à l’inventaire de vos produits, notamment leur prix et leur disponibilité, pour éviter toute manipulation de la part du client. Transmettez l’ID du prix prédéfini récupéré ci-dessus. ### Définir le mode Définissez le mode sur `subscription`. Checkout prend également en charge les modes [paiement](https://docs.stripe.com/checkout/quickstart.md) et [configuration](https://docs.stripe.com/payments/save-and-reuse.md) pour les paiements non récurrents. ### URL de confirmation de fourniture Indiquez une URL accessible au public vers laquelle Stripe peut rediriger les clients après une opération réussie. Ajoutez le paramètre de requête `session_id` à la fin de votre URL afin de pouvoir récupérer le client ultérieurement et permettre à Stripe de générer le Dashboard hébergé du client. ### Rediriger depuis Checkout Après avoir créé la session, redirigez votre client vers l’URL renvoyée dans la réponse (l’URL de réussite ou d’annulation). ### Créer une session de portail client Lancez une [session de portail client](https://docs.stripe.com/api/customer_portal/sessions/create.md) sécurisée et hébergée par Stripe, qui permettra à vos clients de gérer leurs abonnements et leurs informations de facturation. ### Rediriger vers le portail client Après avoir créé la session du portail, redirigez votre client vers l’URL renvoyée dans la réponse. ### Traiter l’abonnement Créez un endpoint `/webhook` et obtenez votre clé secrète de webhook dans l’onglet [Webhooks](https://dashboard.stripe.com/webhooks) de Workbench pour écouter les événements liés à l’activité des abonnements. Après un paiement réussi et une redirection vers la page de confirmation, vérifiez que l’état de l’abonnement est `active` et accordez à votre client l’accès aux produits et fonctionnalités auxquels il s’est abonné. ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash npm start ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash npm start ``` ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash ruby server.rb ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash ruby server.rb ``` ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash python3 -m flask run --port=4242 ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash python3 -m flask run --port=4242 ``` ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash php -S 127.0.0.1:4242 --docroot=public ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash php -S 127.0.0.1:4242 --docroot=public ``` ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash dotnet run ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash dotnet run ``` ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash go run server.go ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash go run server.go ``` ### Exécuter le serveur Démarrez votre serveur et accédez à ```bash java -cp target/sample-jar-with-dependencies.jar com.stripe.sample.Server ``` ### Exécuter le serveur Démarrez votre serveur. Il ouvre automatiquement une fenêtre de navigateur à l’adresse ```bash java -cp target/sample-jar-with-dependencies.jar com.stripe.sample.Server ``` ### Faire un essai Cliquez sur le bouton de paiement. Sur la page de paiement hébergée par Stripe, utilisez l’une de ces cartes bancaires de test pour simuler un paiement. | Scenario | Card Number | | ----------------------------------- | ---------------- | | Payment succeeds | 4242424242424242 | | Payment requires 3DS authentication | 4000002500003155 | | Payment is declined | 4000000000009995 | ## Ajouter des fonctionnalités de personnalisation Si vous avez réussi à vous abonner à votre produit lors de votre test, votre intégration de paiement par abonnement est fonctionnelle. Utilisez les boutons ci-dessous pour voir comment personnaliser cet exemple à l’aide de fonctionnalités supplémentaires. ### Ajouter des périodes d’essai Associez une période d’essai à une session Checkout. ### Ajouter une période d’essai Utilisez `subscription_data` pour ajouter un nombre entier représentant le nombre de `trial_period_days` avant de débiter le client pour la première fois. La valeur doit être supérieure ou égale à `1`. Si vous démarrez un essai gratuit sans moyen de paiement, définissez `les paramètres_d’essai[end_behavior][missing_payment_method]` sur `suspendre` ou `annuler` afin que l’abonnement ne se poursuive pas si l’essai se termine sans moyen de paiement. Transmettez ce paramètre dans `abonnement_données` lorsque vous créez une Checkout Session , ou la mettez à jour sur l’abonnement à un autre moment. Consultez la page [Utilisez des périodes d’essai](https://docs.stripe.com/billing/subscriptions/trials/free-trials.md#create-free-trials-without-payment) pour en savoir plus. ### Définir la date de début de cycle de facturation Spécifiez une date de début du cycle de facturation lors de la création d’une session Checkout. ### Définir la date de début du cycle de facturation des abonnements Utilisez `subscription_data` afin de définir un horodatage `billing_cycle_anchor` pour la prochaine date de facturation d’un abonnement. Pour en savoir plus, consultez la page [Définir la date de début de cycle de facturation dans Checkout](https://docs.stripe.com/payments/checkout/billing-cycle.md). ### Automatiser la perception des taxes Calculez et percevez le montant de taxes approprié sur les transactions Stripe. En savoir plus sur [Stripe Tax](https://docs.stripe.com/tax.md) et la [manière de l’intégrer à Checkout](https://docs.stripe.com/tax/checkout.md). [Activez Stripe Tax](https://dashboard.stripe.com/tax) dans le Dashboard avant l’intégration. ### Ajouter le paramètre automatic_tax Définissez le paramètre `automatic_tax` sur `enabled: true`.

{{PRODUCT_NAME}}

{{FORMATTED_RECURRING_PRICE}}

Subscription to Starter plan successful!

Picked the wrong subscription? Shop around then come back to pay!

{{PRODUCT_NAME}}

{{FORMATTED_RECURRING_PRICE}}
{/* Add a hidden field with the lookup_key of your Price */}
{/* Add a hidden field with the lookup_key of your Price */}

Subscription to {{PRODUCT_NAME}} successful!

{ "name": "stripe-sample", "version": "0.1.0", "dependencies": { "@stripe/react-stripe-js": "^3.7.0", "@stripe/stripe-js": "^7.3.0", "express": "^4.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "^5.0.1", "stripe": "^8.202.0" }, "devDependencies": { "concurrently": "4.1.2" }, "homepage": "http://localhost:3000/checkout", "proxy": "http://127.0.0.1:4242", "scripts": { "start-client": "react-scripts start", "start-server": "node server.js", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "start": "concurrently \"yarn start-client\" \"yarn start-server\"" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } { "name": "client", "version": "0.1.0", "private": true, "dependencies": { "@stripe/react-stripe-js": "^3.7.0", "@stripe/stripe-js": "^7.3.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "^5.0.1" }, "homepage": "http://localhost:3000/checkout", "proxy": "http://127.0.0.1:4242", "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. const stripe = require('stripe')('<>'); const YOUR_DOMAIN = "http://localhost:4242"; const YOUR_DOMAIN = "http://localhost:3000"; const prices = await stripe.prices.list({ lookup_keys: [req.body.lookup_key], expand: ['data.product'], }); const session = await stripe.checkout.sessions.create({ billing_address_collection: 'auto', line_items: [ { price: prices.data[0].id, // For usage-based billing, don't pass quantity quantity: 1, }, ], mode: 'subscription', success_url: `${YOUR_DOMAIN}/success.html?session_id={CHECKOUT_SESSION_ID}`, success_url: `${YOUR_DOMAIN}/?success=true&session_id={CHECKOUT_SESSION_ID}`, discounts: [{ coupon: '{{COUPON_ID}}', }], customer: 'cus_123', customer_account: 'acct_123', subscription_data: { trial_period_days: 7, billing_cycle_anchor: 1672531200, }, subscription_data: { billing_cycle_anchor: 1672531200, }, automatic_tax: { enabled: true }, }); res.redirect(303, session.url); // This is the url to which the customer will be redirected when they're done // managing their billing with the portal. const returnUrl = YOUR_DOMAIN; const portalSession = await stripe.billingPortal.sessions.create({ customer: checkoutSession.customer, return_url: returnUrl, }); // This is the url to which the customer will be redirected when they're done // managing their billing with the portal. const returnUrl = YOUR_DOMAIN; const portalSession = await stripe.billingPortal.sessions.create({ customer_account: checkoutSession.customer_account, return_url: returnUrl, }); res.redirect(303, portalSession.url); app.post( '/webhook', express.raw({ type: 'application/json' }), (request, response) => { let event = request.body; // Replace this endpoint secret with your endpoint's unique secret // If you are testing with the CLI, find the secret by running 'stripe listen' // If you are using an endpoint defined with the API or dashboard, look in your webhook settings // at https://dashboard.stripe.com/webhooks const endpointSecret = 'whsec_12345'; // Only verify the event if you have an endpoint secret defined. // Otherwise use the basic event deserialized with JSON.parse if (endpointSecret) { // Get the signature sent by Stripe const signature = request.headers['stripe-signature']; try { event = stripe.webhooks.constructEvent( request.body, signature, endpointSecret ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`, err.message); return response.sendStatus(400); } } let subscription; let status; // Handle the event switch (event.type) { case 'customer.subscription.trial_will_end': subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription trial ending. // handleSubscriptionTrialEnding(subscription); break; case 'customer.subscription.deleted': subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription deleted. // handleSubscriptionDeleted(subscriptionDeleted); break; case 'customer.subscription.created': subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription created. // handleSubscriptionCreated(subscription); break; case 'customer.subscription.updated': subscription = event.data.object; status = subscription.status; console.log(`Subscription status is ${status}.`); // Then define and call a method to handle the subscription update. // handleSubscriptionUpdated(subscription); break; case 'entitlements.active_entitlement_summary.updated': subscription = event.data.object; console.log(`Active entitlement summary updated for ${subscription}.`); // Then define and call a method to handle active entitlement summary updated // handleEntitlementUpdated(subscription); break; default: // Unexpected event type console.log(`Unhandled event type ${event.type}.`); } // Return a 200 response to acknowledge receipt of the event response.send(); } ); { "name": "stripe-sample", "version": "1.0.0", "description": "A sample Stripe implementation", "main": "server.js", "scripts": { "start": "node server.js" }, "author": "stripe-samples", "license": "ISC", "dependencies": { "express": "^4.17.1", "stripe": "^21.0.1" } } { "name": "stripe-sample", "version": "0.1.0", "dependencies": { "@stripe/react-stripe-js": "^3.7.0", "@stripe/stripe-js": "^7.3.0", "express": "^4.17.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "^5.0.1", "stripe": "21.0.1" }, "devDependencies": { "concurrently": "4.1.2" }, "homepage": "http://localhost:3000/checkout", "proxy": "http://localhost:4242", "scripts": { "start-client": "react-scripts start", "start-server": "node server.js", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "start": "concurrently \"yarn start-client\" \"yarn start-server\"" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } } \# This is a public sample test API key. # Don’t submit any personally identifiable information in requests made with this key. # Sign in to see your own test API key embedded in code samples. \# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. client = Stripe::StripeClient.new('<>') prices = client.v1.prices.list( lookup_keys: [params['lookup_key']], expand: ['data.product'] ) session = client.v1.checkout.sessions.create({ mode: 'subscription', line_items: [{ quantity: 1, price: prices.data[0].id }], success_url: YOUR_DOMAIN + '/success.html?session_id={CHECKOUT_SESSION_ID}', success_url: YOUR_DOMAIN + '?success=true&session_id={CHECKOUT_SESSION_ID}', subscription_data: { trial_period_days: 7, billing_cycle_anchor: 1672531200 }, subscription_data: { billing_cycle_anchor: 1672531200 }, customer: 'cus_JyTTNqVDAoRYE1', customer_account: 'acct_123', discounts: [{ coupon: 'gBY6sFUf' }], automatic_tax: { enabled: true }, }) redirect session.url, 303 session = client.v1.billing_portal.sessions.create({ customer: checkout_session.customer, return_url: return_url }) session = client.v1.billing_portal.sessions.create({ customer_account: checkout_session.customer_account, return_url: return_url }) redirect session.url, 303 post '/webhook' do \# Replace this endpoint secret with your endpoint's unique secret # If you are testing with the CLI, find the secret by running 'stripe listen' # If you are using an endpoint defined with the API or dashboard, look in your webhook settings # at https://dashboard.stripe.com/webhooks webhook_secret = 'whsec_12345' payload = request.body.read if !webhook_secret.empty? # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = JSON.parse(payload, symbolize_names: true) event = Stripe::Event.construct_from(data) end # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] data = event['data'] data_object = data['object'] if event.type == 'customer.subscription.deleted' # handle subscription canceled automatically based # upon your subscription settings. Or if the user cancels it. # puts data_object puts "Subscription canceled: #{event.id}" end if event.type == 'customer.subscription.updated' # handle subscription updated # puts data_object puts "Subscription updated: #{event.id}" end if event.type == 'customer.subscription.created' # handle subscription created # puts data_object puts "Subscription created: #{event.id}" end if event.type == 'customer.subscription.trial_will_end' # handle subscription trial ending # puts data_object puts "Subscription trial will end: #{event.id}" end if event.type == 'entitlements.active_entitlement_summary.updated' # handle active entitlement summary updated # puts data_object puts "Active entitlement summary updated: #{event.id}" end content_type 'application/json' { status: 'success' }.to_json end import stripe \# This is a public sample test API key. # Don’t submit any personally identifiable information in requests made with this key. # Sign in to see your own test API key embedded in code samples. \# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. client = stripe.StripeClient('<>') try: prices = client.v1.prices.list(params={ 'lookup_keys': [request.form['lookup_key']], 'expand': ['data.product'], }) checkout_session = client.v1.checkout.sessions.create(params={ 'line_items': [ { 'price': prices.data[0].id, 'quantity': 1, }, ], 'mode': 'subscription', 'success_url': YOUR_DOMAIN + '/success.html?session_id={CHECKOUT_SESSION_ID}', 'success_url': YOUR_DOMAIN + '?success=true&session_id={CHECKOUT_SESSION_ID}', 'subscription_data': { 'trial_period_days': 7, 'billing_cycle_anchor': 1672531200, }, 'subscription_data': { 'billing_cycle_anchor': 1672531200, }, 'discounts': [ { 'coupon': '{{COUPON_ID}}' } ], 'customer': 'cus_123', 'customer_account': 'acct_123', 'automatic_tax': { 'enabled': True }, }) return redirect(checkout_session.url, code=303) portalSession = client.v1.billing_portal.sessions.create(params={ 'customer': checkout_session.customer, 'return_url': return_url, }) portalSession = stripe.billing_portal.Session.create( customer_account=checkout_session.customer_account, return_url=return_url, ) return redirect(portalSession.url, code=303) @app.route('/webhook', methods=['POST']) def webhook_received(): \# Replace this endpoint secret with your endpoint's unique secret # If you are testing with the CLI, find the secret by running 'stripe listen' # If you are using an endpoint defined with the API or dashboard, look in your webhook settings # at https://dashboard.stripe.com/webhooks webhook_secret = 'whsec_12345' request_data = json.loads(request.data) if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = client.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] data_object = data['object'] print('event ' + event_type) if event_type == 'checkout.session.completed': print('🔔 Payment succeeded!') elif event_type == 'customer.subscription.trial_will_end': print('Subscription trial will end') elif event_type == 'customer.subscription.created': print('Subscription created %s', event.id) elif event_type == 'customer.subscription.updated': print('Subscription created %s', event.id) elif event_type == 'customer.subscription.deleted': # handle subscription canceled automatically based # upon your subscription settings. Or if the user cancels it. print('Subscription canceled: %s', event.id) elif event_type == 'entitlements.active_entitlement_summary.updated': # handle active entitlement summary updated print('Active entitlement summary updated: %s', event.id) return jsonify({'status': 'success'}) certifi==2026.1.4 chardet==5.2.0 click==8.3.1 Flask==3.1.2 idna==3.11 itsdangerous==2.2.0 Jinja2==3.1.6 MarkupSafe==3.0.3 requests==2.32.5 stripe==15.0.0 toml==0.10.2 Werkzeug==3.1.5 $session = $stripe->billingPortal->sessions->create([ 'customer' => $checkout_session->customer, 'return_url' => $return_url, ]); $session = $stripe->billingPortal->sessions->create([ 'customer_account' => $checkout_session->customer_account, 'return_url' => $return_url, ]); header("HTTP/1.1 303 See Other"); header("Location: " . $session->url); // Replace this endpoint secret with your endpoint's unique secret // If you are testing with the CLI, find the secret by running 'stripe listen' // If you are using an endpoint defined with the API or dashboard, look in your webhook settings // at https://dashboard.stripe.com/webhooks $endpoint_secret = 'whsec_12345'; $payload = @file_get_contents('php://input'); $event = null; try { $event = \Stripe\Event::constructFrom( json_decode($payload, true) ); } catch(\UnexpectedValueException $e) { // Invalid payload echo '⚠️ Webhook error while parsing basic request.'; http_response_code(400); exit(); } // Handle the event switch ($event->type) { case 'customer.subscription.trial_will_end': $subscription = $event->data->object; // contains a \Stripe\Subscription // Then define and call a method to handle the trial ending. // handleTrialWillEnd($subscription); break; case 'customer.subscription.created': $subscription = $event->data->object; // contains a \Stripe\Subscription // Then define and call a method to handle the subscription being created. // handleSubscriptionCreated($subscription); break; case 'customer.subscription.deleted': $subscription = $event->data->object; // contains a \Stripe\Subscription // Then define and call a method to handle the subscription being deleted. // handleSubscriptionDeleted($subscription); break; case 'customer.subscription.updated': $subscription = $event->data->object; // contains a \Stripe\Subscription // Then define and call a method to handle the subscription being updated. // handleSubscriptionUpdated($subscription); break; case 'entitlements.active_entitlement_summary.updated': $subscription = $event->data->object; // contains a \Stripe\Subscription // Then define and call a method to handle active entitlement summary updated. // handleEntitlementUpdated($subscription); break; default: // Unexpected event type echo 'Received unknown event type'; } // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. $stripeSecretKey = '<>'; $stripe = new \Stripe\StripeClient($stripeSecretKey); $prices = $stripe->prices->all([ // retrieve lookup_key from form data POST body 'lookup_keys' => [$_POST['lookup_key']], 'expand' => ['data.product'] ]); $checkout_session = $stripe->checkout->sessions->create([ 'line_items' => [[ 'price' => $prices->data[0]->id, 'quantity' => 1, ]], 'mode' => 'subscription', 'success_url' => $YOUR_DOMAIN . '/success.html?session_id={CHECKOUT_SESSION_ID}', 'success_url' => $YOUR_DOMAIN . '?success=true&session_id={CHECKOUT_SESSION_ID}', 'subscription_data' => [ 'trial_period_days' => 7, 'billing_cycle_anchor' => 1672531200, ], 'subscription_data' => [ 'billing_cycle_anchor' => 1672531200, ], 'discounts' => [[ 'coupon' => '{{COUPON_ID}}', ]], 'customer' => 'cus_123', 'automatic_tax' => [ 'enabled' => true, ], ]); header("HTTP/1.1 303 See Other"); header("Location: " . $checkout_session->url); // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. services.AddSingleton(new StripeClient("<>")); var priceOptions = new PriceListOptions { LookupKeys = new List { Request.Form["lookup_key"] } }; StripeList prices = _client.V1.Prices.List(priceOptions); var options = new SessionCreateOptions { LineItems = new List { new SessionLineItemOptions { Price = prices.Data[0].Id, Quantity = 1, }, }, Mode = "subscription", SuccessUrl = domain + "/success.html?session_id={CHECKOUT_SESSION_ID}", SuccessUrl = domain + "?success=true&session_id={CHECKOUT_SESSION_ID}", SubscriptionData = new SessionSubscriptionDataOptions { TrialPeriodDays = 7, BillingCycleAnchor = 1672531200, }, SubscriptionData = new SessionSubscriptionDataOptions { BillingCycleAnchor = 1672531200, }, Customer = "cus_123", CustomerAccount: stripe.String("acct_123"), AutomaticTax = new SessionAutomaticTaxOptions { Enabled = true }, CustomerAccount = "acct_123", }; Session session = _client.V1.Checkout.Sessions.Create(options); Response.Headers.Add("Location", session.Url); return new StatusCodeResult(303); var options = new Stripe.BillingPortal.SessionCreateOptions { Customer = checkoutSession.CustomerId, ReturnUrl = returnUrl, }; var session = _client.V1.BillingPortal.Sessions.Create(options); var options = new Stripe.BillingPortal.SessionCreateOptions { CustomerAccount = checkoutSession.CustomerAccount, ReturnUrl = returnUrl, }; var session = _client.V1.BillingPortal.Sessions.Create(options); Response.Headers.Add("Location", session.Url); return new StatusCodeResult(303); [Route("webhook")] [ApiController] public class WebhookController : Controller { [HttpPost] public async Task Index() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); // Replace this endpoint secret with your endpoint's unique secret // If you are testing with the CLI, find the secret by running 'stripe listen' // If you are using an endpoint defined with the API or dashboard, look in your webhook settings // at https://dashboard.stripe.com/webhooks const string endpointSecret = "whsec_12345"; try { var stripeEvent = EventUtility.ParseEvent(json); var signatureHeader = Request.Headers["Stripe-Signature"]; stripeEvent = EventUtility.ConstructEvent(json, signatureHeader, endpointSecret); // If on SDK version < 46, use class Events instead of EventTypes if (stripeEvent.Type == EventTypes.CustomerSubscriptionDeleted) { var subscription = stripeEvent.Data.Object as Subscription; Console.WriteLine("A subscription was canceled.", subscription.Id); // Then define and call a method to handle the successful payment intent. // handleSubscriptionCanceled(subscription); } else if (stripeEvent.Type == EventTypes.CustomerSubscriptionUpdated) { var subscription = stripeEvent.Data.Object as Subscription; Console.WriteLine("A subscription was updated.", subscription.Id); // Then define and call a method to handle the successful payment intent. // handleSubscriptionUpdated(subscription); } else if (stripeEvent.Type == EventTypes.CustomerSubscriptionCreated) { var subscription = stripeEvent.Data.Object as Subscription; Console.WriteLine("A subscription was created.", subscription.Id); // Then define and call a method to handle the successful payment intent. // handleSubscriptionUpdated(subscription); } else if (stripeEvent.Type == EventTypes.CustomerSubscriptionTrialWillEnd) { var subscription = stripeEvent.Data.Object as Subscription; Console.WriteLine("A subscription trial will end", subscription.Id); // Then define and call a method to handle the successful payment intent. // handleSubscriptionUpdated(subscription); } else if (stripeEvent.Type == EventTypes.ActiveEntitlementSummaryUpdated) { var summary = stripeEvent.Data.Object as ActiveEntitlementSummary; Console.WriteLine("Active entitlement summary updated for customer", summary.Customer); // Then define and call a method to handle active entitlement summary updated. // handleEntitlementUpdated($subscription); } else { Console.WriteLine("Unhandled event type: {0}", stripeEvent.Type); } return Ok(); } catch (StripeException e) { Console.WriteLine("Error: {0}", e.Message); return BadRequest(); } } "github.com/stripe/stripe-go/v85" "github.com/stripe/stripe-go/v85/webhook" // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. sc = stripe.NewClient("<>") params := &stripe.PriceListParams{ LookupKeys: stripe.StringSlice([]string{ lookup_key, }), } var price *stripe.Price for p, err := range sc.V1Prices.List(context.TODO(), params).All(context.TODO()) { if err != nil { log.Printf("sc.V1Prices.List: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } price = p break } if price == nil { log.Printf(">>>>>>>>>>>>>>>>>>>>>>>>>>> Add a price lookup key to checkout.html for the demo <<<<<<<<<<<<<<<<<<<<<<<<") return } checkoutParams := &stripe.CheckoutSessionCreateParams{ Mode: stripe.String(stripe.CheckoutSessionModeSubscription), LineItems: []*stripe.CheckoutSessionCreateLineItemParams{ &stripe.CheckoutSessionCreateLineItemParams{ Price: stripe.String(price.ID), Quantity: stripe.Int64(1), }, }, SubscriptionData: &stripe.CheckoutSessionCreateSubscriptionDataParams{ TrialPeriodDays: stripe.Int64(7), BillingCycleAnchor: stripe.Int64(1672531200), }, SubscriptionData: &stripe.CheckoutSessionCreateSubscriptionDataParams{ BillingCycleAnchor: stripe.Int64(1672531200), }, Discounts: []*stripe.CheckoutSessionCreateDiscountParams{ &stripe.CheckoutSessionCreateDiscountParams{ Coupon: stripe.String("gBY6sFUf"), }, }, SuccessURL: stripe.String(domain + "/success.html?session_id={CHECKOUT_SESSION_ID}"), SuccessURL: stripe.String(domain + "?success=true&session_id={CHECKOUT_SESSION_ID}"), Customer: stripe.String("cus_123"), CustomerAccount: stripe.String("acct_123"), AutomaticTax: &stripe.CheckoutSessionCreateAutomaticTaxParams{ Enabled: stripe.Bool(true), }, } s, err := sc.V1CheckoutSessions.Create(context.TODO(), checkoutParams) http.Redirect(w, r, s.URL, http.StatusSeeOther) // Authenticate your user. params := &stripe.BillingPortalSessionCreateParams{ Customer: stripe.String(s.Customer.ID), ReturnURL: stripe.String(domain), } ps, _ := sc.V1BillingPortalSessions.Create(context.TODO(), params) log.Printf("sc.V1BillingPortalSessions.Create: %v", ps.URL) // Authenticate your user. params := &stripe.BillingPortalSessionCreateParams{ CustomerAccount: stripe.String(s.CustomerAccount), ReturnURL: stripe.String(domain), } ps, _ := sc.V1BillingPortalSessions.Create(context.TODO(), params) log.Printf("sc.V1BillingPortalSessions.Create: %v", ps.URL) http.Redirect(w, r, ps.URL, http.StatusSeeOther) func handleWebhook(w http.ResponseWriter, req *http.Request) { const MaxBodyBytes = int64(65536) bodyReader := http.MaxBytesReader(w, req.Body, MaxBodyBytes) payload, err := ioutil.ReadAll(bodyReader) if err != nil { fmt.Fprintf(os.Stderr, "Error reading request body: %v\n", err) w.WriteHeader(http.StatusServiceUnavailable) return } // Replace this endpoint secret with your endpoint's unique secret // If you are testing with the CLI, find the secret by running 'stripe listen' // If you are using an endpoint defined with the API or dashboard, look in your webhook settings // at https://dashboard.stripe.com/webhooks endpointSecret := "whsec_12345" signatureHeader := req.Header.Get("Stripe-Signature") event, err := sc.ConstructEvent(payload, signatureHeader, endpointSecret) if err != nil { fmt.Fprintf(os.Stderr, "⚠️ Webhook signature verification failed. %v\n", err) w.WriteHeader(http.StatusBadRequest) // Return a 400 error on a bad signature return } // Unmarshal the event data into an appropriate struct depending on its Type switch event.Type { case "customer.subscription.deleted": var subscription stripe.Subscription err := json.Unmarshal(event.Data.Raw, &subscription) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } log.Printf("Subscription deleted for %d.", subscription.ID) // Then define and call a func to handle the deleted subscription. // handleSubscriptionCanceled(subscription) case "customer.subscription.updated": var subscription stripe.Subscription err := json.Unmarshal(event.Data.Raw, &subscription) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } log.Printf("Subscription updated for %d.", subscription.ID) // Then define and call a func to handle the successful attachment of a PaymentMethod. // handleSubscriptionUpdated(subscription) case "customer.subscription.created": var subscription stripe.Subscription err := json.Unmarshal(event.Data.Raw, &subscription) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } log.Printf("Subscription created for %d.", subscription.ID) // Then define and call a func to handle the successful attachment of a PaymentMethod. // handleSubscriptionCreated(subscription) case "customer.subscription.trial_will_end": var subscription stripe.Subscription err := json.Unmarshal(event.Data.Raw, &subscription) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } log.Printf("Subscription trial will end for %d.", subscription.ID) // Then define and call a func to handle the successful attachment of a PaymentMethod. // handleSubscriptionTrialWillEnd(subscription) case "entitlements.active_entitlement_summary.updated": var subscription stripe.Subscription err := json.Unmarshal(event.Data.Raw, &subscription) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing webhook JSON: %v\n", err) w.WriteHeader(http.StatusBadRequest) return } log.Printf("Active entitlement summary updated for %d.", subscription.ID) // Then define and call a func to handle active entitlement summary updated. // handleEntitlementUpdated(subscription) default: fmt.Fprintf(os.Stderr, "Unhandled event type: %s\n", event.Type) } w.WriteHeader(http.StatusOK) } require github.com/stripe/stripe-go/v85 v85.0.0 // This is a public sample test API key. // Don’t submit any personally identifiable information in requests made with this key. // Sign in to see your own test API key embedded in code samples. // Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. StripeClient client = new StripeClient("<>"); PriceListParams priceParams = PriceListParams.builder().addLookupKey(request.queryParams("lookup_key")).build(); StripeCollection prices = client.v1().prices().list(priceParams); SessionCreateParams params = SessionCreateParams.builder() .addLineItem( SessionCreateParams.LineItem.builder().setPrice(prices.getData().get(0).getId()).setQuantity(1L).build()) .setMode(SessionCreateParams.Mode.SUBSCRIPTION) .setSuccessUrl(YOUR_DOMAIN + "/success.html?session_id={CHECKOUT_SESSION_ID}") .setSuccessUrl(YOUR_DOMAIN + "?success=true&session_id={CHECKOUT_SESSION_ID}") .setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setTrialPeriodDays(7L).setBillingCycleAnchor(1672531200).build()) .setSubscriptionData(SessionCreateParams.SubscriptionData.builder().setBillingCycleAnchor(1672531200).build()) .addDiscount(SessionCreateParams.Discount.builder().setCoupon("{{COUPON_ID}}").build()) .setCustomer("cus_JyTTNqVDAoRYE1") .setCustomerAccount("acct_123") .setAutomaticTax( SessionCreateParams.AutomaticTax.builder() .setEnabled(true) .build()) .build(); Session session = client.v1().checkout().sessions().create(params); response.redirect(session.getUrl(), 303); com.stripe.param.billingportal.SessionCreateParams params = new com.stripe.param.billingportal.SessionCreateParams.Builder() .setCustomer(checkoutSession.getCustomer()) .setReturnUrl(YOUR_DOMAIN).build(); com.stripe.model.billingportal.Session portalSession = client.v1().billingPortal().sessions().create(params); com.stripe.param.billingportal.SessionCreateParams params = new com.stripe.param.billingportal.SessionCreateParams.Builder() .setCustomerAccount(checkoutSession.getCustomerAccount()) .setReturnUrl(YOUR_DOMAIN).build(); com.stripe.model.billingportal.Session portalSession = client.v1().billingPortal().sessions().create(params); response.redirect(portalSession.getUrl(), 303); return ""; post("/webhook", (request, response) -> { String payload = request.body(); Event event = null; try { event = ApiResource.GSON.fromJson(payload, Event.class); } catch (JsonSyntaxException e) { // Invalid payload System.out.println("⚠️ Webhook error while parsing basic request."); response.status(400); return ""; } String sigHeader = request.headers("Stripe-Signature"); if (endpointSecret != null && sigHeader != null) { // Only verify the event if you have an endpoint secret defined. // Otherwise use the basic event deserialized with GSON. try { event = client.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { // Invalid signature System.out.println("⚠️ Webhook error while validating signature."); response.status(400); return ""; } } // Deserialize the nested object inside the event EventDataObjectDeserializer dataObjectDeserializer = event.getDataObjectDeserializer(); StripeObject stripeObject = null; if (dataObjectDeserializer.getObject().isPresent()) { stripeObject = dataObjectDeserializer.getObject().get(); } else { // Deserialization failed, probably due to an API version mismatch. // Refer to the Javadoc documentation on `EventDataObjectDeserializer` for // instructions on how to handle this case, or return an error here. } // Handle the event Subscription subscription = null; switch (event.getType()) { case "customer.subscription.deleted": subscription = (Subscription) stripeObject; // Then define and call a function to handle the event // customer.subscription.deleted // handleSubscriptionTrialEnding(subscription); case "customer.subscription.trial_will_end": subscription = (Subscription) stripeObject; // Then define and call a function to handle the event // customer.subscription.trial_will_end // handleSubscriptionDeleted(subscriptionDeleted); case "customer.subscription.created": subscription = (Subscription) stripeObject; // Then define and call a function to handle the event // customer.subscription.created // handleSubscriptionCreated(subscription); case "customer.subscription.updated": subscription = (Subscription) stripeObject; // Then define and call a function to handle the event // customer.subscription.updated // handleSubscriptionUpdated(subscription); case "entitlements.active_entitlement_summary.updated": subscription = (Subscription) stripeObject; // Then define and call a function to handle the event // entitlements.active_entitlement_summary.updated // handleEntitlementUpdated(subscription); // ... handle other event types default: System.out.println("Unhandled event type: " + event.getType()); } response.status(200); return ""; }); 1. Build the server ~~~ pip3 install -r requirements.txt ~~~ 1. Build the server ~~~ bundle install ~~~ 1. Build the server ~~~ composer install ~~~ 1. Build the server ~~~ dotnet restore ~~~ 1. Build the server ~~~ mvn package ~~~ 2. Run the server ~~~ export FLASK_APP=server.py python3 -m flask run --port=4242 ~~~ 2. Run the server ~~~ ruby server.rb -o 0.0.0.0 ~~~ 2. Run the server ~~~ php -S 127.0.0.1:4242 --docroot=public ~~~ 2. Run the server ~~~ dotnet run ~~~ 2. Run the server ~~~ java -cp target/sample-jar-with-dependencies.jar com.stripe.sample.Server ~~~ 3. Build the client app ~~~ npm install ~~~ 4. Run the client app ~~~ npm start ~~~ 5. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) 3. Build the client app ~~~ npm install ~~~ 4. Run the client app ~~~ npm start ~~~ 5. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) 3. Build the client app ~~~ npm install ~~~ 4. Run the client app ~~~ npm start ~~~ 5. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) 3. Build the client app ~~~ npm install ~~~ 4. Run the client app ~~~ npm start ~~~ 5. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) 3. Build the client app ~~~ npm install ~~~ 4. Run the client app ~~~ npm start ~~~ 5. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) 1. Run the server ~~~ go run server.go ~~~ 2. Build the client app ~~~ npm install ~~~ 3. Run the client app ~~~ npm start ~~~ 4. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 1. Run the server ~~~ go run server.go ~~~ 2. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) 1. Build the application ~~~ npm install ~~~ 2. Run the application ~~~ npm start ~~~ 3. Go to [http://localhost:3000/checkout](http://localhost:3000/checkout) 1. Build the server ~~~ npm install ~~~ 2. Run the server ~~~ npm start ~~~ 3. Go to [http://localhost:4242/checkout.html](http://localhost:4242/checkout.html) ## Prochaines étapes #### [Modifier le tarif des abonnements](https://docs.stripe.com/billing/subscriptions/change-price.md) Modifiez les Abonnement pour gérer les clients qui passent à une offre tarifaire supérieure ou inférieure. #### [Appliquer les calculs au prorata](https://docs.stripe.com/billing/subscriptions/prorations.md) Découvrez comment ajuster la facture d’un client pour qu’elle reflète correctement les changements de tarif en milieu de cycle. #### [Proposer des abonnements supérieurs](https://docs.stripe.com/payments/checkout/upsells.md) Incitez les clients à prendre un abonnement sur une plus longue période en leur accordant des réductions.